| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  | from __future__ import annotations | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | import copy | 
					
						
							| 
									
										
										
										
											2018-01-18 21:51:43 -05:00
										 |  |  | from enum import Enum, unique | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  | import logging | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  | import json | 
					
						
							| 
									
										
										
										
											2020-05-10 19:27:13 +10:00
										 |  |  | from collections import OrderedDict, Counter, deque | 
					
						
							| 
									
										
										
										
											2020-08-27 04:05:11 +02:00
										 |  |  | from typing import Union, Optional, List, Set | 
					
						
							| 
									
										
										
										
											2020-07-14 07:01:51 +02:00
										 |  |  | import secrets | 
					
						
							|  |  |  | import random | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-22 19:19:29 +02:00
										 |  |  | from EntranceShuffle import door_addresses, indirect_connections | 
					
						
							|  |  |  | from Utils import int16_as_bytes | 
					
						
							|  |  |  | from Items import item_name_groups | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-14 07:01:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | class World(object): | 
					
						
							| 
									
										
										
										
											2020-04-10 20:54:18 +02:00
										 |  |  |     debug_types = False | 
					
						
							| 
									
										
										
										
											2020-02-23 17:06:44 +01:00
										 |  |  |     player_names: list | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     _region_cache: dict | 
					
						
							|  |  |  |     difficulty_requirements: dict | 
					
						
							|  |  |  |     required_medallions: dict | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-16 16:49:48 +02:00
										 |  |  |     def __init__(self, players: int, shuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, | 
					
						
							|  |  |  |                  progressive, | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |                  goal, algorithm, accessibility, shuffle_ganon, retro, custom, customitemarray, hints): | 
					
						
							| 
									
										
										
										
											2020-04-10 20:54:18 +02:00
										 |  |  |         if self.debug_types: | 
					
						
							|  |  |  |             import inspect | 
					
						
							|  |  |  |             methods = inspect.getmembers(self, predicate=inspect.ismethod) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for name, method in methods: | 
					
						
							|  |  |  |                 if name.startswith("_debug_"): | 
					
						
							|  |  |  |                     setattr(self, name[7:], method) | 
					
						
							| 
									
										
										
										
											2020-04-10 20:58:52 +02:00
										 |  |  |                     logging.debug(f"Set {self}.{name[7:]} to {method}") | 
					
						
							| 
									
										
										
										
											2020-04-10 20:54:18 +02:00
										 |  |  |             self.get_location = self._debug_get_location | 
					
						
							| 
									
										
										
										
											2020-07-14 07:01:51 +02:00
										 |  |  |         self.random = random.Random()  # world-local random state is saved in case of future use a | 
					
						
							|  |  |  |         # persistently running program with multiple worlds rolling concurrently | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         self.players = players | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |         self.teams = 1 | 
					
						
							| 
									
										
										
										
											2019-12-16 18:24:34 +01:00
										 |  |  |         self.shuffle = shuffle.copy() | 
					
						
							| 
									
										
										
										
											2019-12-16 13:26:07 +01:00
										 |  |  |         self.logic = logic.copy() | 
					
						
							| 
									
										
										
										
											2019-12-16 16:54:46 +01:00
										 |  |  |         self.mode = mode.copy() | 
					
						
							| 
									
										
										
										
											2019-12-16 14:31:47 +01:00
										 |  |  |         self.swords = swords.copy() | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |         self.difficulty = difficulty.copy() | 
					
						
							|  |  |  |         self.difficulty_adjustments = difficulty_adjustments.copy() | 
					
						
							| 
									
										
										
										
											2020-02-02 20:10:56 -05:00
										 |  |  |         self.timer = timer.copy() | 
					
						
							| 
									
										
										
										
											2017-11-10 04:11:40 -06:00
										 |  |  |         self.progressive = progressive | 
					
						
							| 
									
										
										
										
											2019-12-16 15:27:20 +01:00
										 |  |  |         self.goal = goal.copy() | 
					
						
							| 
									
										
										
										
											2017-06-04 15:02:27 +02:00
										 |  |  |         self.algorithm = algorithm | 
					
						
							| 
									
										
										
										
											2017-10-15 12:16:07 -04:00
										 |  |  |         self.dungeons = [] | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         self.regions = [] | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  |         self.shops = [] | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         self.itempool = [] | 
					
						
							| 
									
										
										
										
											2017-05-20 14:03:15 +02:00
										 |  |  |         self.seed = None | 
					
						
							| 
									
										
										
										
											2019-08-10 15:30:14 -04:00
										 |  |  |         self.precollected_items = [] | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         self.state = CollectionState(self) | 
					
						
							| 
									
										
										
										
											2019-01-20 01:01:02 -06:00
										 |  |  |         self._cached_entrances = None | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         self._cached_locations = None | 
					
						
							|  |  |  |         self._entrance_cache = {} | 
					
						
							|  |  |  |         self._location_cache = {} | 
					
						
							| 
									
										
										
										
											2017-06-04 16:15:59 +02:00
										 |  |  |         self.required_locations = [] | 
					
						
							| 
									
										
										
										
											2017-07-14 14:38:13 +02:00
										 |  |  |         self.shuffle_bonk_prizes = False | 
					
						
							| 
									
										
										
										
											2017-06-03 21:27:34 +02:00
										 |  |  |         self.light_world_light_cone = False | 
					
						
							| 
									
										
										
										
											2017-06-03 15:46:05 +02:00
										 |  |  |         self.dark_world_light_cone = False | 
					
						
							| 
									
										
										
										
											2018-01-21 20:43:44 -06:00
										 |  |  |         self.rupoor_cost = 10 | 
					
						
							| 
									
										
										
										
											2017-08-01 18:58:42 +02:00
										 |  |  |         self.aga_randomness = True | 
					
						
							| 
									
										
										
										
											2017-06-04 13:10:22 +02:00
										 |  |  |         self.lock_aga_door_in_escape = False | 
					
						
							| 
									
										
										
										
											2018-09-22 22:51:54 -04:00
										 |  |  |         self.save_and_quit_from_boss = True | 
					
						
							| 
									
										
										
										
											2019-12-17 12:14:29 +01:00
										 |  |  |         self.accessibility = accessibility.copy() | 
					
						
							| 
									
										
										
										
											2017-07-17 22:28:29 +02:00
										 |  |  |         self.shuffle_ganon = shuffle_ganon | 
					
						
							|  |  |  |         self.fix_gtower_exit = self.shuffle_ganon | 
					
						
							| 
									
										
										
										
											2019-12-17 00:16:02 +01:00
										 |  |  |         self.retro = retro.copy() | 
					
						
							| 
									
										
										
										
											2018-01-21 20:43:44 -06:00
										 |  |  |         self.custom = custom | 
					
						
							| 
									
										
										
										
											2020-08-24 02:24:48 +02:00
										 |  |  |         self.customitemarray: List[int] = customitemarray | 
					
						
							| 
									
										
										
										
											2019-12-17 12:22:55 +01:00
										 |  |  |         self.hints = hints.copy() | 
					
						
							| 
									
										
										
										
											2018-03-22 23:18:40 -04:00
										 |  |  |         self.dynamic_regions = [] | 
					
						
							|  |  |  |         self.dynamic_locations = [] | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  |         self.spoiler = Spoiler(self) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-17 21:09:33 +01:00
										 |  |  |         for player in range(1, players + 1): | 
					
						
							|  |  |  |             def set_player_attr(attr, val): | 
					
						
							|  |  |  |                 self.__dict__.setdefault(attr, {})[player] = val | 
					
						
							|  |  |  |             set_player_attr('_region_cache', {}) | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |             set_player_attr('player_names', []) | 
					
						
							| 
									
										
										
										
											2020-01-18 09:50:12 +01:00
										 |  |  |             set_player_attr('remote_items', False) | 
					
						
							| 
									
										
										
										
											2019-12-17 21:09:33 +01:00
										 |  |  |             set_player_attr('required_medallions', ['Ether', 'Quake']) | 
					
						
							|  |  |  |             set_player_attr('swamp_patch_required', False) | 
					
						
							|  |  |  |             set_player_attr('powder_patch_required', False) | 
					
						
							|  |  |  |             set_player_attr('ganon_at_pyramid', True) | 
					
						
							|  |  |  |             set_player_attr('ganonstower_vanilla', True) | 
					
						
							|  |  |  |             set_player_attr('sewer_light_cone', self.mode[player] == 'standard') | 
					
						
							|  |  |  |             set_player_attr('fix_trock_doors', self.shuffle[player] != 'vanilla' or self.mode[player] == 'inverted') | 
					
						
							|  |  |  |             set_player_attr('fix_skullwoods_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']) | 
					
						
							|  |  |  |             set_player_attr('fix_palaceofdarkness_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']) | 
					
						
							|  |  |  |             set_player_attr('fix_trock_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']) | 
					
						
							| 
									
										
										
										
											2019-12-18 20:45:51 +01:00
										 |  |  |             set_player_attr('can_access_trock_eyebridge', None) | 
					
						
							|  |  |  |             set_player_attr('can_access_trock_front', None) | 
					
						
							|  |  |  |             set_player_attr('can_access_trock_big_chest', None) | 
					
						
							|  |  |  |             set_player_attr('can_access_trock_middle', None) | 
					
						
							|  |  |  |             set_player_attr('fix_fake_world', True) | 
					
						
							| 
									
										
										
										
											2019-12-17 21:09:33 +01:00
										 |  |  |             set_player_attr('mapshuffle', False) | 
					
						
							|  |  |  |             set_player_attr('compassshuffle', False) | 
					
						
							|  |  |  |             set_player_attr('keyshuffle', False) | 
					
						
							|  |  |  |             set_player_attr('bigkeyshuffle', False) | 
					
						
							|  |  |  |             set_player_attr('difficulty_requirements', None) | 
					
						
							|  |  |  |             set_player_attr('boss_shuffle', 'none') | 
					
						
							| 
									
										
										
										
											2020-08-19 23:24:17 +02:00
										 |  |  |             set_player_attr('enemy_shuffle', False) | 
					
						
							| 
									
										
										
										
											2019-12-17 21:09:33 +01:00
										 |  |  |             set_player_attr('enemy_health', 'default') | 
					
						
							|  |  |  |             set_player_attr('enemy_damage', 'default') | 
					
						
							| 
									
										
										
										
											2020-08-19 23:24:17 +02:00
										 |  |  |             set_player_attr('killable_thieves', False) | 
					
						
							|  |  |  |             set_player_attr('tile_shuffle', False) | 
					
						
							|  |  |  |             set_player_attr('bush_shuffle', False) | 
					
						
							| 
									
										
										
										
											2019-12-30 03:03:53 +01:00
										 |  |  |             set_player_attr('beemizer', 0) | 
					
						
							| 
									
										
										
										
											2019-12-17 21:09:33 +01:00
										 |  |  |             set_player_attr('escape_assist', []) | 
					
						
							|  |  |  |             set_player_attr('crystals_needed_for_ganon', 7) | 
					
						
							|  |  |  |             set_player_attr('crystals_needed_for_gt', 7) | 
					
						
							|  |  |  |             set_player_attr('open_pyramid', False) | 
					
						
							| 
									
										
										
										
											2019-12-21 10:42:59 +01:00
										 |  |  |             set_player_attr('treasure_hunt_icon', 'Triforce Piece') | 
					
						
							|  |  |  |             set_player_attr('treasure_hunt_count', 0) | 
					
						
							| 
									
										
										
										
											2020-03-04 13:55:03 +01:00
										 |  |  |             set_player_attr('clock_mode', False) | 
					
						
							| 
									
										
										
										
											2020-02-02 21:52:57 -05:00
										 |  |  |             set_player_attr('can_take_damage', True) | 
					
						
							| 
									
										
										
										
											2020-04-16 11:02:16 +02:00
										 |  |  |             set_player_attr('glitch_boots', True) | 
					
						
							| 
									
										
										
										
											2020-05-18 03:54:29 +02:00
										 |  |  |             set_player_attr('progression_balancing', True) | 
					
						
							| 
									
										
										
										
											2020-06-03 22:13:58 +02:00
										 |  |  |             set_player_attr('local_items', set()) | 
					
						
							| 
									
										
										
										
											2020-06-17 01:02:54 -07:00
										 |  |  |             set_player_attr('triforce_pieces_available', 30) | 
					
						
							| 
									
										
										
										
											2020-06-07 15:22:24 +02:00
										 |  |  |             set_player_attr('triforce_pieces_required', 20) | 
					
						
							| 
									
										
										
										
											2020-08-23 15:03:06 +02:00
										 |  |  |             set_player_attr('shop_shuffle', 'off') | 
					
						
							| 
									
										
										
										
											2019-12-17 21:09:33 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-14 07:01:51 +02:00
										 |  |  |     def secure(self): | 
					
						
							|  |  |  |         self.random = secrets.SystemRandom() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-19 03:01:23 +02:00
										 |  |  |     @property | 
					
						
							|  |  |  |     def player_ids(self): | 
					
						
							|  |  |  |         yield from range(1, self.players + 1) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def get_name_string_for_object(self, obj) -> str: | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |         return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-20 03:57:09 +02:00
										 |  |  |     def get_player_names(self, player: int) -> str: | 
					
						
							|  |  |  |         return ", ".join(self.player_names[player]) | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-14 19:19:08 +01:00
										 |  |  |     def initialize_regions(self, regions=None): | 
					
						
							|  |  |  |         for region in regions if regions else self.regions: | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  |             region.world = self | 
					
						
							| 
									
										
										
										
											2019-12-14 19:19:08 +01:00
										 |  |  |             self._region_cache[region.player][region.name] = region | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def get_regions(self, player=None): | 
					
						
							|  |  |  |         return self.regions if player is None else self._region_cache[player].values() | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:17:36 +01:00
										 |  |  |     def get_region(self, regionname: str, player: int) -> Region: | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2019-12-14 19:19:08 +01:00
										 |  |  |             return self._region_cache[player][regionname] | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         except KeyError: | 
					
						
							|  |  |  |             for region in self.regions: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                 if region.name == regionname and region.player == player: | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |                     assert not region.world  # this should only happen before initialization | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |                     return region | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |             raise RuntimeError('No such region %s for player %d' % (regionname, player)) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 20:54:18 +02:00
										 |  |  |     def _debug_get_region(self, regionname: str, player: int) -> Region: | 
					
						
							|  |  |  |         if type(regionname) != str: | 
					
						
							|  |  |  |             raise TypeError(f"expected str, got {type(regionname)} instead") | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             return self._region_cache[player][regionname] | 
					
						
							|  |  |  |         except KeyError: | 
					
						
							|  |  |  |             for region in self.regions: | 
					
						
							|  |  |  |                 if region.name == regionname and region.player == player: | 
					
						
							|  |  |  |                     assert not region.world  # this should only happen before initialization | 
					
						
							|  |  |  |                     return region | 
					
						
							|  |  |  |             raise RuntimeError('No such region %s for player %d' % (regionname, player)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:17:36 +01:00
										 |  |  |     def get_entrance(self, entrance: str, player: int) -> Entrance: | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |             return self._entrance_cache[(entrance, player)] | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         except KeyError: | 
					
						
							|  |  |  |             for region in self.regions: | 
					
						
							|  |  |  |                 for exit in region.exits: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                     if exit.name == entrance and exit.player == player: | 
					
						
							|  |  |  |                         self._entrance_cache[(entrance, player)] = exit | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |                         return exit | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |             raise RuntimeError('No such entrance %s for player %d' % (entrance, player)) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 20:54:18 +02:00
										 |  |  |     def _debug_get_entrance(self, entrance: str, player: int) -> Entrance: | 
					
						
							|  |  |  |         if type(entrance) != str: | 
					
						
							|  |  |  |             raise TypeError(f"expected str, got {type(entrance)} instead") | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             return self._entrance_cache[(entrance, player)] | 
					
						
							|  |  |  |         except KeyError: | 
					
						
							|  |  |  |             for region in self.regions: | 
					
						
							|  |  |  |                 for exit in region.exits: | 
					
						
							|  |  |  |                     if exit.name == entrance and exit.player == player: | 
					
						
							|  |  |  |                         self._entrance_cache[(entrance, player)] = exit | 
					
						
							|  |  |  |                         return exit | 
					
						
							|  |  |  |             raise RuntimeError('No such entrance %s for player %d' % (entrance, player)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:17:36 +01:00
										 |  |  |     def get_location(self, location: str, player: int) -> Location: | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |             return self._location_cache[(location, player)] | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         except KeyError: | 
					
						
							|  |  |  |             for region in self.regions: | 
					
						
							|  |  |  |                 for r_location in region.locations: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                     if r_location.name == location and r_location.player == player: | 
					
						
							|  |  |  |                         self._location_cache[(location, player)] = r_location | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |                         return r_location | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         raise RuntimeError('No such location %s for player %d' % (location, player)) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 20:54:18 +02:00
										 |  |  |     def _debug_get_location(self, location: str, player: int) -> Location: | 
					
						
							|  |  |  |         if type(location) != str: | 
					
						
							|  |  |  |             raise TypeError(f"expected str, got {type(location)} instead") | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             return self._location_cache[(location, player)] | 
					
						
							|  |  |  |         except KeyError: | 
					
						
							|  |  |  |             for region in self.regions: | 
					
						
							|  |  |  |                 for r_location in region.locations: | 
					
						
							|  |  |  |                     if r_location.name == location and r_location.player == player: | 
					
						
							|  |  |  |                         self._location_cache[(location, player)] = r_location | 
					
						
							|  |  |  |                         return r_location | 
					
						
							|  |  |  |         raise RuntimeError('No such location %s for player %d' % (location, player)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:17:36 +01:00
										 |  |  |     def get_dungeon(self, dungeonname: str, player: int) -> Dungeon: | 
					
						
							| 
									
										
										
										
											2018-09-26 13:12:20 -04:00
										 |  |  |         for dungeon in self.dungeons: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |             if dungeon.name == dungeonname and dungeon.player == player: | 
					
						
							| 
									
										
										
										
											2018-09-26 13:12:20 -04:00
										 |  |  |                 return dungeon | 
					
						
							| 
									
										
										
										
											2020-04-10 20:54:18 +02:00
										 |  |  |         raise RuntimeError('No such dungeon %s for player %d' % (dungeonname, player)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _debug_get_dungeon(self, dungeonname: str, player: int) -> Dungeon: | 
					
						
							|  |  |  |         if type(dungeonname) != str: | 
					
						
							|  |  |  |             raise TypeError(f"expected str, got {type(dungeonname)} instead") | 
					
						
							|  |  |  |         for dungeon in self.dungeons: | 
					
						
							|  |  |  |             if dungeon.name == dungeonname and dungeon.player == player: | 
					
						
							|  |  |  |                 return dungeon | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         raise RuntimeError('No such dungeon %s for player %d' % (dungeonname, player)) | 
					
						
							| 
									
										
										
										
											2018-09-26 13:12:20 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def get_all_state(self, keys=False) -> CollectionState: | 
					
						
							| 
									
										
										
										
											2017-06-17 14:40:37 +02:00
										 |  |  |         ret = CollectionState(self) | 
					
						
							| 
									
										
										
										
											2017-11-04 14:23:57 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-17 14:40:37 +02:00
										 |  |  |         def soft_collect(item): | 
					
						
							|  |  |  |             if item.name.startswith('Progressive '): | 
					
						
							|  |  |  |                 if 'Sword' in item.name: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                     if ret.has('Golden Sword', item.player): | 
					
						
							| 
									
										
										
										
											2017-06-17 14:40:37 +02:00
										 |  |  |                         pass | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |                     elif ret.has('Tempered Sword', item.player) and self.difficulty_requirements[ | 
					
						
							|  |  |  |                         item.player].progressive_sword_limit >= 4: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                         ret.prog_items['Golden Sword', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |                     elif ret.has('Master Sword', item.player) and self.difficulty_requirements[ | 
					
						
							|  |  |  |                         item.player].progressive_sword_limit >= 3: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                         ret.prog_items['Tempered Sword', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |                     elif ret.has('Fighter Sword', item.player) and self.difficulty_requirements[item.player].progressive_sword_limit >= 2: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                         ret.prog_items['Master Sword', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |                     elif self.difficulty_requirements[item.player].progressive_sword_limit >= 1: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                         ret.prog_items['Fighter Sword', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2017-06-17 14:40:37 +02:00
										 |  |  |                 elif 'Glove' in item.name: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                     if ret.has('Titans Mitts', item.player): | 
					
						
							| 
									
										
										
										
											2017-06-17 14:40:37 +02:00
										 |  |  |                         pass | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                     elif ret.has('Power Glove', item.player): | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                         ret.prog_items['Titans Mitts', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2017-06-17 14:40:37 +02:00
										 |  |  |                     else: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                         ret.prog_items['Power Glove', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2017-12-16 15:38:48 -05:00
										 |  |  |                 elif 'Shield' in item.name: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                     if ret.has('Mirror Shield', item.player): | 
					
						
							| 
									
										
										
										
											2017-12-16 15:38:48 -05:00
										 |  |  |                         pass | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |                     elif ret.has('Red Shield', item.player) and self.difficulty_requirements[item.player].progressive_shield_limit >= 3: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                         ret.prog_items['Mirror Shield', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |                     elif ret.has('Blue Shield', item.player)  and self.difficulty_requirements[item.player].progressive_shield_limit >= 2: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                         ret.prog_items['Red Shield', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |                     elif self.difficulty_requirements[item.player].progressive_shield_limit >= 1: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                         ret.prog_items['Blue Shield', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2019-08-04 12:32:35 -04:00
										 |  |  |                 elif 'Bow' in item.name: | 
					
						
							| 
									
										
										
										
											2020-06-30 09:51:11 +02:00
										 |  |  |                     if ret.has('Silver', item.player): | 
					
						
							| 
									
										
										
										
											2019-08-04 12:32:35 -04:00
										 |  |  |                         pass | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |                     elif ret.has('Bow', item.player) and self.difficulty_requirements[item.player].progressive_bow_limit >= 2: | 
					
						
							| 
									
										
										
										
											2020-06-30 09:51:11 +02:00
										 |  |  |                         ret.prog_items['Silver Bow', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |                     elif self.difficulty_requirements[item.player].progressive_bow_limit >= 1: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                         ret.prog_items['Bow', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2018-01-04 01:06:22 -05:00
										 |  |  |             elif item.name.startswith('Bottle'): | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |                 if ret.bottle_count(item.player) < self.difficulty_requirements[item.player].progressive_bottle_limit: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                     ret.prog_items[item.name, item.player] += 1 | 
					
						
							| 
									
										
										
										
											2019-12-13 22:37:52 +01:00
										 |  |  |             elif item.advancement or item.smallkey or item.bigkey: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                 ret.prog_items[item.name, item.player] += 1 | 
					
						
							| 
									
										
										
										
											2017-06-17 14:40:37 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         for item in self.itempool: | 
					
						
							|  |  |  |             soft_collect(item) | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-05 17:52:18 +02:00
										 |  |  |         if keys: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |             for p in range(1, self.players + 1): | 
					
						
							|  |  |  |                 from Items import ItemFactory | 
					
						
							| 
									
										
										
										
											2020-06-24 16:22:49 +02:00
										 |  |  |                 for item in ItemFactory( | 
					
						
							|  |  |  |                         ['Small Key (Hyrule Castle)', 'Big Key (Eastern Palace)', 'Big Key (Desert Palace)', | 
					
						
							|  |  |  |                          'Small Key (Desert Palace)', 'Big Key (Tower of Hera)', 'Small Key (Tower of Hera)', | 
					
						
							|  |  |  |                          'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', | 
					
						
							|  |  |  |                          'Big Key (Palace of Darkness)'] + ['Small Key (Palace of Darkness)'] * 6 + [ | 
					
						
							|  |  |  |                             'Big Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Skull Woods)'] + [ | 
					
						
							|  |  |  |                             'Small Key (Skull Woods)'] * 3 + ['Big Key (Swamp Palace)', | 
					
						
							|  |  |  |                                                               'Small Key (Swamp Palace)', 'Big Key (Ice Palace)'] + [ | 
					
						
							|  |  |  |                             'Small Key (Ice Palace)'] * 2 + ['Big Key (Misery Mire)', 'Big Key (Turtle Rock)', | 
					
						
							|  |  |  |                                                              'Big Key (Ganons Tower)'] + [ | 
					
						
							|  |  |  |                             'Small Key (Misery Mire)'] * 3 + ['Small Key (Turtle Rock)'] * 4 + [ | 
					
						
							|  |  |  |                             'Small Key (Ganons Tower)'] * 4, | 
					
						
							|  |  |  |                         p): | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                     soft_collect(item) | 
					
						
							| 
									
										
										
										
											2017-07-17 23:14:31 +02:00
										 |  |  |         ret.sweep_for_events() | 
					
						
							| 
									
										
										
										
											2017-06-17 14:40:37 +02:00
										 |  |  |         return ret | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def get_items(self) -> list: | 
					
						
							| 
									
										
										
										
											2018-01-02 20:01:16 -05:00
										 |  |  |         return [loc.item for loc in self.get_filled_locations()] + self.itempool | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def find_items(self, item, player: int) -> list: | 
					
						
							|  |  |  |         return [location for location in self.get_locations() if | 
					
						
							|  |  |  |                 location.item is not None and location.item.name == item and location.item.player == player] | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def push_precollected(self, item: Item): | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |         item.world = self | 
					
						
							| 
									
										
										
										
											2020-01-09 08:31:49 +01:00
										 |  |  |         if (item.smallkey and self.keyshuffle[item.player]) or (item.bigkey and self.bigkeyshuffle[item.player]): | 
					
						
							|  |  |  |             item.advancement = True | 
					
						
							| 
									
										
										
										
											2019-08-10 15:30:14 -04:00
										 |  |  |         self.precollected_items.append(item) | 
					
						
							|  |  |  |         self.state.collect(item, True) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def push_item(self, location: Location, item: Item, collect: bool = True): | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         if not isinstance(location, Location): | 
					
						
							| 
									
										
										
										
											2020-08-14 00:34:41 +02:00
										 |  |  |             raise RuntimeError( | 
					
						
							|  |  |  |                 'Cannot assign item %s to invalid location %s (player %d).' % (item, location, item.player)) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-02 00:39:53 -05:00
										 |  |  |         if location.can_fill(self.state, item, False): | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |             location.item = item | 
					
						
							|  |  |  |             item.location = location | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |             item.world = self | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |             if collect: | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  |                 self.state.collect(item, location.event, location) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-14 00:34:41 +02:00
										 |  |  |             logging.debug('Placed %s at %s', item, location) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         else: | 
					
						
							|  |  |  |             raise RuntimeError('Cannot assign item %s to location %s.' % (item, location)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def get_entrances(self) -> list: | 
					
						
							| 
									
										
										
										
											2019-01-20 01:01:02 -06:00
										 |  |  |         if self._cached_entrances is None: | 
					
						
							| 
									
										
										
										
											2020-08-27 04:05:11 +02:00
										 |  |  |             self._cached_entrances = [entrance for region in self.regions for entrance in region.entrances] | 
					
						
							| 
									
										
										
										
											2019-01-20 01:01:02 -06:00
										 |  |  |         return self._cached_entrances | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def clear_entrance_cache(self): | 
					
						
							|  |  |  |         self._cached_entrances = None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def get_locations(self) -> list: | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         if self._cached_locations is None: | 
					
						
							| 
									
										
										
										
											2020-08-27 04:05:11 +02:00
										 |  |  |             self._cached_locations = [location for region in self.regions for location in region.locations] | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         return self._cached_locations | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-22 23:18:40 -04:00
										 |  |  |     def clear_location_cache(self): | 
					
						
							|  |  |  |         self._cached_locations = None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def get_unfilled_locations(self, player=None) -> list: | 
					
						
							| 
									
										
										
										
											2020-06-03 02:19:16 +02:00
										 |  |  |         if player is not None: | 
					
						
							|  |  |  |             return [location for location in self.get_locations() if | 
					
						
							|  |  |  |                     location.player == player and not location.item] | 
					
						
							|  |  |  |         return [location for location in self.get_locations() if not location.item] | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def get_filled_locations(self, player=None) -> list: | 
					
						
							| 
									
										
										
										
											2020-08-14 00:34:41 +02:00
										 |  |  |         if player is not None: | 
					
						
							|  |  |  |             return [location for location in self.get_locations() if | 
					
						
							|  |  |  |                     location.player == player and location.item is not None] | 
					
						
							|  |  |  |         return [location for location in self.get_locations() if location.item is not None] | 
					
						
							| 
									
										
										
										
											2017-06-17 14:40:37 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def get_reachable_locations(self, state=None, player=None) -> list: | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         if state is None: | 
					
						
							|  |  |  |             state = self.state | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |         return [location for location in self.get_locations() if | 
					
						
							|  |  |  |                 (player is None or location.player == player) and location.can_reach(state)] | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def get_placeable_locations(self, state=None, player=None) -> list: | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         if state is None: | 
					
						
							|  |  |  |             state = self.state | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |         return [location for location in self.get_locations() if | 
					
						
							|  |  |  |                 (player is None or location.player == player) and location.item is None and location.can_reach(state)] | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def unlocks_new_location(self, item) -> bool: | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         temp_state = self.state.copy() | 
					
						
							| 
									
										
										
										
											2017-06-17 14:40:37 +02:00
										 |  |  |         temp_state.collect(item, True) | 
					
						
							| 
									
										
										
										
											2017-05-26 09:55:49 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         for location in self.get_unfilled_locations(): | 
					
						
							|  |  |  |             if temp_state.can_reach(location) and not self.state.can_reach(location): | 
					
						
							|  |  |  |                 return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return False | 
					
						
							| 
									
										
										
										
											2017-11-18 20:43:37 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-25 19:45:33 +02:00
										 |  |  |     def has_beaten_game(self, state, player: Optional[int] = None): | 
					
						
							| 
									
										
										
										
											2019-07-09 22:18:24 -04:00
										 |  |  |         if player: | 
					
						
							| 
									
										
										
										
											2020-07-12 20:19:45 +10:00
										 |  |  |             return state.has('Triforce', player) or state.world.logic[player] == 'nologic' | 
					
						
							| 
									
										
										
										
											2019-07-09 22:18:24 -04:00
										 |  |  |         else: | 
					
						
							|  |  |  |             return all((self.has_beaten_game(state, p) for p in range(1, self.players + 1))) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-23 22:15:29 +02:00
										 |  |  |     def can_beat_game(self, starting_state=None): | 
					
						
							|  |  |  |         if starting_state: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:20:11 +01:00
										 |  |  |             if self.has_beaten_game(starting_state): | 
					
						
							|  |  |  |                 return True | 
					
						
							| 
									
										
										
										
											2017-06-23 22:15:29 +02:00
										 |  |  |             state = starting_state.copy() | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:20:11 +01:00
										 |  |  |             if self.has_beaten_game(self.state): | 
					
						
							|  |  |  |                 return True | 
					
						
							| 
									
										
										
										
											2017-06-23 22:15:29 +02:00
										 |  |  |             state = CollectionState(self) | 
					
						
							| 
									
										
										
										
											2020-03-07 23:20:11 +01:00
										 |  |  |         prog_locations = {location for location in self.get_locations() if location.item is not None and ( | 
					
						
							|  |  |  |                     location.item.advancement or location.event) and location not in state.locations_checked} | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |         while prog_locations: | 
					
						
							|  |  |  |             sphere = [] | 
					
						
							|  |  |  |             # build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres | 
					
						
							|  |  |  |             for location in prog_locations: | 
					
						
							| 
									
										
										
										
											2019-07-11 00:12:09 -04:00
										 |  |  |                 if location.can_reach(state): | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |                     sphere.append(location) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if not sphere: | 
					
						
							| 
									
										
										
										
											2019-07-11 00:12:09 -04:00
										 |  |  |                 # ran out of places and did not finish yet, quit | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |                 return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for location in sphere: | 
					
						
							|  |  |  |                 prog_locations.remove(location) | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  |                 state.collect(location.item, True, location) | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-11 00:12:09 -04:00
										 |  |  |             if self.has_beaten_game(state): | 
					
						
							|  |  |  |                 return True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | class CollectionState(object): | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def __init__(self, parent: World): | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |         self.prog_items = Counter() | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         self.world = parent | 
					
						
							| 
									
										
										
										
											2019-07-11 00:18:30 -04:00
										 |  |  |         self.reachable_regions = {player: set() for player in range(1, parent.players + 1)} | 
					
						
							| 
									
										
										
										
											2020-05-10 19:27:13 +10:00
										 |  |  |         self.blocked_connections = {player: set() for player in range(1, parent.players + 1)} | 
					
						
							| 
									
										
										
										
											2020-08-22 19:19:29 +02:00
										 |  |  |         self.events = set() | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  |         self.path = {} | 
					
						
							|  |  |  |         self.locations_checked = set() | 
					
						
							| 
									
										
										
										
											2019-07-11 00:18:30 -04:00
										 |  |  |         self.stale = {player: True for player in range(1, parent.players + 1)} | 
					
						
							| 
									
										
										
										
											2019-08-10 15:30:14 -04:00
										 |  |  |         for item in parent.precollected_items: | 
					
						
							|  |  |  |             self.collect(item, True) | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def update_reachable_regions(self, player: int): | 
					
						
							| 
									
										
										
										
											2019-07-11 00:18:30 -04:00
										 |  |  |         self.stale[player] = False | 
					
						
							|  |  |  |         rrp = self.reachable_regions[player] | 
					
						
							| 
									
										
										
										
											2020-05-10 19:27:13 +10:00
										 |  |  |         bc = self.blocked_connections[player] | 
					
						
							|  |  |  |         queue = deque(self.blocked_connections[player]) | 
					
						
							|  |  |  |         start = self.world.get_region('Menu', player) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # init on first call - this can't be done on construction since the regions don't exist yet | 
					
						
							|  |  |  |         if not start in rrp: | 
					
						
							|  |  |  |             rrp.add(start) | 
					
						
							|  |  |  |             bc.update(start.exits) | 
					
						
							|  |  |  |             queue.extend(start.exits) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # run BFS on all connections, and keep track of those blocked by missing items | 
					
						
							| 
									
										
										
										
											2020-06-30 07:32:05 +02:00
										 |  |  |         while queue: | 
					
						
							|  |  |  |             connection = queue.popleft() | 
					
						
							|  |  |  |             new_region = connection.connected_region | 
					
						
							|  |  |  |             if new_region in rrp: | 
					
						
							|  |  |  |                 bc.remove(connection) | 
					
						
							|  |  |  |             elif connection.can_reach(self): | 
					
						
							|  |  |  |                 rrp.add(new_region) | 
					
						
							|  |  |  |                 bc.remove(connection) | 
					
						
							|  |  |  |                 bc.update(new_region.exits) | 
					
						
							|  |  |  |                 queue.extend(new_region.exits) | 
					
						
							|  |  |  |                 self.path[new_region] = (new_region.name, self.path.get(connection, None)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 # Retry connections if the new region can unblock them | 
					
						
							|  |  |  |                 if new_region.name in indirect_connections: | 
					
						
							|  |  |  |                     new_entrance = self.world.get_entrance(indirect_connections[new_region.name], player) | 
					
						
							|  |  |  |                     if new_entrance in bc and new_entrance not in queue: | 
					
						
							|  |  |  |                         queue.append(new_entrance) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def copy(self) -> CollectionState: | 
					
						
							| 
									
										
										
										
											2017-06-17 14:40:37 +02:00
										 |  |  |         ret = CollectionState(self.world) | 
					
						
							| 
									
										
										
										
											2019-07-13 18:17:16 -04:00
										 |  |  |         ret.prog_items = self.prog_items.copy() | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |         ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in | 
					
						
							|  |  |  |                                  range(1, self.world.players + 1)} | 
					
						
							| 
									
										
										
										
											2020-05-10 19:27:13 +10:00
										 |  |  |         ret.blocked_connections = {player: copy.copy(self.blocked_connections[player]) for player in range(1, self.world.players + 1)} | 
					
						
							| 
									
										
										
										
											2017-06-17 14:40:37 +02:00
										 |  |  |         ret.events = copy.copy(self.events) | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  |         ret.path = copy.copy(self.path) | 
					
						
							|  |  |  |         ret.locations_checked = copy.copy(self.locations_checked) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         return ret | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-30 07:32:05 +02:00
										 |  |  |     def can_reach(self, spot, resolution_hint=None, player=None) -> bool: | 
					
						
							| 
									
										
										
										
											2020-03-08 05:41:56 +01:00
										 |  |  |         if not hasattr(spot, "spot_type"): | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |             # try to resolve a name | 
					
						
							|  |  |  |             if resolution_hint == 'Location': | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                 spot = self.world.get_location(spot, player) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |             elif resolution_hint == 'Entrance': | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                 spot = self.world.get_entrance(spot, player) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |             else: | 
					
						
							|  |  |  |                 # default to Region | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                 spot = self.world.get_region(spot, player) | 
					
						
							| 
									
										
										
										
											2019-07-08 22:48:16 -04:00
										 |  |  |         return spot.can_reach(self) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-30 07:32:05 +02:00
										 |  |  |     def sweep_for_events(self, key_only: bool = False, locations=None): | 
					
						
							| 
									
										
										
										
											2019-12-13 22:37:52 +01:00
										 |  |  |         if locations is None: | 
					
						
							|  |  |  |             locations = self.world.get_filled_locations() | 
					
						
							| 
									
										
										
										
											2017-06-17 14:40:37 +02:00
										 |  |  |         new_locations = True | 
					
						
							|  |  |  |         while new_locations: | 
					
						
							| 
									
										
										
										
											2020-08-22 19:19:29 +02:00
										 |  |  |             reachable_events = {location for location in locations if location.event and | 
					
						
							| 
									
										
										
										
											2020-06-30 07:32:05 +02:00
										 |  |  |                                 (not key_only or (not self.world.keyshuffle[ | 
					
						
							|  |  |  |                                     location.item.player] and location.item.smallkey) or (not self.world.bigkeyshuffle[ | 
					
						
							|  |  |  |                                     location.item.player] and location.item.bigkey)) | 
					
						
							| 
									
										
										
										
											2020-08-22 19:19:29 +02:00
										 |  |  |                                 and location.can_reach(self)} | 
					
						
							|  |  |  |             new_locations = reachable_events - self.events | 
					
						
							|  |  |  |             for event in new_locations: | 
					
						
							|  |  |  |                 self.events.add(event) | 
					
						
							|  |  |  |                 self.collect(event.item, True, event) | 
					
						
							| 
									
										
										
										
											2019-07-13 18:17:16 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-30 07:32:05 +02:00
										 |  |  |     def has(self, item, player: int, count: int = 1): | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |         return self.prog_items[item, player] >= count | 
					
						
							| 
									
										
										
										
											2017-11-18 20:43:37 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-30 07:32:05 +02:00
										 |  |  |     def has_key(self, item, player, count: int = 1): | 
					
						
							| 
									
										
										
										
											2020-07-16 18:59:23 +10:00
										 |  |  |         if self.world.logic[player] == 'nologic': | 
					
						
							|  |  |  |             return True | 
					
						
							| 
									
										
										
										
											2020-08-20 20:13:00 +02:00
										 |  |  |         if self.world.keyshuffle[player] == "universal": | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |             return self.can_buy_unlimited('Small Key (Universal)', player) | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |         return self.prog_items[item, player] >= count | 
					
						
							| 
									
										
										
										
											2018-03-15 16:23:02 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def can_buy_unlimited(self, item: str, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2020-08-20 20:13:00 +02:00
										 |  |  |         return any(shop.region.player == player and shop.has_unlimited(item) and shop.region.can_reach(self) for | 
					
						
							|  |  |  |                    shop in self.world.shops) | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-23 21:38:21 +02:00
										 |  |  |     def can_buy(self, item: str, player: int) -> bool: | 
					
						
							|  |  |  |         return any(shop.region.player == player and shop.has(item) and shop.region.can_reach(self) for | 
					
						
							|  |  |  |                    shop in self.world.shops) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def item_count(self, item, player: int) -> int: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |         return self.prog_items[item, player] | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-26 07:18:53 -07:00
										 |  |  |     def has_triforce_pieces(self, count: int, player: int) -> bool: | 
					
						
							|  |  |  |         return self.item_count('Triforce Piece', player) + self.item_count('Power Star', player) >= count | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def has_crystals(self, count: int, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2020-08-14 00:34:41 +02:00
										 |  |  |         found: int = 0 | 
					
						
							| 
									
										
										
										
											2020-08-22 19:19:29 +02:00
										 |  |  |         for crystalnumber in range(1, 8): | 
					
						
							|  |  |  |             found += self.prog_items[f"Crystal {crystalnumber}", player] | 
					
						
							|  |  |  |             if found >= count: | 
					
						
							|  |  |  |                 return True | 
					
						
							| 
									
										
										
										
											2020-08-14 00:34:41 +02:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2019-07-25 18:25:14 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-14 00:34:41 +02:00
										 |  |  |     def can_lift_rocks(self, player: int): | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         return self.has('Power Glove', player) or self.has('Titans Mitts', player) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def has_bottle(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2020-08-14 00:34:41 +02:00
										 |  |  |         return self.has_bottles(1, player) | 
					
						
							| 
									
										
										
										
											2018-01-04 01:06:22 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def bottle_count(self, player: int) -> int: | 
					
						
							| 
									
										
										
										
											2020-08-22 19:19:29 +02:00
										 |  |  |         found: int = 0 | 
					
						
							|  |  |  |         for bottlename in item_name_groups["Bottles"]: | 
					
						
							|  |  |  |             found += self.prog_items[bottlename, player] | 
					
						
							|  |  |  |         return found | 
					
						
							| 
									
										
										
										
											2020-08-14 00:34:41 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-22 19:19:29 +02:00
										 |  |  |     def has_bottles(self, bottles: int, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2020-08-14 00:34:41 +02:00
										 |  |  |         """Version of bottle_count that allows fast abort""" | 
					
						
							|  |  |  |         found: int = 0 | 
					
						
							| 
									
										
										
										
											2020-08-22 19:19:29 +02:00
										 |  |  |         for bottlename in item_name_groups["Bottles"]: | 
					
						
							|  |  |  |             found += self.prog_items[bottlename, player] | 
					
						
							|  |  |  |             if found >= bottles: | 
					
						
							|  |  |  |                 return True | 
					
						
							| 
									
										
										
										
											2020-08-14 00:34:41 +02:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2017-11-11 20:22:44 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def has_hearts(self, player: int, count: int) -> int: | 
					
						
							| 
									
										
										
										
											2018-09-16 12:55:49 -04:00
										 |  |  |         # Warning: This only considers items that are marked as advancement items | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         return self.heart_count(player) >= count | 
					
						
							| 
									
										
										
										
											2018-01-06 13:39:22 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def heart_count(self, player: int) -> int: | 
					
						
							| 
									
										
										
										
											2018-09-16 12:55:49 -04:00
										 |  |  |         # Warning: This only considers items that are marked as advancement items | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |         diff = self.world.difficulty_requirements[player] | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |         return min(self.item_count('Boss Heart Container', player), diff.boss_heart_container_limit) \ | 
					
						
							|  |  |  |                + self.item_count('Sanctuary Heart Container', player) \ | 
					
						
							|  |  |  |                + min(self.item_count('Piece of Heart', player), diff.heart_piece_limit) // 4 \ | 
					
						
							|  |  |  |                + 3  # starting hearts | 
					
						
							| 
									
										
										
										
											2018-01-06 13:39:22 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def can_lift_heavy_rocks(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         return self.has('Titans Mitts', player) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def can_extend_magic(self, player: int, smallmagic: int = 16, | 
					
						
							|  |  |  |                          fullrefill: bool = False):  # This reflects the total magic Link has, not the total extra he has. | 
					
						
							| 
									
										
										
										
											2018-01-06 21:07:46 -06:00
										 |  |  |         basemagic = 8 | 
					
						
							| 
									
										
										
										
											2020-03-14 10:31:28 +11:00
										 |  |  |         if self.has('Magic Upgrade (1/4)', player): | 
					
						
							| 
									
										
										
										
											2018-01-06 21:07:46 -06:00
										 |  |  |             basemagic = 32 | 
					
						
							| 
									
										
										
										
											2020-03-14 10:31:28 +11:00
										 |  |  |         elif self.has('Magic Upgrade (1/2)', player): | 
					
						
							| 
									
										
										
										
											2018-01-06 21:07:46 -06:00
										 |  |  |             basemagic = 16 | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         if self.can_buy_unlimited('Green Potion', player) or self.can_buy_unlimited('Blue Potion', player): | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |             if self.world.difficulty_adjustments[player] == 'hard' and not fullrefill: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                 basemagic = basemagic + int(basemagic * 0.5 * self.bottle_count(player)) | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |             elif self.world.difficulty_adjustments[player] == 'expert' and not fullrefill: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                 basemagic = basemagic + int(basemagic * 0.25 * self.bottle_count(player)) | 
					
						
							| 
									
										
										
										
											2018-09-16 12:55:49 -04:00
										 |  |  |             else: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                 basemagic = basemagic + basemagic * self.bottle_count(player) | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  |         return basemagic >= smallmagic | 
					
						
							| 
									
										
										
										
											2018-01-02 00:39:53 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def can_kill_most_things(self, player: int, enemies=5) -> bool: | 
					
						
							| 
									
										
										
										
											2020-04-20 19:17:10 +02:00
										 |  |  |         return (self.has_melee_weapon(player) | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                 or self.has('Cane of Somaria', player) | 
					
						
							|  |  |  |                 or (self.has('Cane of Byrna', player) and (enemies < 6 or self.can_extend_magic(player))) | 
					
						
							|  |  |  |                 or self.can_shoot_arrows(player) | 
					
						
							|  |  |  |                 or self.has('Fire Rod', player) | 
					
						
							| 
									
										
										
										
											2020-03-15 21:59:06 +11:00
										 |  |  |                 or (self.has('Bombs (10)', player) and enemies < 6)) | 
					
						
							| 
									
										
										
										
											2018-01-02 00:39:53 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def can_shoot_arrows(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2019-12-17 00:16:02 +01:00
										 |  |  |         if self.world.retro[player]: | 
					
						
							| 
									
										
										
										
											2020-04-04 09:16:39 +02:00
										 |  |  |             # TODO: Progressive and Non-Progressive silvers work differently (progressive is not usable until the shop arrow is bought) | 
					
						
							| 
									
										
										
										
											2020-08-23 21:38:21 +02:00
										 |  |  |             return (self.has('Bow', player) or self.has('Silver Bow', player)) and self.can_buy('Single Arrow', player) | 
					
						
							| 
									
										
										
										
											2020-06-30 09:51:11 +02:00
										 |  |  |         return self.has('Bow', player) or self.has('Silver Bow', player) | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def can_get_good_bee(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         cave = self.world.get_region('Good Bee Cave', player) | 
					
						
							| 
									
										
										
										
											2018-09-22 22:51:54 -04:00
										 |  |  |         return ( | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |                 self.has_bottle(player) and | 
					
						
							|  |  |  |                 self.has('Bug Catching Net', player) and | 
					
						
							|  |  |  |                 (self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))) and | 
					
						
							|  |  |  |                 cave.can_reach(self) and | 
					
						
							|  |  |  |                 self.is_not_bunny(cave, player) | 
					
						
							| 
									
										
										
										
											2018-09-22 22:51:54 -04:00
										 |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def has_sword(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2020-08-20 20:13:00 +02:00
										 |  |  |         return self.has('Fighter Sword', player) \ | 
					
						
							|  |  |  |                or self.has('Master Sword', player) \ | 
					
						
							|  |  |  |                or self.has('Tempered Sword', player) \ | 
					
						
							|  |  |  |                or self.has('Golden Sword', player) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def has_beam_sword(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         return self.has('Master Sword', player) or self.has('Tempered Sword', player) or self.has('Golden Sword', player) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-20 19:17:10 +02:00
										 |  |  |     def has_melee_weapon(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         return self.has_sword(player) or self.has('Hammer', player) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def has_Mirror(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         return self.has('Magic Mirror', player) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def has_Boots(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         return self.has('Pegasus Boots', player) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def has_Pearl(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         return self.has('Moon Pearl', player) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def has_fire_source(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         return self.has('Fire Rod', player) or self.has('Lamp', player) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def can_flute(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2019-07-27 09:13:13 -04:00
										 |  |  |         lw = self.world.get_region('Light World', player) | 
					
						
							| 
									
										
										
										
											2020-02-17 10:08:03 +01:00
										 |  |  |         return self.has('Flute', player) and lw.can_reach(self) and self.is_not_bunny(lw, player) | 
					
						
							| 
									
										
										
										
											2019-07-27 09:13:13 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def can_melt_things(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2019-07-27 09:13:13 -04:00
										 |  |  |         return self.has('Fire Rod', player) or (self.has('Bombos', player) and self.has_sword(player)) | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def can_avoid_lasers(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2019-07-27 09:13:13 -04:00
										 |  |  |         return self.has('Mirror Shield', player) or self.has('Cane of Byrna', player) or self.has('Cape', player) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def is_not_bunny(self, region: Region, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2019-07-27 09:13:13 -04:00
										 |  |  |         if self.has_Pearl(player): | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |             return True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-16 16:54:46 +01:00
										 |  |  |         return region.is_light_world if self.world.mode[player] != 'inverted' else region.is_dark_world | 
					
						
							| 
									
										
										
										
											2019-07-27 09:13:13 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def can_reach_light_world(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2019-09-21 21:59:16 -04:00
										 |  |  |         if True in [i.is_light_world for i in self.reachable_regions[player]]: | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def can_reach_dark_world(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2019-09-21 21:59:16 -04:00
										 |  |  |         if True in [i.is_dark_world for i in self.reachable_regions[player]]: | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def has_misery_mire_medallion(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         return self.has(self.world.required_medallions[player][0], player) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def has_turtle_rock_medallion(self, player: int) -> bool: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         return self.has(self.world.required_medallions[player][1], player) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-10 00:38:55 -04:00
										 |  |  |     def can_boots_clip_lw(self, player): | 
					
						
							|  |  |  |         if self.world.mode[player] == 'inverted': | 
					
						
							|  |  |  |             return self.has_Boots(player) and self.has_Pearl(player) | 
					
						
							|  |  |  |         return self.has_Boots(player) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def can_boots_clip_dw(self, player): | 
					
						
							|  |  |  |         if self.world.mode[player] != 'inverted': | 
					
						
							|  |  |  |             return self.has_Boots(player) and self.has_Pearl(player) | 
					
						
							|  |  |  |         return self.has_Boots(player) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-10 23:54:35 -04:00
										 |  |  |     def can_get_glitched_speed_lw(self, player): | 
					
						
							|  |  |  |         rules = [self.has_Boots(player), any([self.has('Hookshot', player), self.has_sword(player)])] | 
					
						
							|  |  |  |         if self.world.mode[player] == 'inverted': | 
					
						
							|  |  |  |             rules.append(self.has_Pearl(player)) | 
					
						
							|  |  |  |         return all(rules) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-12 19:48:36 -04:00
										 |  |  |     def can_superbunny_mirror_with_sword(self, player): | 
					
						
							|  |  |  |         return self.has_Mirror(player) and self.has_sword(player) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-10 23:54:35 -04:00
										 |  |  |     def can_get_glitched_speed_dw(self, player): | 
					
						
							|  |  |  |         rules = [self.has_Boots(player), any([self.has('Hookshot', player), self.has_sword(player)])] | 
					
						
							|  |  |  |         if self.world.mode[player] != 'inverted': | 
					
						
							|  |  |  |             rules.append(self.has_Pearl(player)) | 
					
						
							|  |  |  |         return all(rules) | 
					
						
							| 
									
										
										
										
											2020-02-10 00:38:55 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def collect(self, item: Item, event=False, location=None): | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  |         if location: | 
					
						
							|  |  |  |             self.locations_checked.add(location) | 
					
						
							| 
									
										
										
										
											2017-05-26 09:55:49 +02:00
										 |  |  |         changed = False | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         if item.name.startswith('Progressive '): | 
					
						
							|  |  |  |             if 'Sword' in item.name: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                 if self.has('Golden Sword', item.player): | 
					
						
							| 
									
										
										
										
											2017-05-26 09:55:49 +02:00
										 |  |  |                     pass | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |                 elif self.has('Tempered Sword', item.player) and self.world.difficulty_requirements[ | 
					
						
							|  |  |  |                     item.player].progressive_sword_limit >= 4: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                     self.prog_items['Golden Sword', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2017-05-26 09:55:49 +02:00
										 |  |  |                     changed = True | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |                 elif self.has('Master Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 3: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                     self.prog_items['Tempered Sword', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2017-05-26 09:55:49 +02:00
										 |  |  |                     changed = True | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |                 elif self.has('Fighter Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 2: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                     self.prog_items['Master Sword', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2017-05-26 09:55:49 +02:00
										 |  |  |                     changed = True | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |                 elif self.world.difficulty_requirements[item.player].progressive_sword_limit >= 1: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                     self.prog_items['Fighter Sword', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2017-05-26 09:55:49 +02:00
										 |  |  |                     changed = True | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |             elif 'Glove' in item.name: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                 if self.has('Titans Mitts', item.player): | 
					
						
							| 
									
										
										
										
											2017-05-26 09:55:49 +02:00
										 |  |  |                     pass | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                 elif self.has('Power Glove', item.player): | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                     self.prog_items['Titans Mitts', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2017-05-26 09:55:49 +02:00
										 |  |  |                     changed = True | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |                 else: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                     self.prog_items['Power Glove', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2017-05-26 09:55:49 +02:00
										 |  |  |                     changed = True | 
					
						
							| 
									
										
										
										
											2017-10-14 14:45:59 -04:00
										 |  |  |             elif 'Shield' in item.name: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                 if self.has('Mirror Shield', item.player): | 
					
						
							| 
									
										
										
										
											2017-10-14 14:45:59 -04:00
										 |  |  |                     pass | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |                 elif self.has('Red Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 3: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                     self.prog_items['Mirror Shield', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2017-10-14 14:45:59 -04:00
										 |  |  |                     changed = True | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |                 elif self.has('Blue Shield', item.player)  and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 2: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                     self.prog_items['Red Shield', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2017-10-14 14:45:59 -04:00
										 |  |  |                     changed = True | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |                 elif self.world.difficulty_requirements[item.player].progressive_shield_limit >= 1: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                     self.prog_items['Blue Shield', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2017-10-14 14:45:59 -04:00
										 |  |  |                     changed = True | 
					
						
							| 
									
										
										
										
											2019-08-04 12:32:35 -04:00
										 |  |  |             elif 'Bow' in item.name: | 
					
						
							| 
									
										
										
										
											2020-06-30 09:51:11 +02:00
										 |  |  |                 if self.has('Silver Bow', item.player): | 
					
						
							| 
									
										
										
										
											2019-08-04 12:32:35 -04:00
										 |  |  |                     pass | 
					
						
							|  |  |  |                 elif self.has('Bow', item.player): | 
					
						
							| 
									
										
										
										
											2020-06-30 09:51:11 +02:00
										 |  |  |                     self.prog_items['Silver Bow', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2019-08-04 12:32:35 -04:00
										 |  |  |                     changed = True | 
					
						
							|  |  |  |                 else: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                     self.prog_items['Bow', item.player] += 1 | 
					
						
							| 
									
										
										
										
											2019-08-04 12:32:35 -04:00
										 |  |  |                     changed = True | 
					
						
							| 
									
										
										
										
											2018-01-04 01:06:22 -05:00
										 |  |  |         elif item.name.startswith('Bottle'): | 
					
						
							| 
									
										
										
										
											2019-12-16 17:46:21 +01:00
										 |  |  |             if self.bottle_count(item.player) < self.world.difficulty_requirements[item.player].progressive_bottle_limit: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                 self.prog_items[item.name, item.player] += 1 | 
					
						
							| 
									
										
										
										
											2018-01-04 01:06:22 -05:00
										 |  |  |                 changed = True | 
					
						
							| 
									
										
										
										
											2017-06-17 14:40:37 +02:00
										 |  |  |         elif event or item.advancement: | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |             self.prog_items[item.name, item.player] += 1 | 
					
						
							| 
									
										
										
										
											2017-05-26 09:55:49 +02:00
										 |  |  |             changed = True | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-11 00:18:30 -04:00
										 |  |  |         self.stale[item.player] = True | 
					
						
							| 
									
										
										
										
											2017-05-26 09:55:49 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if changed: | 
					
						
							| 
									
										
										
										
											2017-06-17 14:40:37 +02:00
										 |  |  |             if not event: | 
					
						
							|  |  |  |                 self.sweep_for_events() | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |     def remove(self, item): | 
					
						
							|  |  |  |         if item.advancement: | 
					
						
							|  |  |  |             to_remove = item.name | 
					
						
							|  |  |  |             if to_remove.startswith('Progressive '): | 
					
						
							|  |  |  |                 if 'Sword' in to_remove: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                     if self.has('Golden Sword', item.player): | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |                         to_remove = 'Golden Sword' | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                     elif self.has('Tempered Sword', item.player): | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |                         to_remove = 'Tempered Sword' | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                     elif self.has('Master Sword', item.player): | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |                         to_remove = 'Master Sword' | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                     elif self.has('Fighter Sword', item.player): | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |                         to_remove = 'Fighter Sword' | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         to_remove = None | 
					
						
							|  |  |  |                 elif 'Glove' in item.name: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                     if self.has('Titans Mitts', item.player): | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |                         to_remove = 'Titans Mitts' | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |                     elif self.has('Power Glove', item.player): | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |                         to_remove = 'Power Glove' | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         to_remove = None | 
					
						
							| 
									
										
										
										
											2019-08-04 12:32:35 -04:00
										 |  |  |                 elif 'Shield' in item.name: | 
					
						
							|  |  |  |                     if self.has('Mirror Shield', item.player): | 
					
						
							|  |  |  |                         to_remove = 'Mirror Shield' | 
					
						
							|  |  |  |                     elif self.has('Red Shield', item.player): | 
					
						
							|  |  |  |                         to_remove = 'Red Shield' | 
					
						
							|  |  |  |                     elif self.has('Blue Shield', item.player): | 
					
						
							|  |  |  |                         to_remove = 'Blue Shield' | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         to_remove = 'None' | 
					
						
							|  |  |  |                 elif 'Bow' in item.name: | 
					
						
							| 
									
										
										
										
											2020-06-30 09:51:11 +02:00
										 |  |  |                     if self.has('Silver Bow', item.player): | 
					
						
							|  |  |  |                         to_remove = 'Silver Bow' | 
					
						
							| 
									
										
										
										
											2019-08-04 12:32:35 -04:00
										 |  |  |                     elif self.has('Bow', item.player): | 
					
						
							|  |  |  |                         to_remove = 'Bow' | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         to_remove = None | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             if to_remove is not None: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-07 23:35:55 +01:00
										 |  |  |                 self.prog_items[to_remove, item.player] -= 1 | 
					
						
							|  |  |  |                 if self.prog_items[to_remove, item.player] < 1: | 
					
						
							|  |  |  |                     del (self.prog_items[to_remove, item.player]) | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |                 # invalidate caches, nothing can be trusted anymore now | 
					
						
							| 
									
										
										
										
											2019-07-11 00:18:30 -04:00
										 |  |  |                 self.reachable_regions[item.player] = set() | 
					
						
							| 
									
										
										
										
											2020-05-10 19:27:13 +10:00
										 |  |  |                 self.blocked_connections[item.player] = set() | 
					
						
							| 
									
										
										
										
											2019-07-11 00:18:30 -04:00
										 |  |  |                 self.stale[item.player] = True | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-18 21:51:43 -05:00
										 |  |  | @unique | 
					
						
							|  |  |  | class RegionType(Enum): | 
					
						
							|  |  |  |     LightWorld = 1 | 
					
						
							|  |  |  |     DarkWorld = 2 | 
					
						
							|  |  |  |     Cave = 3 # Also includes Houses | 
					
						
							|  |  |  |     Dungeon = 4 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def is_indoors(self): | 
					
						
							|  |  |  |         """Shorthand for checking if Cave or Dungeon""" | 
					
						
							|  |  |  |         return self in (RegionType.Cave, RegionType.Dungeon) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | class Region(object): | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def __init__(self, name: str, type, hint, player: int): | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         self.name = name | 
					
						
							| 
									
										
										
										
											2018-01-18 21:51:43 -05:00
										 |  |  |         self.type = type | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         self.entrances = [] | 
					
						
							|  |  |  |         self.exits = [] | 
					
						
							|  |  |  |         self.locations = [] | 
					
						
							| 
									
										
										
										
											2017-10-15 12:16:07 -04:00
										 |  |  |         self.dungeon = None | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  |         self.shop = None | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  |         self.world = None | 
					
						
							| 
									
										
										
										
											2020-03-07 23:20:11 +01:00
										 |  |  |         self.is_light_world = False  # will be set after making connections. | 
					
						
							| 
									
										
										
										
											2018-01-27 17:17:03 -05:00
										 |  |  |         self.is_dark_world = False | 
					
						
							| 
									
										
										
										
											2017-05-20 14:03:15 +02:00
										 |  |  |         self.spot_type = 'Region' | 
					
						
							| 
									
										
										
										
											2019-01-20 01:01:02 -06:00
										 |  |  |         self.hint_text = hint | 
					
						
							| 
									
										
										
										
											2017-05-26 09:55:49 +02:00
										 |  |  |         self.recursion_count = 0 | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         self.player = player | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def can_reach(self, state): | 
					
						
							| 
									
										
										
										
											2019-07-11 00:18:30 -04:00
										 |  |  |         if state.stale[self.player]: | 
					
						
							|  |  |  |             state.update_reachable_regions(self.player) | 
					
						
							|  |  |  |         return self in state.reachable_regions[self.player] | 
					
						
							| 
									
										
										
										
											2019-07-08 22:48:16 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-07 23:20:11 +01:00
										 |  |  |     def can_reach_private(self, state: CollectionState): | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         for entrance in self.entrances: | 
					
						
							| 
									
										
										
										
											2019-07-11 00:18:30 -04:00
										 |  |  |             if entrance.can_reach(state): | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  |                 if not self in state.path: | 
					
						
							|  |  |  |                     state.path[self] = (self.name, state.path.get(entrance, None)) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |                 return True | 
					
						
							|  |  |  |         return False | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-07 23:20:11 +01:00
										 |  |  |     def can_fill(self, item: Item): | 
					
						
							| 
									
										
										
										
											2019-12-16 21:46:47 +01:00
										 |  |  |         inside_dungeon_item = ((item.smallkey and not self.world.keyshuffle[item.player]) | 
					
						
							|  |  |  |                                or (item.bigkey and not self.world.bigkeyshuffle[item.player]) | 
					
						
							|  |  |  |                                or (item.map and not self.world.mapshuffle[item.player]) | 
					
						
							|  |  |  |                                or (item.compass and not self.world.compassshuffle[item.player])) | 
					
						
							| 
									
										
										
										
											2020-06-24 16:22:49 +02:00
										 |  |  |         sewer_hack = self.world.mode[item.player] == 'standard' and item.name == 'Small Key (Hyrule Castle)' | 
					
						
							| 
									
										
										
										
											2019-12-13 22:37:52 +01:00
										 |  |  |         if sewer_hack or inside_dungeon_item: | 
					
						
							| 
									
										
										
										
											2019-07-09 22:18:24 -04:00
										 |  |  |             return self.dungeon and self.dungeon.is_dungeon_item(item) and item.player == self.player | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-15 12:16:07 -04:00
										 |  |  |         return True | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 21:31:15 +02:00
										 |  |  |     def __repr__(self): | 
					
						
							|  |  |  |         return self.__str__() | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 21:31:15 +02:00
										 |  |  |     def __str__(self): | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |         return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})' | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Entrance(object): | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def __init__(self, player: int, name: str = '', parent=None): | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         self.name = name | 
					
						
							|  |  |  |         self.parent_region = parent | 
					
						
							|  |  |  |         self.connected_region = None | 
					
						
							| 
									
										
										
										
											2017-05-20 14:03:15 +02:00
										 |  |  |         self.target = None | 
					
						
							| 
									
										
										
										
											2017-06-03 15:33:11 +02:00
										 |  |  |         self.addresses = None | 
					
						
							| 
									
										
										
										
											2017-05-20 14:03:15 +02:00
										 |  |  |         self.spot_type = 'Entrance' | 
					
						
							| 
									
										
										
										
											2017-05-26 09:55:49 +02:00
										 |  |  |         self.recursion_count = 0 | 
					
						
							| 
									
										
										
										
											2017-06-17 13:16:13 +02:00
										 |  |  |         self.vanilla = None | 
					
						
							| 
									
										
										
										
											2017-12-17 00:25:46 -05:00
										 |  |  |         self.access_rule = lambda state: True | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         self.player = player | 
					
						
							| 
									
										
										
										
											2020-05-10 19:27:13 +10:00
										 |  |  |         self.hide_path = False | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def can_reach(self, state): | 
					
						
							| 
									
										
										
										
											2019-07-11 00:18:30 -04:00
										 |  |  |         if self.parent_region.can_reach(state) and self.access_rule(state): | 
					
						
							| 
									
										
										
										
											2020-05-10 19:27:13 +10:00
										 |  |  |             if not self.hide_path and not self in state.path: | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  |                 state.path[self] = (self.name, state.path.get(self.parent_region, (self.parent_region.name, None))) | 
					
						
							| 
									
										
										
										
											2017-05-20 14:03:15 +02:00
										 |  |  |             return True | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-20 14:03:15 +02:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-17 13:16:13 +02:00
										 |  |  |     def connect(self, region, addresses=None, target=None, vanilla=None): | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         self.connected_region = region | 
					
						
							| 
									
										
										
										
											2017-05-20 14:03:15 +02:00
										 |  |  |         self.target = target | 
					
						
							| 
									
										
										
										
											2017-06-03 15:33:11 +02:00
										 |  |  |         self.addresses = addresses | 
					
						
							| 
									
										
										
										
											2017-06-17 13:16:13 +02:00
										 |  |  |         self.vanilla = vanilla | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         region.entrances.append(self) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 21:31:15 +02:00
										 |  |  |     def __repr__(self): | 
					
						
							|  |  |  |         return self.__str__() | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 21:31:15 +02:00
										 |  |  |     def __str__(self): | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |         world = self.parent_region.world if self.parent_region else None | 
					
						
							|  |  |  |         return world.get_name_string_for_object(self) if world else f'{self.name} (Player {self.player})' | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-15 12:16:07 -04:00
										 |  |  | class Dungeon(object): | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 03:30:59 +02:00
										 |  |  |     def __init__(self, name: str, regions, big_key, small_keys, dungeon_items, player: int): | 
					
						
							| 
									
										
										
										
											2017-10-15 12:16:07 -04:00
										 |  |  |         self.name = name | 
					
						
							|  |  |  |         self.regions = regions | 
					
						
							|  |  |  |         self.big_key = big_key | 
					
						
							|  |  |  |         self.small_keys = small_keys | 
					
						
							|  |  |  |         self.dungeon_items = dungeon_items | 
					
						
							| 
									
										
										
										
											2018-09-26 13:12:20 -04:00
										 |  |  |         self.bosses = dict() | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         self.player = player | 
					
						
							| 
									
										
										
										
											2019-07-13 18:11:43 -04:00
										 |  |  |         self.world = None | 
					
						
							| 
									
										
										
										
											2018-09-26 13:12:20 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def boss(self): | 
					
						
							|  |  |  |         return self.bosses.get(None, None) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @boss.setter | 
					
						
							|  |  |  |     def boss(self, value): | 
					
						
							|  |  |  |         self.bosses[None] = value | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-15 12:16:07 -04:00
										 |  |  |     @property | 
					
						
							|  |  |  |     def keys(self): | 
					
						
							|  |  |  |         return self.small_keys + ([self.big_key] if self.big_key else []) | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-15 12:16:07 -04:00
										 |  |  |     @property | 
					
						
							|  |  |  |     def all_items(self): | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  |         return self.dungeon_items + self.keys | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 03:30:59 +02:00
										 |  |  |     def is_dungeon_item(self, item: Item) -> bool: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         return item.player == self.player and item.name in [dungeon_item.name for dungeon_item in self.all_items] | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 03:30:59 +02:00
										 |  |  |     def __eq__(self, other: Item) -> bool: | 
					
						
							|  |  |  |         return self.name == other.name and self.player == other.player | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 21:31:15 +02:00
										 |  |  |     def __repr__(self): | 
					
						
							|  |  |  |         return self.__str__() | 
					
						
							| 
									
										
										
										
											2017-10-15 12:16:07 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 21:31:15 +02:00
										 |  |  |     def __str__(self): | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |         return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})' | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-26 13:12:20 -04:00
										 |  |  | class Boss(object): | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def __init__(self, name, enemizer_name, defeat_rule, player: int): | 
					
						
							| 
									
										
										
										
											2018-09-26 13:12:20 -04:00
										 |  |  |         self.name = name | 
					
						
							|  |  |  |         self.enemizer_name = enemizer_name | 
					
						
							|  |  |  |         self.defeat_rule = defeat_rule | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         self.player = player | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def can_defeat(self, state) -> bool: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         return self.defeat_rule(state, self.player) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-26 13:12:20 -04:00
										 |  |  | class Location(object): | 
					
						
							| 
									
										
										
										
											2020-08-16 16:49:48 +02:00
										 |  |  |     def __init__(self, player: int, name: str = '', address=None, crystal: bool = False, | 
					
						
							|  |  |  |                  hint_text: Optional[str] = None, parent=None, | 
					
						
							| 
									
										
										
										
											2020-03-06 23:08:46 +01:00
										 |  |  |                  player_address=None): | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         self.name = name | 
					
						
							|  |  |  |         self.parent_region = parent | 
					
						
							|  |  |  |         self.item = None | 
					
						
							| 
									
										
										
										
											2017-05-25 17:47:15 +02:00
										 |  |  |         self.crystal = crystal | 
					
						
							|  |  |  |         self.address = address | 
					
						
							| 
									
										
										
										
											2019-12-09 19:27:56 +01:00
										 |  |  |         self.player_address = player_address | 
					
						
							| 
									
										
										
										
											2017-05-20 14:03:15 +02:00
										 |  |  |         self.spot_type = 'Location' | 
					
						
							| 
									
										
										
										
											2020-03-06 23:08:46 +01:00
										 |  |  |         self.hint_text: str = hint_text if hint_text else name | 
					
						
							| 
									
										
										
										
											2017-05-26 09:55:49 +02:00
										 |  |  |         self.recursion_count = 0 | 
					
						
							| 
									
										
										
										
											2017-06-17 14:40:37 +02:00
										 |  |  |         self.event = False | 
					
						
							| 
									
										
										
										
											2019-12-10 19:21:05 +01:00
										 |  |  |         self.locked = False | 
					
						
							| 
									
										
										
										
											2018-01-02 00:39:53 -05:00
										 |  |  |         self.always_allow = lambda item, state: False | 
					
						
							| 
									
										
										
										
											2017-12-17 00:25:46 -05:00
										 |  |  |         self.access_rule = lambda state: True | 
					
						
							| 
									
										
										
										
											2018-01-02 00:39:53 -05:00
										 |  |  |         self.item_rule = lambda item: True | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         self.player = player | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-09 16:16:31 +02:00
										 |  |  |     def can_fill(self, state: CollectionState, item: Item, check_access=True) -> bool: | 
					
						
							| 
									
										
										
										
											2020-07-16 18:59:23 +10:00
										 |  |  |         return self.always_allow(state, item) or (self.parent_region.can_fill(item) and self.item_rule(item) and (not check_access or self.can_reach(state))) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-09 16:16:31 +02:00
										 |  |  |     def can_reach(self, state: CollectionState) -> bool: | 
					
						
							| 
									
										
										
										
											2020-08-21 18:35:48 +02:00
										 |  |  |         # self.access_rule computes faster on average, so placing it first for faster abort | 
					
						
							|  |  |  |         if self.access_rule(state) and self.parent_region.can_reach(state): | 
					
						
							| 
									
										
										
										
											2017-05-20 14:03:15 +02:00
										 |  |  |             return True | 
					
						
							|  |  |  |         return False | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 21:31:15 +02:00
										 |  |  |     def __repr__(self): | 
					
						
							|  |  |  |         return self.__str__() | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 21:31:15 +02:00
										 |  |  |     def __str__(self): | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |         world = self.parent_region.world if self.parent_region and self.parent_region.world else None | 
					
						
							|  |  |  |         return world.get_name_string_for_object(self) if world else f'{self.name} (Player {self.player})' | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-22 19:19:29 +02:00
										 |  |  |     def __hash__(self): | 
					
						
							|  |  |  |         return hash((self.name, self.player)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  | class Item(object): | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |     def __init__(self, name='', advancement=False, priority=False, type=None, code=None, pedestal_hint=None, pedestal_credit=None, sickkid_credit=None, zora_credit=None, witch_credit=None, fluteboy_credit=None, hint_text=None, player=None): | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |         self.name = name | 
					
						
							|  |  |  |         self.advancement = advancement | 
					
						
							| 
									
										
										
										
											2017-06-04 14:43:13 +02:00
										 |  |  |         self.priority = priority | 
					
						
							| 
									
										
										
										
											2017-10-15 12:16:07 -04:00
										 |  |  |         self.type = type | 
					
						
							| 
									
										
										
										
											2017-10-28 23:42:35 -04:00
										 |  |  |         self.pedestal_hint_text = pedestal_hint | 
					
						
							|  |  |  |         self.pedestal_credit_text = pedestal_credit | 
					
						
							| 
									
										
										
										
											2017-05-25 15:58:35 +02:00
										 |  |  |         self.sickkid_credit_text = sickkid_credit | 
					
						
							|  |  |  |         self.zora_credit_text = zora_credit | 
					
						
							|  |  |  |         self.magicshop_credit_text = witch_credit | 
					
						
							|  |  |  |         self.fluteboy_credit_text = fluteboy_credit | 
					
						
							| 
									
										
										
										
											2019-04-08 20:11:33 -05:00
										 |  |  |         self.hint_text = hint_text | 
					
						
							| 
									
										
										
										
											2017-05-20 14:03:15 +02:00
										 |  |  |         self.code = code | 
					
						
							| 
									
										
										
										
											2017-05-25 15:58:35 +02:00
										 |  |  |         self.location = None | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |         self.world = None | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         self.player = player | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def crystal(self) -> bool: | 
					
						
							| 
									
										
										
										
											2017-10-15 12:16:07 -04:00
										 |  |  |         return self.type == 'Crystal' | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def smallkey(self) -> bool: | 
					
						
							| 
									
										
										
										
											2019-12-13 22:37:52 +01:00
										 |  |  |         return self.type == 'SmallKey' | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def bigkey(self) -> bool: | 
					
						
							| 
									
										
										
										
											2019-12-13 22:37:52 +01:00
										 |  |  |         return self.type == 'BigKey' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  |     @property | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def map(self) -> bool: | 
					
						
							| 
									
										
										
										
											2017-10-15 12:16:07 -04:00
										 |  |  |         return self.type == 'Map' | 
					
						
							| 
									
										
										
										
											2017-10-28 18:34:37 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							| 
									
										
										
										
											2020-03-03 00:12:14 +01:00
										 |  |  |     def compass(self) -> bool: | 
					
						
							| 
									
										
										
										
											2017-10-15 12:16:07 -04:00
										 |  |  |         return self.type == 'Compass' | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 21:31:15 +02:00
										 |  |  |     def __repr__(self): | 
					
						
							|  |  |  |         return self.__str__() | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 21:31:15 +02:00
										 |  |  |     def __str__(self): | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |         return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})' | 
					
						
							| 
									
										
										
										
											2017-05-20 14:03:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # have 6 address that need to be filled | 
					
						
							|  |  |  | class Crystal(Item): | 
					
						
							|  |  |  |     pass | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  | @unique | 
					
						
							|  |  |  | class ShopType(Enum): | 
					
						
							|  |  |  |     Shop = 0 | 
					
						
							|  |  |  |     TakeAny = 1 | 
					
						
							| 
									
										
										
										
											2018-09-22 22:51:54 -04:00
										 |  |  |     UpgradeShop = 2 | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-25 14:31:20 +02:00
										 |  |  | class Shop(): | 
					
						
							|  |  |  |     slots = 3  # slot count is not dynamic in asm, however inventory can have None as empty slots | 
					
						
							|  |  |  |     blacklist = set()  # items that don't work, todo: actually check against this | 
					
						
							|  |  |  |     type = ShopType.Shop | 
					
						
							| 
									
										
										
										
											2020-08-23 21:38:21 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-25 14:31:20 +02:00
										 |  |  |     def __init__(self, region: Region, room_id: int, shopkeeper_config: int, custom: bool, locked: bool): | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  |         self.region = region | 
					
						
							|  |  |  |         self.room_id = room_id | 
					
						
							| 
									
										
										
										
											2020-08-25 14:31:20 +02:00
										 |  |  |         self.inventory: List[Union[None, dict]] = [None] * self.slots | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  |         self.shopkeeper_config = shopkeeper_config | 
					
						
							| 
									
										
										
										
											2020-01-10 11:41:22 +01:00
										 |  |  |         self.custom = custom | 
					
						
							|  |  |  |         self.locked = locked | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							| 
									
										
										
										
											2020-08-25 14:31:20 +02:00
										 |  |  |     def item_count(self) -> int: | 
					
						
							|  |  |  |         for x in range(self.slots - 1, -1, -1):  # last x is 0 | 
					
						
							|  |  |  |             if self.inventory[x]: | 
					
						
							|  |  |  |                 return x + 1 | 
					
						
							|  |  |  |         return 0 | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-25 14:31:20 +02:00
										 |  |  |     def get_bytes(self) -> List[int]: | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  |         # [id][roomID-low][roomID-high][doorID][zero][shop_config][shopkeeper_config][sram_index] | 
					
						
							|  |  |  |         entrances = self.region.entrances | 
					
						
							|  |  |  |         config = self.item_count | 
					
						
							| 
									
										
										
										
											2020-01-03 08:27:22 +11:00
										 |  |  |         if len(entrances) == 1 and entrances[0].name in door_addresses: | 
					
						
							| 
									
										
										
										
											2020-08-25 14:31:20 +02:00
										 |  |  |             door_id = door_addresses[entrances[0].name][0] + 1 | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  |         else: | 
					
						
							|  |  |  |             door_id = 0 | 
					
						
							| 
									
										
										
										
											2020-08-25 14:31:20 +02:00
										 |  |  |             config |= 0x40  # ignore door id | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  |         if self.type == ShopType.TakeAny: | 
					
						
							|  |  |  |             config |= 0x80 | 
					
						
							| 
									
										
										
										
											2020-08-25 14:31:20 +02:00
										 |  |  |         elif self.type == ShopType.UpgradeShop: | 
					
						
							|  |  |  |             config |= 0x10  # Alt. VRAM | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  |         return [0x00]+int16_as_bytes(self.room_id)+[door_id, 0x00, config, self.shopkeeper_config, 0x00] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-23 21:38:21 +02:00
										 |  |  |     def has_unlimited(self, item: str) -> bool: | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  |         for inv in self.inventory: | 
					
						
							|  |  |  |             if inv is None: | 
					
						
							|  |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2020-08-23 21:38:21 +02:00
										 |  |  |             if inv['item'] == item: | 
					
						
							|  |  |  |                 return True | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  |             if inv['max'] != 0 and inv['replacement'] is not None and inv['replacement'] == item: | 
					
						
							|  |  |  |                 return True | 
					
						
							| 
									
										
										
										
											2020-08-23 21:38:21 +02:00
										 |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def has(self, item: str) -> bool: | 
					
						
							|  |  |  |         for inv in self.inventory: | 
					
						
							|  |  |  |             if inv is None: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             if inv['item'] == item: | 
					
						
							|  |  |  |                 return True | 
					
						
							|  |  |  |             if inv['max'] != 0 and inv['replacement'] == item: | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  |                 return True | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def clear_inventory(self): | 
					
						
							| 
									
										
										
										
											2020-08-25 14:31:20 +02:00
										 |  |  |         self.inventory = [None] * self.slots | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-23 21:38:21 +02:00
										 |  |  |     def add_inventory(self, slot: int, item: str, price: int, max: int = 0, | 
					
						
							|  |  |  |                       replacement: Optional[str] = None, replacement_price: int = 0, create_location: bool = False): | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  |         self.inventory[slot] = { | 
					
						
							|  |  |  |             'item': item, | 
					
						
							|  |  |  |             'price': price, | 
					
						
							|  |  |  |             'max': max, | 
					
						
							|  |  |  |             'replacement': replacement, | 
					
						
							|  |  |  |             'replacement_price': replacement_price, | 
					
						
							|  |  |  |             'create_location': create_location | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-23 21:38:21 +02:00
										 |  |  |     def push_inventory(self, slot: int, item: str, price: int, max: int = 1): | 
					
						
							|  |  |  |         if not self.inventory[slot]: | 
					
						
							|  |  |  |             raise ValueError("Inventory can't be pushed back if it doesn't exist") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.inventory[slot] = { | 
					
						
							|  |  |  |             'item': item, | 
					
						
							|  |  |  |             'price': price, | 
					
						
							|  |  |  |             'max': max, | 
					
						
							|  |  |  |             'replacement': self.inventory[slot]["item"], | 
					
						
							|  |  |  |             'replacement_price': self.inventory[slot]["price"], | 
					
						
							|  |  |  |             'create_location': self.inventory[slot]["create_location"] | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-25 14:31:20 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | class TakeAny(Shop): | 
					
						
							|  |  |  |     type = ShopType.TakeAny | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class UpgradeShop(Shop): | 
					
						
							|  |  |  |     type = ShopType.UpgradeShop | 
					
						
							|  |  |  |     # Potions break due to VRAM flags set in UpgradeShop. | 
					
						
							|  |  |  |     # Didn't check for more things breaking as not much else can be shuffled here currently | 
					
						
							|  |  |  |     blacklist = item_name_groups["Potions"] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  | class Spoiler(object): | 
					
						
							| 
									
										
										
										
											2020-02-16 15:32:40 +01:00
										 |  |  |     world: World | 
					
						
							| 
									
										
										
										
											2020-08-25 14:31:20 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  |     def __init__(self, world): | 
					
						
							|  |  |  |         self.world = world | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |         self.hashes = {} | 
					
						
							| 
									
										
										
										
											2018-03-24 01:50:54 -04:00
										 |  |  |         self.entrances = OrderedDict() | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  |         self.medallions = {} | 
					
						
							|  |  |  |         self.playthrough = {} | 
					
						
							| 
									
										
										
										
											2019-12-21 13:33:07 +01:00
										 |  |  |         self.unreachables = [] | 
					
						
							| 
									
										
										
										
											2020-01-09 08:31:49 +01:00
										 |  |  |         self.startinventory = [] | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  |         self.locations = {} | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  |         self.paths = {} | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  |         self.metadata = {} | 
					
						
							| 
									
										
										
										
											2018-03-24 01:43:10 -04:00
										 |  |  |         self.shops = [] | 
					
						
							| 
									
										
										
										
											2018-09-26 13:12:20 -04:00
										 |  |  |         self.bosses = OrderedDict() | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |     def set_entrance(self, entrance, exit, direction, player): | 
					
						
							| 
									
										
										
										
											2019-07-13 18:11:43 -04:00
										 |  |  |         if self.world.players == 1: | 
					
						
							|  |  |  |             self.entrances[(entrance, direction, player)] = OrderedDict([('entrance', entrance), ('exit', exit), ('direction', direction)]) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.entrances[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)]) | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def parse_data(self): | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         self.medallions = OrderedDict() | 
					
						
							| 
									
										
										
										
											2019-07-13 18:11:43 -04:00
										 |  |  |         if self.world.players == 1: | 
					
						
							|  |  |  |             self.medallions['Misery Mire'] = self.world.required_medallions[1][0] | 
					
						
							|  |  |  |             self.medallions['Turtle Rock'] = self.world.required_medallions[1][1] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             for player in range(1, self.world.players + 1): | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |                 self.medallions[f'Misery Mire ({self.world.get_player_names(player)})'] = self.world.required_medallions[player][0] | 
					
						
							|  |  |  |                 self.medallions[f'Turtle Rock ({self.world.get_player_names(player)})'] = self.world.required_medallions[player][1] | 
					
						
							| 
									
										
										
										
											2018-03-23 11:03:38 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-10 07:02:44 +01:00
										 |  |  |         self.startinventory = list(map(str, self.world.precollected_items)) | 
					
						
							| 
									
										
										
										
											2018-03-23 11:03:38 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         self.locations = OrderedDict() | 
					
						
							|  |  |  |         listed_locations = set() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         lw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld] | 
					
						
							|  |  |  |         self.locations['Light World'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in lw_locations]) | 
					
						
							|  |  |  |         listed_locations.update(lw_locations) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         dw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld] | 
					
						
							|  |  |  |         self.locations['Dark World'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in dw_locations]) | 
					
						
							|  |  |  |         listed_locations.update(dw_locations) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         cave_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.Cave] | 
					
						
							|  |  |  |         self.locations['Caves'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in cave_locations]) | 
					
						
							|  |  |  |         listed_locations.update(cave_locations) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for dungeon in self.world.dungeons: | 
					
						
							|  |  |  |             dungeon_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon] | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |             self.locations[str(dungeon)] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in dungeon_locations]) | 
					
						
							| 
									
										
										
										
											2018-03-23 11:03:38 -04:00
										 |  |  |             listed_locations.update(dungeon_locations) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations] | 
					
						
							|  |  |  |         if other_locations: | 
					
						
							|  |  |  |             self.locations['Other Locations'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in other_locations]) | 
					
						
							|  |  |  |             listed_locations.update(other_locations) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         self.shops = [] | 
					
						
							| 
									
										
										
										
											2018-03-24 01:43:10 -04:00
										 |  |  |         for shop in self.world.shops: | 
					
						
							| 
									
										
										
										
											2020-01-10 11:41:22 +01:00
										 |  |  |             if not shop.custom: | 
					
						
							| 
									
										
										
										
											2018-03-24 01:43:10 -04:00
										 |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |             shopdata = {'location': str(shop.region), | 
					
						
							| 
									
										
										
										
											2018-03-24 01:43:10 -04:00
										 |  |  |                         'type': 'Take Any' if shop.type == ShopType.TakeAny else 'Shop' | 
					
						
							|  |  |  |                        } | 
					
						
							|  |  |  |             for index, item in enumerate(shop.inventory): | 
					
						
							|  |  |  |                 if item is None: | 
					
						
							|  |  |  |                     continue | 
					
						
							| 
									
										
										
										
											2018-03-26 21:39:48 -04:00
										 |  |  |                 shopdata['item_{}'.format(index)] = "{} — {}".format(item['item'], item['price']) if item['price'] else item['item'] | 
					
						
							| 
									
										
										
										
											2018-03-24 01:43:10 -04:00
										 |  |  |             self.shops.append(shopdata) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         for player in range(1, self.world.players + 1): | 
					
						
							|  |  |  |             self.bosses[str(player)] = OrderedDict() | 
					
						
							|  |  |  |             self.bosses[str(player)]["Eastern Palace"] = self.world.get_dungeon("Eastern Palace", player).boss.name | 
					
						
							|  |  |  |             self.bosses[str(player)]["Desert Palace"] = self.world.get_dungeon("Desert Palace", player).boss.name | 
					
						
							|  |  |  |             self.bosses[str(player)]["Tower Of Hera"] = self.world.get_dungeon("Tower of Hera", player).boss.name | 
					
						
							|  |  |  |             self.bosses[str(player)]["Hyrule Castle"] = "Agahnim" | 
					
						
							|  |  |  |             self.bosses[str(player)]["Palace Of Darkness"] = self.world.get_dungeon("Palace of Darkness", player).boss.name | 
					
						
							|  |  |  |             self.bosses[str(player)]["Swamp Palace"] = self.world.get_dungeon("Swamp Palace", player).boss.name | 
					
						
							|  |  |  |             self.bosses[str(player)]["Skull Woods"] = self.world.get_dungeon("Skull Woods", player).boss.name | 
					
						
							|  |  |  |             self.bosses[str(player)]["Thieves Town"] = self.world.get_dungeon("Thieves Town", player).boss.name | 
					
						
							|  |  |  |             self.bosses[str(player)]["Ice Palace"] = self.world.get_dungeon("Ice Palace", player).boss.name | 
					
						
							|  |  |  |             self.bosses[str(player)]["Misery Mire"] = self.world.get_dungeon("Misery Mire", player).boss.name | 
					
						
							|  |  |  |             self.bosses[str(player)]["Turtle Rock"] = self.world.get_dungeon("Turtle Rock", player).boss.name | 
					
						
							| 
									
										
										
										
											2019-12-16 16:54:46 +01:00
										 |  |  |             if self.world.mode[player] != 'inverted': | 
					
						
							| 
									
										
										
										
											2019-07-27 09:13:13 -04:00
										 |  |  |                 self.bosses[str(player)]["Ganons Tower Basement"] = self.world.get_dungeon('Ganons Tower', player).bosses['bottom'].name | 
					
						
							|  |  |  |                 self.bosses[str(player)]["Ganons Tower Middle"] = self.world.get_dungeon('Ganons Tower', player).bosses['middle'].name | 
					
						
							|  |  |  |                 self.bosses[str(player)]["Ganons Tower Top"] = self.world.get_dungeon('Ganons Tower', player).bosses['top'].name | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 self.bosses[str(player)]["Ganons Tower Basement"] = self.world.get_dungeon('Inverted Ganons Tower', player).bosses['bottom'].name | 
					
						
							|  |  |  |                 self.bosses[str(player)]["Ganons Tower Middle"] = self.world.get_dungeon('Inverted Ganons Tower', player).bosses['middle'].name | 
					
						
							|  |  |  |                 self.bosses[str(player)]["Ganons Tower Top"] = self.world.get_dungeon('Inverted Ganons Tower', player).bosses['top'].name | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |             self.bosses[str(player)]["Ganons Tower"] = "Agahnim 2" | 
					
						
							|  |  |  |             self.bosses[str(player)]["Ganon"] = "Ganon" | 
					
						
							| 
									
										
										
										
											2018-09-26 13:12:20 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-13 18:11:43 -04:00
										 |  |  |         if self.world.players == 1: | 
					
						
							|  |  |  |             self.bosses = self.bosses["1"] | 
					
						
							| 
									
										
										
										
											2018-03-24 01:43:10 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-20 14:50:49 +02:00
										 |  |  |         from Utils import __version__ as ERVersion | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  |         self.metadata = {'version': ERVersion, | 
					
						
							|  |  |  |                          'logic': self.world.logic, | 
					
						
							|  |  |  |                          'mode': self.world.mode, | 
					
						
							| 
									
										
										
										
											2019-12-17 00:16:02 +01:00
										 |  |  |                          'retro': self.world.retro, | 
					
						
							| 
									
										
										
										
											2019-08-24 15:35:58 -04:00
										 |  |  |                          'weapons': self.world.swords, | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  |                          'goal': self.world.goal, | 
					
						
							|  |  |  |                          'shuffle': self.world.shuffle, | 
					
						
							| 
									
										
										
										
											2019-08-24 15:35:58 -04:00
										 |  |  |                          'item_pool': self.world.difficulty, | 
					
						
							|  |  |  |                          'item_functionality': self.world.difficulty_adjustments, | 
					
						
							| 
									
										
										
										
											2019-12-16 19:09:15 +01:00
										 |  |  |                          'gt_crystals': self.world.crystals_needed_for_gt, | 
					
						
							|  |  |  |                          'ganon_crystals': self.world.crystals_needed_for_ganon, | 
					
						
							|  |  |  |                          'open_pyramid': self.world.open_pyramid, | 
					
						
							| 
									
										
										
										
											2019-08-04 17:40:13 -04:00
										 |  |  |                          'accessibility': self.world.accessibility, | 
					
						
							| 
									
										
										
										
											2019-08-24 15:35:58 -04:00
										 |  |  |                          'hints': self.world.hints, | 
					
						
							| 
									
										
										
										
											2019-12-13 22:37:52 +01:00
										 |  |  |                          'mapshuffle': self.world.mapshuffle, | 
					
						
							|  |  |  |                          'compassshuffle': self.world.compassshuffle, | 
					
						
							|  |  |  |                          'keyshuffle': self.world.keyshuffle, | 
					
						
							|  |  |  |                          'bigkeyshuffle': self.world.bigkeyshuffle, | 
					
						
							| 
									
										
										
										
											2019-12-17 15:55:53 +01:00
										 |  |  |                          'boss_shuffle': self.world.boss_shuffle, | 
					
						
							|  |  |  |                          'enemy_shuffle': self.world.enemy_shuffle, | 
					
						
							|  |  |  |                          'enemy_health': self.world.enemy_health, | 
					
						
							|  |  |  |                          'enemy_damage': self.world.enemy_damage, | 
					
						
							| 
									
										
										
										
											2020-08-19 23:24:17 +02:00
										 |  |  |                          'killable_thieves': self.world.killable_thieves, | 
					
						
							|  |  |  |                          'tile_shuffle': self.world.tile_shuffle, | 
					
						
							|  |  |  |                          'bush_shuffle': self.world.bush_shuffle, | 
					
						
							| 
									
										
										
										
											2020-01-18 12:51:10 -05:00
										 |  |  |                          'beemizer': self.world.beemizer, | 
					
						
							| 
									
										
										
										
											2020-01-22 06:28:58 +01:00
										 |  |  |                          'progressive': self.world.progressive, | 
					
						
							| 
									
										
										
										
											2020-01-18 12:51:10 -05:00
										 |  |  |                          'shufflepots': self.world.shufflepots, | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |                          'players': self.world.players, | 
					
						
							| 
									
										
										
										
											2020-05-20 22:30:21 +02:00
										 |  |  |                          'teams': self.world.teams, | 
					
						
							| 
									
										
										
										
											2020-06-07 15:22:24 +02:00
										 |  |  |                          'progression_balancing': self.world.progression_balancing, | 
					
						
							| 
									
										
										
										
											2020-06-17 01:02:54 -07:00
										 |  |  |                          'triforce_pieces_available': self.world.triforce_pieces_available, | 
					
						
							| 
									
										
										
										
											2020-06-07 15:22:24 +02:00
										 |  |  |                          'triforce_pieces_required': self.world.triforce_pieces_required, | 
					
						
							| 
									
										
										
										
											2020-08-23 21:38:21 +02:00
										 |  |  |                          'shop_shuffle': self.world.shop_shuffle | 
					
						
							| 
									
										
										
										
											2019-08-24 15:35:58 -04:00
										 |  |  |                          } | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def to_json(self): | 
					
						
							|  |  |  |         self.parse_data() | 
					
						
							|  |  |  |         out = OrderedDict() | 
					
						
							| 
									
										
										
										
											2018-03-26 21:39:48 -04:00
										 |  |  |         out['Entrances'] = list(self.entrances.values()) | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  |         out.update(self.locations) | 
					
						
							| 
									
										
										
										
											2020-01-09 08:31:49 +01:00
										 |  |  |         out['Starting Inventory'] = self.startinventory | 
					
						
							| 
									
										
										
										
											2018-03-26 21:39:48 -04:00
										 |  |  |         out['Special'] = self.medallions | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |         if self.hashes: | 
					
						
							|  |  |  |             out['Hashes'] = {f"{self.world.player_names[player][team]} (Team {team+1})": hash for (player, team), hash in self.hashes.items()} | 
					
						
							| 
									
										
										
										
											2018-03-26 21:39:48 -04:00
										 |  |  |         if self.shops: | 
					
						
							|  |  |  |             out['Shops'] = self.shops | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  |         out['playthrough'] = self.playthrough | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  |         out['paths'] = self.paths | 
					
						
							| 
									
										
										
										
											2019-12-17 15:55:53 +01:00
										 |  |  |         out['Bosses'] = self.bosses | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  |         out['meta'] = self.metadata | 
					
						
							| 
									
										
										
										
											2018-09-26 13:12:20 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  |         return json.dumps(out) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def to_file(self, filename): | 
					
						
							|  |  |  |         self.parse_data() | 
					
						
							| 
									
										
										
										
											2020-08-19 23:24:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-20 20:13:00 +02:00
										 |  |  |         def bool_to_text(variable: Union[bool, str]) -> str: | 
					
						
							|  |  |  |             if type(variable) == str: | 
					
						
							|  |  |  |                 return variable | 
					
						
							| 
									
										
										
										
											2020-08-19 23:24:17 +02:00
										 |  |  |             return 'Yes' if variable else 'No' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-10 00:36:26 +01:00
										 |  |  |         with open(filename, 'w', encoding="utf-8-sig") as outfile: | 
					
						
							|  |  |  |             outfile.write( | 
					
						
							| 
									
										
										
										
											2020-08-19 23:24:17 +02:00
										 |  |  |                 'ALttP Berserker\'s Multiworld Version %s  -  Seed: %s\n\n' % ( | 
					
						
							| 
									
										
										
										
											2020-08-20 20:13:00 +02:00
										 |  |  |                     self.metadata['version'], self.world.seed)) | 
					
						
							| 
									
										
										
										
											2019-08-24 15:53:21 -04:00
										 |  |  |             outfile.write('Filling Algorithm:               %s\n' % self.world.algorithm) | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |             outfile.write('Players:                         %d\n' % self.world.players) | 
					
						
							|  |  |  |             outfile.write('Teams:                           %d\n' % self.world.teams) | 
					
						
							|  |  |  |             for player in range(1, self.world.players + 1): | 
					
						
							|  |  |  |                 if self.world.players > 1: | 
					
						
							|  |  |  |                     outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_names(player))) | 
					
						
							|  |  |  |                 for team in range(self.world.teams): | 
					
						
							| 
									
										
										
										
											2020-03-10 00:36:26 +01:00
										 |  |  |                     outfile.write('%s%s\n' % ( | 
					
						
							| 
									
										
										
										
											2020-06-07 15:22:24 +02:00
										 |  |  |                         f"Hash - {self.world.player_names[player][team]} (Team {team + 1}): " if self.world.teams > 1 else 'Hash: ', | 
					
						
							|  |  |  |                         self.hashes[player, team])) | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |                 outfile.write('Logic:                           %s\n' % self.metadata['logic'][player]) | 
					
						
							| 
									
										
										
										
											2020-05-20 22:30:21 +02:00
										 |  |  |                 if self.world.players > 1: | 
					
						
							| 
									
										
										
										
											2020-06-07 15:22:24 +02:00
										 |  |  |                     outfile.write('Progression Balanced:            %s\n' % ( | 
					
						
							|  |  |  |                         'Yes' if self.metadata['progression_balancing'][player] else 'No')) | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |                 outfile.write('Mode:                            %s\n' % self.metadata['mode'][player]) | 
					
						
							| 
									
										
										
										
											2020-08-23 21:38:21 +02:00
										 |  |  |                 outfile.write('Retro:                           %s\n' % | 
					
						
							|  |  |  |                               ('Yes' if self.metadata['retro'][player] else 'No')) | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |                 outfile.write('Swords:                          %s\n' % self.metadata['weapons'][player]) | 
					
						
							|  |  |  |                 outfile.write('Goal:                            %s\n' % self.metadata['goal'][player]) | 
					
						
							| 
									
										
										
										
											2020-06-07 15:22:24 +02:00
										 |  |  |                 if "triforce" in self.metadata["goal"][player]:  # triforce hunt | 
					
						
							| 
									
										
										
										
											2020-08-23 21:38:21 +02:00
										 |  |  |                     outfile.write("Pieces available for Triforce:   %s\n" % | 
					
						
							|  |  |  |                                   self.metadata['triforce_pieces_available'][player]) | 
					
						
							|  |  |  |                     outfile.write("Pieces required for Triforce:    %s\n" % | 
					
						
							|  |  |  |                                   self.metadata["triforce_pieces_required"][player]) | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |                 outfile.write('Difficulty:                      %s\n' % self.metadata['item_pool'][player]) | 
					
						
							|  |  |  |                 outfile.write('Item Functionality:              %s\n' % self.metadata['item_functionality'][player]) | 
					
						
							| 
									
										
										
										
											2020-01-22 12:08:56 -05:00
										 |  |  |                 outfile.write('Item Progression:                %s\n' % self.metadata['progressive'][player]) | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |                 outfile.write('Entrance Shuffle:                %s\n' % self.metadata['shuffle'][player]) | 
					
						
							|  |  |  |                 outfile.write('Crystals required for GT:        %s\n' % self.metadata['gt_crystals'][player]) | 
					
						
							|  |  |  |                 outfile.write('Crystals required for Ganon:     %s\n' % self.metadata['ganon_crystals'][player]) | 
					
						
							| 
									
										
										
										
											2020-06-07 15:22:24 +02:00
										 |  |  |                 outfile.write('Pyramid hole pre-opened:         %s\n' % ( | 
					
						
							|  |  |  |                     'Yes' if self.metadata['open_pyramid'][player] else 'No')) | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |                 outfile.write('Accessibility:                   %s\n' % self.metadata['accessibility'][player]) | 
					
						
							| 
									
										
										
										
											2020-08-23 21:38:21 +02:00
										 |  |  |                 outfile.write('Map shuffle:                     %s\n' % | 
					
						
							|  |  |  |                               ('Yes' if self.metadata['mapshuffle'][player] else 'No')) | 
					
						
							|  |  |  |                 outfile.write('Compass shuffle:                 %s\n' % | 
					
						
							|  |  |  |                               ('Yes' if self.metadata['compassshuffle'][player] else 'No')) | 
					
						
							| 
									
										
										
										
											2020-08-19 23:24:17 +02:00
										 |  |  |                 outfile.write( | 
					
						
							| 
									
										
										
										
											2020-08-20 20:13:00 +02:00
										 |  |  |                     'Small Key shuffle:               %s\n' % (bool_to_text(self.metadata['keyshuffle'][player]))) | 
					
						
							| 
									
										
										
										
											2020-08-19 23:24:17 +02:00
										 |  |  |                 outfile.write('Big Key shuffle:                 %s\n' % ( | 
					
						
							|  |  |  |                     'Yes' if self.metadata['bigkeyshuffle'][player] else 'No')) | 
					
						
							| 
									
										
										
										
											2020-08-23 21:38:21 +02:00
										 |  |  |                 outfile.write('Shop inventory shuffle:          %s\n' % | 
					
						
							|  |  |  |                               bool_to_text("i" in self.metadata["shop_shuffle"][player])) | 
					
						
							|  |  |  |                 outfile.write('Shop price shuffle:              %s\n' % | 
					
						
							|  |  |  |                               bool_to_text("p" in self.metadata["shop_shuffle"][player])) | 
					
						
							|  |  |  |                 outfile.write('Shop upgrade shuffle:            %s\n' % | 
					
						
							|  |  |  |                               bool_to_text("u" in self.metadata["shop_shuffle"][player])) | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |                 outfile.write('Boss shuffle:                    %s\n' % self.metadata['boss_shuffle'][player]) | 
					
						
							| 
									
										
										
										
											2020-08-19 23:24:17 +02:00
										 |  |  |                 outfile.write( | 
					
						
							|  |  |  |                     'Enemy shuffle:                   %s\n' % bool_to_text(self.metadata['enemy_shuffle'][player])) | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |                 outfile.write('Enemy health:                    %s\n' % self.metadata['enemy_health'][player]) | 
					
						
							|  |  |  |                 outfile.write('Enemy damage:                    %s\n' % self.metadata['enemy_damage'][player]) | 
					
						
							| 
									
										
										
										
											2020-08-19 23:24:17 +02:00
										 |  |  |                 outfile.write(f'Killable thieves:                {bool_to_text(self.metadata["killable_thieves"])}\n') | 
					
						
							|  |  |  |                 outfile.write(f'Shuffled tiles:                  {bool_to_text(self.metadata["tile_shuffle"])}\n') | 
					
						
							|  |  |  |                 outfile.write(f'Shuffled bushes:                 {bool_to_text(self.metadata["bush_shuffle"])}\n') | 
					
						
							|  |  |  |                 outfile.write( | 
					
						
							|  |  |  |                     'Hints:                           %s\n' % ('Yes' if self.metadata['hints'][player] else 'No')) | 
					
						
							| 
									
										
										
										
											2020-01-18 12:51:10 -05:00
										 |  |  |                 outfile.write('Beemizer:                        %s\n' % self.metadata['beemizer'][player]) | 
					
						
							| 
									
										
										
										
											2020-08-19 23:24:17 +02:00
										 |  |  |                 outfile.write( | 
					
						
							|  |  |  |                     'Pot shuffle                      %s\n' % ('Yes' if self.metadata['shufflepots'][player] else 'No')) | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  |             if self.entrances: | 
					
						
							|  |  |  |                 outfile.write('\n\nEntrances:\n\n') | 
					
						
							| 
									
										
										
										
											2020-08-23 21:38:21 +02:00
										 |  |  |                 outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' | 
					
						
							|  |  |  |                                                          if self.world.players > 1 else '', entry['entrance'], | 
					
						
							|  |  |  |                                                          '<=>' if entry['direction'] == 'both' else | 
					
						
							|  |  |  |                                                          '<=' if entry['direction'] == 'exit' else '=>', | 
					
						
							|  |  |  |                                                          entry['exit']) for entry in self.entrances.values()])) | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |             outfile.write('\n\nMedallions:\n') | 
					
						
							|  |  |  |             for dungeon, medallion in self.medallions.items(): | 
					
						
							|  |  |  |                 outfile.write(f'\n{dungeon}: {medallion}') | 
					
						
							|  |  |  |             if self.startinventory: | 
					
						
							|  |  |  |                 outfile.write('\n\nStarting Inventory:\n\n') | 
					
						
							|  |  |  |                 outfile.write('\n'.join(self.startinventory)) | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  |             outfile.write('\n\nLocations:\n\n') | 
					
						
							| 
									
										
										
										
											2018-03-24 01:43:10 -04:00
										 |  |  |             outfile.write('\n'.join(['%s: %s' % (location, item) for grouping in self.locations.values() for (location, item) in grouping.items()])) | 
					
						
							|  |  |  |             outfile.write('\n\nShops:\n\n') | 
					
						
							|  |  |  |             outfile.write('\n'.join("{} [{}]\n    {}".format(shop['location'], shop['type'], "\n    ".join(item for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops)) | 
					
						
							| 
									
										
										
										
											2020-05-20 13:21:05 -07:00
										 |  |  |             for player in range(1, self.world.players + 1): | 
					
						
							|  |  |  |                 if self.world.boss_shuffle[player] != 'none': | 
					
						
							|  |  |  |                     bossmap = self.bosses[str(player)] if self.world.players > 1 else self.bosses | 
					
						
							|  |  |  |                     outfile.write(f'\n\nBosses{(f" ({self.world.get_player_names(player)})" if self.world.players > 1 else "")}:\n') | 
					
						
							|  |  |  |                     outfile.write('    '+'\n    '.join([f'{x}: {y}' for x, y in bossmap.items()])) | 
					
						
							| 
									
										
										
										
											2017-07-18 12:44:13 +02:00
										 |  |  |             outfile.write('\n\nPlaythrough:\n\n') | 
					
						
							| 
									
										
										
										
											2020-01-09 08:31:49 +01:00
										 |  |  |             outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join(['  %s: %s' % (location, item) for (location, item) in sphere.items()] if sphere_nr != '0' else [f'  {item}' for item in sphere])) for (sphere_nr, sphere) in self.playthrough.items()])) | 
					
						
							| 
									
										
										
										
											2019-12-21 13:33:07 +01:00
										 |  |  |             if self.unreachables: | 
					
						
							|  |  |  |                 outfile.write('\n\nUnreachable Items:\n\n') | 
					
						
							|  |  |  |                 outfile.write('\n'.join(['%s: %s' % (unreachable.item, unreachable) for unreachable in self.unreachables])) | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  |             outfile.write('\n\nPaths:\n\n') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             path_listings = [] | 
					
						
							| 
									
										
										
										
											2018-01-07 01:31:56 -05:00
										 |  |  |             for location, path in sorted(self.paths.items()): | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  |                 path_lines = [] | 
					
						
							| 
									
										
										
										
											2018-01-06 16:25:14 -05:00
										 |  |  |                 for region, exit in path: | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  |                     if exit is not None: | 
					
						
							|  |  |  |                         path_lines.append("{} -> {}".format(region, exit)) | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         path_lines.append(region) | 
					
						
							|  |  |  |                 path_listings.append("{}\n        {}".format(location, "\n   =>   ".join(path_lines))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             outfile.write('\n'.join(path_listings)) |