| 
									
										
										
										
											2018-01-06 16:25:14 -05:00
										 |  |  | from itertools import zip_longest | 
					
						
							| 
									
										
										
										
											2017-12-17 00:25:46 -05:00
										 |  |  | import logging | 
					
						
							| 
									
										
										
										
											2019-05-30 01:10:16 +02:00
										 |  |  | import os | 
					
						
							| 
									
										
										
										
											2017-12-17 00:25:46 -05:00
										 |  |  | import random | 
					
						
							|  |  |  | import time | 
					
						
							| 
									
										
										
										
											2020-01-04 22:08:13 +01:00
										 |  |  | import zlib | 
					
						
							| 
									
										
										
										
											2020-08-21 18:35:48 +02:00
										 |  |  | import concurrent.futures | 
					
						
							| 
									
										
										
										
											2021-01-03 14:32:32 +01:00
										 |  |  | import pickle | 
					
						
							| 
									
										
										
										
											2021-05-11 23:08:50 +02:00
										 |  |  | from typing import Dict, Tuple | 
					
						
							| 
									
										
										
										
											2017-12-17 00:25:46 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-24 06:02:51 +01:00
										 |  |  | from BaseClasses import MultiWorld, CollectionState, Region, Item | 
					
						
							| 
									
										
										
										
											2021-04-08 19:53:24 +02:00
										 |  |  | from worlds.alttp.Items import ItemFactory, item_name_groups | 
					
						
							| 
									
										
										
										
											2021-01-30 23:29:32 +01:00
										 |  |  | from worlds.alttp.Regions import create_regions, mark_light_world_regions, \ | 
					
						
							| 
									
										
										
										
											2021-01-03 14:32:32 +01:00
										 |  |  |     lookup_vanilla_location_to_entrance | 
					
						
							| 
									
										
										
										
											2020-10-24 05:38:56 +02:00
										 |  |  | from worlds.alttp.InvertedRegions import create_inverted_regions, mark_dark_world_regions | 
					
						
							| 
									
										
										
										
											2021-01-03 14:32:32 +01:00
										 |  |  | from worlds.alttp.EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect | 
					
						
							| 
									
										
										
										
											2020-10-24 05:38:56 +02:00
										 |  |  | from worlds.alttp.Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, get_hash_string | 
					
						
							|  |  |  | from worlds.alttp.Rules import set_rules | 
					
						
							|  |  |  | from worlds.alttp.Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive | 
					
						
							| 
									
										
										
										
											2021-01-04 15:14:20 +01:00
										 |  |  | from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned | 
					
						
							| 
									
										
										
										
											2021-01-30 23:43:15 +01:00
										 |  |  | from worlds.alttp.Shops import create_shops, ShopSlotFill, SHOP_ID_START, total_shop_slots, FillDisabledShopSlots | 
					
						
							| 
									
										
										
										
											2020-10-24 05:38:56 +02:00
										 |  |  | from worlds.alttp.ItemPool import generate_itempool, difficulties, fill_prizes | 
					
						
							| 
									
										
										
										
											2020-07-14 04:48:56 +02:00
										 |  |  | from Utils import output_path, parse_player_names, get_options, __version__, _version_tuple | 
					
						
							| 
									
										
										
										
											2021-04-08 19:53:24 +02:00
										 |  |  | from worlds.hk import gen_hollow | 
					
						
							| 
									
										
										
										
											2021-02-24 06:02:51 +01:00
										 |  |  | from worlds.hk import create_regions as hk_create_regions | 
					
						
							| 
									
										
										
										
											2021-02-24 00:36:37 +01:00
										 |  |  | from worlds.generic.Rules import locality_rules | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  | from worlds import Games, lookup_any_item_name_to_id, AutoWorld | 
					
						
							| 
									
										
										
										
											2020-07-10 22:43:54 +02:00
										 |  |  | import Patch | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-28 00:24:45 +02:00
										 |  |  | seeddigits = 20 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_seed(seed=None): | 
					
						
							|  |  |  |     if seed is None: | 
					
						
							|  |  |  |         random.seed(None) | 
					
						
							|  |  |  |         return random.randint(0, pow(10, seeddigits) - 1) | 
					
						
							|  |  |  |     return seed | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-29 16:11:23 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-03 01:54:52 +01:00
										 |  |  | def get_same_seed(world: MultiWorld, seed_def: tuple) -> str: | 
					
						
							| 
									
										
										
										
											2021-03-07 20:11:36 +01:00
										 |  |  |     seeds: Dict[tuple, str] = getattr(world, "__named_seeds", {}) | 
					
						
							| 
									
										
										
										
											2021-02-22 12:42:14 -08:00
										 |  |  |     if seed_def in seeds: | 
					
						
							|  |  |  |         return seeds[seed_def] | 
					
						
							|  |  |  |     seeds[seed_def] = str(world.random.randint(0, 2 ** 64)) | 
					
						
							| 
									
										
										
										
											2021-03-07 20:11:36 +01:00
										 |  |  |     world.__named_seeds = seeds | 
					
						
							| 
									
										
										
										
											2021-02-22 12:42:14 -08:00
										 |  |  |     return seeds[seed_def] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-25 15:58:35 +02:00
										 |  |  | def main(args, seed=None): | 
					
						
							| 
									
										
										
										
											2019-12-28 17:12:27 +01:00
										 |  |  |     if args.outputpath: | 
					
						
							| 
									
										
										
										
											2020-01-10 07:25:16 +01:00
										 |  |  |         os.makedirs(args.outputpath, exist_ok=True) | 
					
						
							| 
									
										
										
										
											2019-12-28 17:12:27 +01:00
										 |  |  |         output_path.cached_path = args.outputpath | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-02 12:38:26 +11:00
										 |  |  |     start = time.perf_counter() | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # initialize the world | 
					
						
							| 
									
										
										
										
											2021-03-14 08:38:02 +01:00
										 |  |  |     world = MultiWorld(args.multi) | 
					
						
							| 
									
										
										
										
											2020-07-14 07:01:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-20 14:07:40 +02:00
										 |  |  |     logger = logging.getLogger('') | 
					
						
							| 
									
										
										
										
											2020-06-28 00:24:45 +02:00
										 |  |  |     world.seed = get_seed(seed) | 
					
						
							| 
									
										
										
										
											2020-07-14 07:01:51 +02:00
										 |  |  |     if args.race: | 
					
						
							|  |  |  |         world.secure() | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         world.random.seed(world.seed) | 
					
						
							| 
									
										
										
										
											2021-05-16 00:21:00 +02:00
										 |  |  |     world.seed_name = str(args.outputname if args.outputname else world.seed) | 
					
						
							| 
									
										
										
										
											2017-05-20 14:07:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-14 08:38:02 +01:00
										 |  |  |     world.shuffle = args.shuffle.copy() | 
					
						
							|  |  |  |     world.logic = args.logic.copy() | 
					
						
							|  |  |  |     world.mode = args.mode.copy() | 
					
						
							| 
									
										
										
										
											2021-04-09 20:40:45 +02:00
										 |  |  |     world.swordless = args.swordless.copy() | 
					
						
							| 
									
										
										
										
											2021-03-14 08:38:02 +01:00
										 |  |  |     world.difficulty = args.difficulty.copy() | 
					
						
							|  |  |  |     world.item_functionality = args.item_functionality.copy() | 
					
						
							|  |  |  |     world.timer = args.timer.copy() | 
					
						
							|  |  |  |     world.progressive = args.progressive.copy() | 
					
						
							|  |  |  |     world.goal = args.goal.copy() | 
					
						
							| 
									
										
										
										
											2021-04-01 11:40:58 +02:00
										 |  |  |     world.local_items = args.local_items.copy() | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |     if hasattr(args, "algorithm"):  # current GUI options | 
					
						
							| 
									
										
										
										
											2021-03-14 08:38:02 +01:00
										 |  |  |         world.algorithm = args.algorithm | 
					
						
							|  |  |  |         world.shuffleganon = args.shuffleganon | 
					
						
							|  |  |  |         world.custom = args.custom | 
					
						
							|  |  |  |         world.customitemarray = args.customitemarray | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     world.accessibility = args.accessibility.copy() | 
					
						
							|  |  |  |     world.retro = args.retro.copy() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     world.hints = args.hints.copy() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-16 21:46:47 +01:00
										 |  |  |     world.mapshuffle = args.mapshuffle.copy() | 
					
						
							|  |  |  |     world.compassshuffle = args.compassshuffle.copy() | 
					
						
							|  |  |  |     world.keyshuffle = args.keyshuffle.copy() | 
					
						
							|  |  |  |     world.bigkeyshuffle = args.bigkeyshuffle.copy() | 
					
						
							| 
									
										
										
										
											2020-09-11 03:23:00 +02:00
										 |  |  |     world.open_pyramid = args.open_pyramid.copy() | 
					
						
							| 
									
										
										
										
											2019-12-17 15:55:53 +01:00
										 |  |  |     world.boss_shuffle = args.shufflebosses.copy() | 
					
						
							| 
									
										
										
										
											2020-08-19 23:24:17 +02:00
										 |  |  |     world.enemy_shuffle = args.enemy_shuffle.copy() | 
					
						
							| 
									
										
										
										
											2019-12-17 15:55:53 +01:00
										 |  |  |     world.enemy_health = args.enemy_health.copy() | 
					
						
							|  |  |  |     world.enemy_damage = args.enemy_damage.copy() | 
					
						
							| 
									
										
										
										
											2020-08-19 23:24:17 +02:00
										 |  |  |     world.killable_thieves = args.killable_thieves.copy() | 
					
						
							|  |  |  |     world.bush_shuffle = args.bush_shuffle.copy() | 
					
						
							|  |  |  |     world.tile_shuffle = args.tile_shuffle.copy() | 
					
						
							| 
									
										
										
										
											2019-12-30 03:03:53 +01:00
										 |  |  |     world.beemizer = args.beemizer.copy() | 
					
						
							| 
									
										
										
										
											2020-02-02 20:10:56 -05:00
										 |  |  |     world.timer = args.timer.copy() | 
					
						
							| 
									
										
										
										
											2020-10-28 16:20:59 -07:00
										 |  |  |     world.countdown_start_time = args.countdown_start_time.copy() | 
					
						
							|  |  |  |     world.red_clock_time = args.red_clock_time.copy() | 
					
						
							|  |  |  |     world.blue_clock_time = args.blue_clock_time.copy() | 
					
						
							|  |  |  |     world.green_clock_time = args.green_clock_time.copy() | 
					
						
							| 
									
										
										
										
											2020-01-18 12:51:10 -05:00
										 |  |  |     world.shufflepots = args.shufflepots.copy() | 
					
						
							| 
									
										
										
										
											2020-01-22 06:28:58 +01:00
										 |  |  |     world.progressive = args.progressive.copy() | 
					
						
							| 
									
										
										
										
											2020-04-12 15:46:32 -07:00
										 |  |  |     world.dungeon_counters = args.dungeon_counters.copy() | 
					
						
							| 
									
										
										
										
											2020-04-16 11:02:16 +02:00
										 |  |  |     world.glitch_boots = args.glitch_boots.copy() | 
					
						
							| 
									
										
										
										
											2020-06-17 01:02:54 -07:00
										 |  |  |     world.triforce_pieces_available = args.triforce_pieces_available.copy() | 
					
						
							| 
									
										
										
										
											2020-06-07 15:22:24 +02:00
										 |  |  |     world.triforce_pieces_required = args.triforce_pieces_required.copy() | 
					
						
							| 
									
										
										
										
											2020-08-23 15:03:06 +02:00
										 |  |  |     world.shop_shuffle = args.shop_shuffle.copy() | 
					
						
							| 
									
										
										
										
											2021-03-17 06:29:48 +01:00
										 |  |  |     world.progression_balancing = args.progression_balancing.copy() | 
					
						
							| 
									
										
										
										
											2020-09-20 04:35:45 +02:00
										 |  |  |     world.shuffle_prizes = args.shuffle_prizes.copy() | 
					
						
							| 
									
										
										
										
											2020-10-06 13:22:03 -07:00
										 |  |  |     world.sprite_pool = args.sprite_pool.copy() | 
					
						
							| 
									
										
										
										
											2020-10-07 19:51:46 +02:00
										 |  |  |     world.dark_room_logic = args.dark_room_logic.copy() | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  |     world.plando_items = args.plando_items.copy() | 
					
						
							| 
									
										
										
										
											2021-01-02 16:44:58 +01:00
										 |  |  |     world.plando_texts = args.plando_texts.copy() | 
					
						
							| 
									
										
										
										
											2021-01-02 22:41:03 +01:00
										 |  |  |     world.plando_connections = args.plando_connections.copy() | 
					
						
							| 
									
										
										
										
											2021-03-14 08:38:02 +01:00
										 |  |  |     world.er_seeds = getattr(args, "er_seeds", {}) | 
					
						
							| 
									
										
										
										
											2020-10-07 19:51:46 +02:00
										 |  |  |     world.restrict_dungeon_item_on_boss = args.restrict_dungeon_item_on_boss.copy() | 
					
						
							| 
									
										
										
										
											2021-01-02 23:00:14 +01:00
										 |  |  |     world.required_medallions = args.required_medallions.copy() | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |     world.game = args.game.copy() | 
					
						
							| 
									
										
										
										
											2021-06-11 18:02:48 +02:00
										 |  |  |     world.set_options(args) | 
					
						
							| 
									
										
										
										
											2021-03-22 13:14:19 -07:00
										 |  |  |     world.glitch_triforce = args.glitch_triforce  # This is enabled/disabled globally, no per player option. | 
					
						
							| 
									
										
										
										
											2019-08-11 08:55:38 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:47:13 +02:00
										 |  |  |     world.slot_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in | 
					
						
							|  |  |  |                         range(1, world.players + 1)} | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |     for player in range(1, world.players + 1): | 
					
						
							| 
									
										
										
										
											2021-02-20 12:01:38 -08:00
										 |  |  |         world.er_seeds[player] = str(world.random.randint(0, 2 ** 64)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if "-" in world.shuffle[player]: | 
					
						
							| 
									
										
										
										
											2021-02-22 12:42:14 -08:00
										 |  |  |             shuffle, seed = world.shuffle[player].split("-", 1) | 
					
						
							| 
									
										
										
										
											2021-02-20 12:01:38 -08:00
										 |  |  |             world.shuffle[player] = shuffle | 
					
						
							| 
									
										
										
										
											2021-02-24 13:23:42 -08:00
										 |  |  |             if shuffle == "vanilla": | 
					
						
							|  |  |  |                 world.er_seeds[player] = "vanilla" | 
					
						
							| 
									
										
										
										
											2021-04-01 11:44:37 +02:00
										 |  |  |             elif seed.startswith("group-") or args.race: | 
					
						
							| 
									
										
										
										
											2021-03-15 07:30:17 -07:00
										 |  |  |                 # renamed from team to group to not confuse with existing team name use | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |                 world.er_seeds[player] = get_same_seed(world, ( | 
					
						
							|  |  |  |                 shuffle, seed, world.retro[player], world.mode[player], world.logic[player])) | 
					
						
							| 
									
										
										
										
											2021-03-15 07:30:17 -07:00
										 |  |  |             else:  # not a race or group seed, use set seed as is. | 
					
						
							| 
									
										
										
										
											2021-02-22 12:42:14 -08:00
										 |  |  |                 world.er_seeds[player] = seed | 
					
						
							| 
									
										
										
										
											2021-02-24 13:23:42 -08:00
										 |  |  |         elif world.shuffle[player] == "vanilla": | 
					
						
							|  |  |  |             world.er_seeds[player] = "vanilla" | 
					
						
							| 
									
										
										
										
											2021-02-20 12:01:38 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-03 14:32:32 +01:00
										 |  |  |     logger.info('Archipelago Version %s  -  Seed: %s\n', __version__, world.seed) | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |     logger.info("Found World Types:") | 
					
						
							|  |  |  |     for name, cls in AutoWorld.AutoWorldRegister.world_types.items(): | 
					
						
							|  |  |  |         logger.info(f"  {name:30} {cls}") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |     parsed_names = parse_player_names(args.names, world.players, args.teams) | 
					
						
							|  |  |  |     world.teams = len(parsed_names) | 
					
						
							|  |  |  |     for i, team in enumerate(parsed_names, 1): | 
					
						
							|  |  |  |         if world.players > 1: | 
					
						
							|  |  |  |             logger.info('%s%s', 'Team%d: ' % i if world.teams > 1 else 'Players: ', ', '.join(team)) | 
					
						
							|  |  |  |         for player, name in enumerate(team, 1): | 
					
						
							|  |  |  |             world.player_names[player].append(name) | 
					
						
							| 
									
										
										
										
											2017-05-20 14:07:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |     logger.info('') | 
					
						
							| 
									
										
										
										
											2021-05-22 06:27:22 +02:00
										 |  |  |     for player in world.alttp_player_ids: | 
					
						
							|  |  |  |         world.difficulty_requirements[player] = difficulties[world.difficulty[player]] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-09 21:22:21 +02:00
										 |  |  |     for player in world.player_ids: | 
					
						
							|  |  |  |         for item_name in args.startinventory[player]: | 
					
						
							|  |  |  |             item = Item(item_name, True, lookup_any_item_name_to_id[item_name], player) | 
					
						
							| 
									
										
										
										
											2021-05-22 06:27:22 +02:00
										 |  |  |             item.game = world.game[player] | 
					
						
							| 
									
										
										
										
											2021-05-09 21:22:21 +02:00
										 |  |  |             world.push_precollected(item) | 
					
						
							| 
									
										
										
										
											2017-05-20 14:07:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |     for player in world.player_ids: | 
					
						
							| 
									
										
										
										
											2021-01-05 09:56:20 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # enforce pre-defined local items. | 
					
						
							|  |  |  |         if world.goal[player] in ["localtriforcehunt", "localganontriforcehunt"]: | 
					
						
							|  |  |  |             world.local_items[player].add('Triforce Piece') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-22 22:53:31 +01:00
										 |  |  |         # items can't be both local and non-local, prefer local | 
					
						
							|  |  |  |         world.non_local_items[player] -= world.local_items[player] | 
					
						
							| 
									
										
										
										
											2020-01-06 19:13:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-19 15:36:29 -08:00
										 |  |  |         # dungeon items can't be in non-local if the appropriate dungeon item shuffle setting is not set. | 
					
						
							|  |  |  |         if not world.mapshuffle[player]: | 
					
						
							|  |  |  |             world.non_local_items[player] -= item_name_groups['Maps'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if not world.compassshuffle[player]: | 
					
						
							|  |  |  |             world.non_local_items[player] -= item_name_groups['Compasses'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if not world.keyshuffle[player]: | 
					
						
							|  |  |  |             world.non_local_items[player] -= item_name_groups['Small Keys'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if not world.bigkeyshuffle[player]: | 
					
						
							|  |  |  |             world.non_local_items[player] -= item_name_groups['Big Keys'] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-19 16:23:21 -08:00
										 |  |  |         # Not possible to place pendants/crystals out side of boss prizes yet. | 
					
						
							|  |  |  |         world.non_local_items[player] -= item_name_groups['Pendants'] | 
					
						
							|  |  |  |         world.non_local_items[player] -= item_name_groups['Crystals'] | 
					
						
							| 
									
										
										
										
											2020-01-06 19:13:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-24 06:02:51 +01:00
										 |  |  |     for player in world.hk_player_ids: | 
					
						
							|  |  |  |         hk_create_regions(world, player) | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |     AutoWorld.call_all(world, "create_regions") | 
					
						
							| 
									
										
										
										
											2021-04-01 11:40:58 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |     for player in world.alttp_player_ids: | 
					
						
							|  |  |  |         if world.open_pyramid[player] == 'goal': | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |             world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', | 
					
						
							|  |  |  |                                                                 'localganontriforcehunt', 'ganonpedestal'} | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |         elif world.open_pyramid[player] == 'auto': | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |             world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', | 
					
						
							|  |  |  |                                                                 'localganontriforcehunt', 'ganonpedestal'} and \ | 
					
						
							|  |  |  |                                          (world.shuffle[player] in {'vanilla', 'dungeonssimple', 'dungeonsfull', | 
					
						
							|  |  |  |                                                                     'dungeonscrossed'} or not world.shuffle_ganon) | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |             world.open_pyramid[player] = {'on': True, 'off': False, 'yes': True, 'no': False}.get( | 
					
						
							| 
									
										
										
										
											2021-06-11 14:26:12 +02:00
										 |  |  |                 world.open_pyramid[player], 'auto') | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |         world.triforce_pieces_available[player] = max(world.triforce_pieces_available[player], | 
					
						
							|  |  |  |                                                       world.triforce_pieces_required[player]) | 
					
						
							| 
									
										
										
										
											2020-06-17 01:33:34 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-16 16:54:46 +01:00
										 |  |  |         if world.mode[player] != 'inverted': | 
					
						
							| 
									
										
										
										
											2019-07-27 09:13:13 -04:00
										 |  |  |             create_regions(world, player) | 
					
						
							| 
									
										
										
										
											2019-12-16 16:54:46 +01:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2019-07-27 09:13:13 -04:00
										 |  |  |             create_inverted_regions(world, player) | 
					
						
							| 
									
										
										
										
											2020-01-10 11:41:22 +01:00
										 |  |  |         create_shops(world, player) | 
					
						
							| 
									
										
										
										
											2019-12-16 16:54:46 +01:00
										 |  |  |         create_dungeons(world, player) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-20 14:07:40 +02:00
										 |  |  |     logger.info('Shuffling the World about.') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |     for player in world.alttp_player_ids: | 
					
						
							| 
									
										
										
										
											2020-08-22 09:28:24 -04:00
										 |  |  |         if world.logic[player] not in ["noglitches", "minorglitches"] and world.shuffle[player] in \ | 
					
						
							| 
									
										
										
										
											2020-07-16 04:14:44 +02:00
										 |  |  |                 {"vanilla", "dungeonssimple", "dungeonsfull", "simple", "restricted", "full"}: | 
					
						
							|  |  |  |             world.fix_fake_world[player] = False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-20 02:30:55 +01:00
										 |  |  |         # seeded entrance shuffle | 
					
						
							| 
									
										
										
										
											2021-02-20 12:01:38 -08:00
										 |  |  |         old_random = world.random | 
					
						
							|  |  |  |         world.random = random.Random(world.er_seeds[player]) | 
					
						
							| 
									
										
										
										
											2021-02-20 02:30:55 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-16 16:54:46 +01:00
										 |  |  |         if world.mode[player] != 'inverted': | 
					
						
							| 
									
										
										
										
											2019-07-27 09:13:13 -04:00
										 |  |  |             link_entrances(world, player) | 
					
						
							| 
									
										
										
										
											2019-12-16 16:54:46 +01:00
										 |  |  |             mark_light_world_regions(world, player) | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2019-07-27 09:13:13 -04:00
										 |  |  |             link_inverted_entrances(world, player) | 
					
						
							| 
									
										
										
										
											2019-12-16 16:54:46 +01:00
										 |  |  |             mark_dark_world_regions(world, player) | 
					
						
							| 
									
										
										
										
											2021-02-20 02:30:55 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         world.random = old_random | 
					
						
							| 
									
										
										
										
											2021-01-02 22:41:03 +01:00
										 |  |  |         plando_connect(world, player) | 
					
						
							| 
									
										
										
										
											2017-05-20 14:07:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-18 16:11:11 -05:00
										 |  |  |     logger.info('Generating Item Pool.') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |     for player in world.alttp_player_ids: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         generate_itempool(world, player) | 
					
						
							| 
									
										
										
										
											2019-04-18 16:11:11 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-20 14:07:40 +02:00
										 |  |  |     logger.info('Calculating Access Rules.') | 
					
						
							| 
									
										
										
										
											2021-02-24 00:36:37 +01:00
										 |  |  |     if world.players > 1: | 
					
						
							|  |  |  |         for player in world.player_ids: | 
					
						
							|  |  |  |             locality_rules(world, player) | 
					
						
							| 
									
										
										
										
											2021-04-01 11:40:58 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 18:02:48 +02:00
										 |  |  |     AutoWorld.call_all(world, "set_rules") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |     for player in world.alttp_player_ids: | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         set_rules(world, player) | 
					
						
							| 
									
										
										
										
											2017-05-20 14:07:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |     for player in world.hk_player_ids: | 
					
						
							|  |  |  |         gen_hollow(world, player) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |     AutoWorld.call_all(world, "generate_basic") | 
					
						
							| 
									
										
										
										
											2021-04-01 11:40:58 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  |     logger.info("Running Item Plando") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 00:47:17 +01:00
										 |  |  |     for item in world.itempool: | 
					
						
							|  |  |  |         item.world = world | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-04 15:14:20 +01:00
										 |  |  |     distribute_planned(world) | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-24 08:26:39 +01:00
										 |  |  |     logger.info('Placing Dungeon Prizes.') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     fill_prizes(world) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-23 21:32:31 +02:00
										 |  |  |     logger.info('Placing Dungeon Items.') | 
					
						
							| 
									
										
										
										
											2017-05-20 14:07:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-14 08:38:02 +01:00
										 |  |  |     if world.algorithm in ['balanced', 'vt26'] or any( | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  |             list(args.mapshuffle.values()) + list(args.compassshuffle.values()) + | 
					
						
							|  |  |  |             list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())): | 
					
						
							| 
									
										
										
										
											2020-10-07 19:51:46 +02:00
										 |  |  |         fill_dungeons_restrictive(world) | 
					
						
							| 
									
										
										
										
											2017-10-15 13:52:42 -04:00
										 |  |  |     else: | 
					
						
							|  |  |  |         fill_dungeons(world) | 
					
						
							| 
									
										
										
										
											2017-05-20 14:07:40 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     logger.info('Fill the world.') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-14 08:38:02 +01:00
										 |  |  |     if world.algorithm == 'flood': | 
					
						
							| 
									
										
										
										
											2017-05-20 14:07:40 +02:00
										 |  |  |         flood_items(world)  # different algo, biased towards early game progress items | 
					
						
							| 
									
										
										
										
											2021-03-14 08:38:02 +01:00
										 |  |  |     elif world.algorithm == 'vt25': | 
					
						
							| 
									
										
										
										
											2019-12-16 15:27:20 +01:00
										 |  |  |         distribute_items_restrictive(world, False) | 
					
						
							| 
									
										
										
										
											2021-03-14 08:38:02 +01:00
										 |  |  |     elif world.algorithm == 'vt26': | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  |         distribute_items_restrictive(world, True) | 
					
						
							| 
									
										
										
										
											2021-03-14 08:38:02 +01:00
										 |  |  |     elif world.algorithm == 'balanced': | 
					
						
							| 
									
										
										
										
											2019-12-16 15:27:20 +01:00
										 |  |  |         distribute_items_restrictive(world, True) | 
					
						
							| 
									
										
										
										
											2017-06-03 21:28:02 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |     logger.info("Filling Shop Slots") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ShopSlotFill(world) | 
					
						
							| 
									
										
										
										
											2020-12-23 15:30:21 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-05 08:07:12 +01:00
										 |  |  |     if world.players > 1: | 
					
						
							|  |  |  |         balance_multiworld_progression(world) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |     logger.info('Generating output files.') | 
					
						
							| 
									
										
										
										
											2021-05-16 00:21:00 +02:00
										 |  |  |     outfilebase = 'AP_' + world.seed_name | 
					
						
							| 
									
										
										
										
											2020-01-04 22:08:13 +01:00
										 |  |  |     rom_names = [] | 
					
						
							| 
									
										
										
										
											2017-05-20 14:07:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-06 23:08:46 +01:00
										 |  |  |     def _gen_rom(team: int, player: int): | 
					
						
							| 
									
										
										
										
											2020-08-19 23:24:17 +02:00
										 |  |  |         use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player] | 
					
						
							| 
									
										
										
										
											2020-03-06 23:08:46 +01:00
										 |  |  |                         or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default' | 
					
						
							| 
									
										
										
										
											2020-10-04 10:57:30 -07:00
										 |  |  |                         or world.shufflepots[player] or world.bush_shuffle[player] | 
					
						
							| 
									
										
										
										
											2020-12-22 01:05:48 -08:00
										 |  |  |                         or world.killable_thieves[player]) | 
					
						
							| 
									
										
										
										
											2019-05-30 01:10:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-09 21:52:46 +02:00
										 |  |  |         rom = LocalRom(args.rom) | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-06 23:08:46 +01:00
										 |  |  |         patch_rom(world, rom, player, team, use_enemizer) | 
					
						
							| 
									
										
										
										
											2019-05-30 01:10:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-09 21:52:46 +02:00
										 |  |  |         if use_enemizer: | 
					
						
							| 
									
										
										
										
											2021-01-27 02:39:12 +01:00
										 |  |  |             patch_enemizer(world, team, player, rom, args.enemizercli) | 
					
						
							| 
									
										
										
										
											2019-05-30 01:10:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-06 23:08:46 +01:00
										 |  |  |         if args.race: | 
					
						
							| 
									
										
										
										
											2020-10-20 01:16:20 -07:00
										 |  |  |             patch_race_rom(rom, world, player) | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-06 23:08:46 +01:00
										 |  |  |         world.spoiler.hashes[(player, team)] = get_hash_string(rom.hash) | 
					
						
							| 
									
										
										
										
											2019-05-30 01:10:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |         palettes_options = {} | 
					
						
							|  |  |  |         palettes_options['dungeon'] = args.uw_palettes[player] | 
					
						
							|  |  |  |         palettes_options['overworld'] = args.ow_palettes[player] | 
					
						
							|  |  |  |         palettes_options['hud'] = args.hud_palettes[player] | 
					
						
							|  |  |  |         palettes_options['sword'] = args.sword_palettes[player] | 
					
						
							|  |  |  |         palettes_options['shield'] = args.shield_palettes[player] | 
					
						
							|  |  |  |         palettes_options['link'] = args.link_palettes[player] | 
					
						
							| 
									
										
										
										
											2021-01-03 13:13:59 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-06 23:08:46 +01:00
										 |  |  |         apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player], | 
					
						
							| 
									
										
										
										
											2021-03-03 01:59:33 +01:00
										 |  |  |                            args.fastmenu[player], args.disablemusic[player], args.sprite[player], | 
					
						
							|  |  |  |                            palettes_options, world, player, True, | 
					
						
							| 
									
										
										
										
											2021-03-07 20:11:36 +01:00
										 |  |  |                            reduceflashing=args.reduceflashing[player] or args.race, | 
					
						
							| 
									
										
										
										
											2021-03-03 01:59:33 +01:00
										 |  |  |                            triforcehud=args.triforcehud[player]) | 
					
						
							| 
									
										
										
										
											2020-10-24 02:44:27 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-09 21:52:46 +02:00
										 |  |  |         mcsb_name = '' | 
					
						
							|  |  |  |         if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], | 
					
						
							|  |  |  |                 world.bigkeyshuffle[player]]): | 
					
						
							|  |  |  |             mcsb_name = '-keysanity' | 
					
						
							|  |  |  |         elif [world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], | 
					
						
							|  |  |  |               world.bigkeyshuffle[player]].count(True) == 1: | 
					
						
							| 
									
										
										
										
											2020-09-02 15:06:36 -07:00
										 |  |  |             mcsb_name = '-mapshuffle' if world.mapshuffle[player] else \ | 
					
						
							|  |  |  |                 '-compassshuffle' if world.compassshuffle[player] else \ | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |                     '-universal_keys' if world.keyshuffle[player] == "universal" else \ | 
					
						
							|  |  |  |                         '-keyshuffle' if world.keyshuffle[player] else '-bigkeyshuffle' | 
					
						
							| 
									
										
										
										
											2020-06-09 21:52:46 +02:00
										 |  |  |         elif any([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], | 
					
						
							|  |  |  |                   world.bigkeyshuffle[player]]): | 
					
						
							|  |  |  |             mcsb_name = '-%s%s%s%sshuffle' % ( | 
					
						
							|  |  |  |                 'M' if world.mapshuffle[player] else '', 'C' if world.compassshuffle[player] else '', | 
					
						
							| 
									
										
										
										
											2020-09-02 15:06:36 -07:00
										 |  |  |                 'U' if world.keyshuffle[player] == "universal" else 'S' if world.keyshuffle[player] else '', | 
					
						
							|  |  |  |                 'B' if world.bigkeyshuffle[player] else '') | 
					
						
							| 
									
										
										
										
											2020-06-09 21:52:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-14 15:25:57 +02:00
										 |  |  |         outfilepname = f'_P{player}' | 
					
						
							| 
									
										
										
										
											2020-09-02 15:06:36 -07:00
										 |  |  |         outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" \ | 
					
						
							|  |  |  |             if world.player_names[player][team] != 'Player%d' % player else '' | 
					
						
							|  |  |  |         outfilestuffs = { | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |             "logic": world.logic[player],  # 0 | 
					
						
							|  |  |  |             "difficulty": world.difficulty[player],  # 1 | 
					
						
							|  |  |  |             "item_functionality": world.item_functionality[player],  # 2 | 
					
						
							|  |  |  |             "mode": world.mode[player],  # 3 | 
					
						
							|  |  |  |             "goal": world.goal[player],  # 4 | 
					
						
							|  |  |  |             "timer": str(world.timer[player]),  # 5 | 
					
						
							|  |  |  |             "shuffle": world.shuffle[player],  # 6 | 
					
						
							|  |  |  |             "algorithm": world.algorithm,  # 7 | 
					
						
							|  |  |  |             "mscb": mcsb_name,  # 8 | 
					
						
							|  |  |  |             "retro": world.retro[player],  # 9 | 
					
						
							|  |  |  |             "progressive": world.progressive,  # A | 
					
						
							|  |  |  |             "hints": 'True' if world.hints[player] else 'False'  # B | 
					
						
							| 
									
										
										
										
											2020-09-02 15:06:36 -07:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-01-11 04:06:25 +01:00
										 |  |  |         #                  0  1  2  3  4 5  6  7 8 9 A B | 
					
						
							| 
									
										
										
										
											2020-09-02 15:06:36 -07:00
										 |  |  |         outfilesuffix = ('_%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s' % ( | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |             #  0          1      2      3    4     5    6      7     8        9         A     B           C | 
					
						
							|  |  |  |             # _noglitches_normal-normal-open-ganon-ohko_simple-balanced-keysanity-retro-prog_random-nohints | 
					
						
							|  |  |  |             # _noglitches_normal-normal-open-ganon     _simple-balanced-keysanity-retro | 
					
						
							|  |  |  |             # _noglitches_normal-normal-open-ganon     _simple-balanced-keysanity      -prog_random | 
					
						
							|  |  |  |             # _noglitches_normal-normal-open-ganon     _simple-balanced-keysanity                  -nohints | 
					
						
							|  |  |  |             outfilestuffs["logic"],  # 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             outfilestuffs["difficulty"],  # 1 | 
					
						
							|  |  |  |             outfilestuffs["item_functionality"],  # 2 | 
					
						
							|  |  |  |             outfilestuffs["mode"],  # 3 | 
					
						
							|  |  |  |             outfilestuffs["goal"],  # 4 | 
					
						
							|  |  |  |             "" if outfilestuffs["timer"] in ['False', 'none', 'display'] else "-" + outfilestuffs["timer"],  # 5 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             outfilestuffs["shuffle"],  # 6 | 
					
						
							|  |  |  |             outfilestuffs["algorithm"],  # 7 | 
					
						
							|  |  |  |             outfilestuffs["mscb"],  # 8 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             "-retro" if outfilestuffs["retro"] == "True" else "",  # 9 | 
					
						
							|  |  |  |             "-prog_" + outfilestuffs["progressive"] if outfilestuffs["progressive"] in ['off', 'random'] else "",  # A | 
					
						
							|  |  |  |             "-nohints" if not outfilestuffs["hints"] == "True" else "")  # B | 
					
						
							|  |  |  |                          ) if not args.outputname else '' | 
					
						
							| 
									
										
										
										
											2020-06-09 21:52:46 +02:00
										 |  |  |         rompath = output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc') | 
					
						
							| 
									
										
										
										
											2020-08-16 11:13:50 +02:00
										 |  |  |         rom.write_to_file(rompath, hide_enemizer=True) | 
					
						
							| 
									
										
										
										
											2020-06-09 21:52:46 +02:00
										 |  |  |         if args.create_diff: | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |             Patch.create_patch_file(rompath, player=player, player_name=world.player_names[player][team]) | 
					
						
							| 
									
										
										
										
											2020-10-19 08:26:31 +02:00
										 |  |  |         return player, team, bytes(rom.name) | 
					
						
							| 
									
										
										
										
											2020-03-06 23:08:46 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-21 18:35:48 +02:00
										 |  |  |     pool = concurrent.futures.ThreadPoolExecutor() | 
					
						
							| 
									
										
										
										
											2021-05-19 21:55:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-11 19:56:18 +01:00
										 |  |  |     check_accessibility_task = pool.submit(world.fulfills_accessibility) | 
					
						
							| 
									
										
										
										
											2021-05-13 01:40:36 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     rom_futures = [] | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |     output_file_futures = [] | 
					
						
							| 
									
										
										
										
											2021-05-13 01:40:36 +02:00
										 |  |  |     for team in range(world.teams): | 
					
						
							|  |  |  |         for player in world.alttp_player_ids: | 
					
						
							|  |  |  |             rom_futures.append(pool.submit(_gen_rom, team, player)) | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |     for player in world.player_ids: | 
					
						
							|  |  |  |         output_file_futures.append(pool.submit(AutoWorld.call_single, world, "generate_output", player)) | 
					
						
							| 
									
										
										
										
											2021-05-13 01:40:36 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def get_entrance_to_region(region: Region): | 
					
						
							|  |  |  |         for entrance in region.entrances: | 
					
						
							|  |  |  |             if entrance.parent_region.type in (RegionType.DarkWorld, RegionType.LightWorld): | 
					
						
							|  |  |  |                 return entrance | 
					
						
							|  |  |  |         for entrance in region.entrances:  # BFS might be better here, trying DFS for now. | 
					
						
							|  |  |  |             return get_entrance_to_region(entrance.parent_region) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # collect ER hint info | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |     er_hint_data = {player: {} for player in range(1, world.players + 1) if | 
					
						
							|  |  |  |                     world.shuffle[player] != "vanilla" or world.retro[player]} | 
					
						
							| 
									
										
										
										
											2021-05-13 01:40:36 +02:00
										 |  |  |     from worlds.alttp.Regions import RegionType | 
					
						
							|  |  |  |     for region in world.regions: | 
					
						
							|  |  |  |         if region.player in er_hint_data and region.locations: | 
					
						
							|  |  |  |             main_entrance = get_entrance_to_region(region) | 
					
						
							|  |  |  |             for location in region.locations: | 
					
						
							|  |  |  |                 if type(location.address) == int:  # skips events and crystals | 
					
						
							|  |  |  |                     if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name: | 
					
						
							|  |  |  |                         er_hint_data[region.player][location.address] = main_entrance.name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ordered_areas = ('Light World', 'Dark World', 'Hyrule Castle', 'Agahnims Tower', 'Eastern Palace', 'Desert Palace', | 
					
						
							|  |  |  |                      'Tower of Hera', 'Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town', 'Ice Palace', | 
					
						
							|  |  |  |                      'Misery Mire', 'Turtle Rock', 'Ganons Tower', "Total") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     checks_in_area = {player: {area: list() for area in ordered_areas} | 
					
						
							|  |  |  |                       for player in range(1, world.players + 1)} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for player in range(1, world.players + 1): | 
					
						
							|  |  |  |         checks_in_area[player]["Total"] = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for location in [loc for loc in world.get_filled_locations() if type(loc.address) is int]: | 
					
						
							|  |  |  |         main_entrance = get_entrance_to_region(location.parent_region) | 
					
						
							|  |  |  |         if location.game != Games.LTTP: | 
					
						
							|  |  |  |             checks_in_area[location.player]["Light World"].append(location.address) | 
					
						
							|  |  |  |         elif location.parent_region.dungeon: | 
					
						
							|  |  |  |             dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower', | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |                            'Inverted Ganons Tower': 'Ganons Tower'} \ | 
					
						
							| 
									
										
										
										
											2021-05-13 01:40:36 +02:00
										 |  |  |                 .get(location.parent_region.dungeon.name, location.parent_region.dungeon.name) | 
					
						
							|  |  |  |             checks_in_area[location.player][dungeonname].append(location.address) | 
					
						
							|  |  |  |         elif main_entrance.parent_region.type == RegionType.LightWorld: | 
					
						
							|  |  |  |             checks_in_area[location.player]["Light World"].append(location.address) | 
					
						
							|  |  |  |         elif main_entrance.parent_region.type == RegionType.DarkWorld: | 
					
						
							|  |  |  |             checks_in_area[location.player]["Dark World"].append(location.address) | 
					
						
							|  |  |  |         checks_in_area[location.player]["Total"] += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     oldmancaves = [] | 
					
						
							|  |  |  |     takeanyregions = ["Old Man Sword Cave", "Take-Any #1", "Take-Any #2", "Take-Any #3", "Take-Any #4"] | 
					
						
							|  |  |  |     for index, take_any in enumerate(takeanyregions): | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |         for region in [world.get_region(take_any, player) for player in range(1, world.players + 1) if | 
					
						
							|  |  |  |                        world.retro[player]]: | 
					
						
							|  |  |  |             item = ItemFactory(region.shop.inventory[(0 if take_any == "Old Man Sword Cave" else 1)]['item'], | 
					
						
							|  |  |  |                                region.player) | 
					
						
							| 
									
										
										
										
											2021-05-13 01:40:36 +02:00
										 |  |  |             player = region.player | 
					
						
							|  |  |  |             location_id = SHOP_ID_START + total_shop_slots + index | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             main_entrance = get_entrance_to_region(region) | 
					
						
							|  |  |  |             if main_entrance.parent_region.type == RegionType.LightWorld: | 
					
						
							|  |  |  |                 checks_in_area[player]["Light World"].append(location_id) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 checks_in_area[player]["Dark World"].append(location_id) | 
					
						
							|  |  |  |             checks_in_area[player]["Total"] += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             er_hint_data[player][location_id] = main_entrance.name | 
					
						
							|  |  |  |             oldmancaves.append(((location_id, player), (item.code, player))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     FillDisabledShopSlots(world) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |     def write_multidata(roms, outputs): | 
					
						
							| 
									
										
										
										
											2021-05-13 01:40:36 +02:00
										 |  |  |         import base64 | 
					
						
							| 
									
										
										
										
											2021-05-13 02:10:37 +02:00
										 |  |  |         import NetUtils | 
					
						
							| 
									
										
										
										
											2021-05-13 01:40:36 +02:00
										 |  |  |         for future in roms: | 
					
						
							|  |  |  |             rom_name = future.result() | 
					
						
							|  |  |  |             rom_names.append(rom_name) | 
					
						
							|  |  |  |         slot_data = {} | 
					
						
							|  |  |  |         client_versions = {} | 
					
						
							|  |  |  |         minimum_versions = {"server": (0, 1, 1), "clients": client_versions} | 
					
						
							|  |  |  |         games = {} | 
					
						
							|  |  |  |         for slot in world.player_ids: | 
					
						
							| 
									
										
										
										
											2021-05-25 01:03:04 +02:00
										 |  |  |             if world.game[slot] == "Factorio": | 
					
						
							| 
									
										
										
										
											2021-05-29 06:23:35 +02:00
										 |  |  |                 client_versions[slot] = (0, 1, 2) | 
					
						
							| 
									
										
										
										
											2021-05-25 01:03:04 +02:00
										 |  |  |             else: | 
					
						
							|  |  |  |                 client_versions[slot] = (0, 0, 3) | 
					
						
							| 
									
										
										
										
											2021-05-13 01:40:36 +02:00
										 |  |  |             games[slot] = world.game[slot] | 
					
						
							|  |  |  |         connect_names = {base64.b64encode(rom_name).decode(): (team, slot) for | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |                          slot, team, rom_name in rom_names} | 
					
						
							|  |  |  |         precollected_items = {player: [] for player in range(1, world.players + 1)} | 
					
						
							| 
									
										
										
										
											2021-05-13 02:10:37 +02:00
										 |  |  |         for item in world.precollected_items: | 
					
						
							|  |  |  |             precollected_items[item.player].append(item.code) | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |         precollected_hints = {player: set() for player in range(1, world.players + 1)} | 
					
						
							| 
									
										
										
										
											2021-05-13 02:10:37 +02:00
										 |  |  |         # for now special case Factorio visibility | 
					
						
							|  |  |  |         sending_visible_players = set() | 
					
						
							|  |  |  |         for player in world.factorio_player_ids: | 
					
						
							|  |  |  |             if world.visibility[player]: | 
					
						
							|  |  |  |                 sending_visible_players.add(player) | 
					
						
							| 
									
										
										
										
											2021-05-13 01:40:36 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         for i, team in enumerate(parsed_names): | 
					
						
							|  |  |  |             for player, name in enumerate(team, 1): | 
					
						
							|  |  |  |                 if player not in world.alttp_player_ids: | 
					
						
							|  |  |  |                     connect_names[name] = (i, player) | 
					
						
							| 
									
										
										
										
											2021-06-11 18:05:49 +02:00
										 |  |  |         if world.hk_player_ids: | 
					
						
							|  |  |  |             import Options | 
					
						
							|  |  |  |             for slot in world.hk_player_ids: | 
					
						
							|  |  |  |                 slots_data = slot_data[slot] = {} | 
					
						
							|  |  |  |                 for option_name in Options.hollow_knight_options: | 
					
						
							|  |  |  |                     option = getattr(world, option_name)[slot] | 
					
						
							|  |  |  |                     slots_data[option_name] = int(option.value) | 
					
						
							| 
									
										
										
										
											2021-05-13 01:40:36 +02:00
										 |  |  |         for slot in world.minecraft_player_ids: | 
					
						
							| 
									
										
										
										
											2021-06-15 18:15:05 -05:00
										 |  |  |             slot_data[slot] = AutoWorld.call_single(world, "fill_slot_data", slot) | 
					
						
							| 
									
										
										
										
											2021-05-13 01:40:36 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         locations_data: Dict[int, Dict[int, Tuple[int, int]]] = {player: {} for player in world.player_ids} | 
					
						
							|  |  |  |         for location in world.get_filled_locations(): | 
					
						
							|  |  |  |             if type(location.address) == int: | 
					
						
							|  |  |  |                 locations_data[location.player][location.address] = (location.item.code, location.item.player) | 
					
						
							| 
									
										
										
										
											2021-05-13 02:10:37 +02:00
										 |  |  |                 if location.player in sending_visible_players and location.item.player != location.player: | 
					
						
							| 
									
										
										
										
											2021-05-14 01:25:41 +02:00
										 |  |  |                     hint = NetUtils.Hint(location.item.player, location.player, location.address, | 
					
						
							|  |  |  |                                          location.item.code, False) | 
					
						
							|  |  |  |                     precollected_hints[location.player].add(hint) | 
					
						
							|  |  |  |                     precollected_hints[location.item.player].add(hint) | 
					
						
							| 
									
										
										
										
											2021-05-13 02:35:50 +02:00
										 |  |  |                 elif location.item.name in args.start_hints[location.item.player]: | 
					
						
							| 
									
										
										
										
											2021-05-14 01:25:41 +02:00
										 |  |  |                     hint = NetUtils.Hint(location.item.player, location.player, location.address, | 
					
						
							|  |  |  |                                          location.item.code, False, | 
					
						
							|  |  |  |                                          er_hint_data.get(location.player, {}).get(location.address, "")) | 
					
						
							|  |  |  |                     precollected_hints[location.player].add(hint) | 
					
						
							|  |  |  |                     precollected_hints[location.item.player].add(hint) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 01:40:36 +02:00
										 |  |  |         multidata = zlib.compress(pickle.dumps({ | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |             "slot_data": slot_data, | 
					
						
							| 
									
										
										
										
											2021-05-13 01:40:36 +02:00
										 |  |  |             "games": games, | 
					
						
							|  |  |  |             "names": parsed_names, | 
					
						
							|  |  |  |             "connect_names": connect_names, | 
					
						
							|  |  |  |             "remote_items": {player for player in range(1, world.players + 1) if | 
					
						
							|  |  |  |                              world.remote_items[player]}, | 
					
						
							|  |  |  |             "locations": locations_data, | 
					
						
							|  |  |  |             "checks_in_area": checks_in_area, | 
					
						
							|  |  |  |             "server_options": get_options()["server_options"], | 
					
						
							|  |  |  |             "er_hint_data": er_hint_data, | 
					
						
							|  |  |  |             "precollected_items": precollected_items, | 
					
						
							| 
									
										
										
										
											2021-05-13 02:10:37 +02:00
										 |  |  |             "precollected_hints": precollected_hints, | 
					
						
							| 
									
										
										
										
											2021-05-13 01:40:36 +02:00
										 |  |  |             "version": tuple(_version_tuple), | 
					
						
							|  |  |  |             "tags": ["AP"], | 
					
						
							|  |  |  |             "minimum_versions": minimum_versions, | 
					
						
							| 
									
										
										
										
											2021-05-16 00:21:00 +02:00
										 |  |  |             "seed_name": world.seed_name | 
					
						
							| 
									
										
										
										
											2021-05-13 01:40:36 +02:00
										 |  |  |         }), 9) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         with open(output_path('%s.archipelago' % outfilebase), 'wb') as f: | 
					
						
							|  |  |  |             f.write(bytes([1]))  # version of format | 
					
						
							|  |  |  |             f.write(multidata) | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |         for future in outputs: | 
					
						
							|  |  |  |             future.result()  # collect errors if they occured | 
					
						
							| 
									
										
										
										
											2021-05-13 01:40:36 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |     multidata_task = pool.submit(write_multidata, rom_futures, output_file_futures) | 
					
						
							| 
									
										
										
										
											2021-01-11 19:56:18 +01:00
										 |  |  |     if not check_accessibility_task.result(): | 
					
						
							|  |  |  |         if not world.can_beat_game(): | 
					
						
							| 
									
										
										
										
											2021-02-22 11:18:53 +01:00
										 |  |  |             raise Exception("Game appears as unbeatable. Aborting.") | 
					
						
							| 
									
										
										
										
											2021-01-11 19:56:18 +01:00
										 |  |  |         else: | 
					
						
							|  |  |  |             logger.warning("Location Accessibility requirements not fulfilled.") | 
					
						
							| 
									
										
										
										
											2020-08-23 12:06:00 +02:00
										 |  |  |     if multidata_task: | 
					
						
							|  |  |  |         multidata_task.result()  # retrieve exception if one exists | 
					
						
							| 
									
										
										
										
											2020-08-21 18:35:48 +02:00
										 |  |  |     pool.shutdown()  # wait for all queued tasks to complete | 
					
						
							| 
									
										
										
										
											2021-03-17 10:53:40 +01:00
										 |  |  |     if not args.skip_playthrough: | 
					
						
							|  |  |  |         logger.info('Calculating playthrough.') | 
					
						
							| 
									
										
											  
											
												Minecraft Randomizer
Squash merge, original Commits:
* Minecraft locations, items, and generation without logic
* added id lookup for minecraft
* typing import fix in minecraft/Items.py
* fix 2
* implementing Minecraft options and hard/postgame advancement exclusion
* first logic pass (75/80)
* logic pass 2 and proper completion conditions
* added insane difficulty pool, modified method of excluding item pools for easier extension
* bump network_data_package version
* minecraft testing framework
* switch Ancient Debris to Netherite Scrap to avoid advancement triggering on receiving that item
* Testing now functions, split tests up by advancement pane, added some story tests
* Newer testing framework: every advancement gets its own function, for ease of testing
* fixed logic for The End... Again...
* changed option names to "include_hard_advancements" etc.
* village/pillager-related advancements now require can_adventure: weapon + food
* a few minecraft tests
* rename "Flint & Steel" to "Flint and Steel" for parity with in-game name
* additional MC tests
* more tests, mostly nether-related tests
* more tests, removed anvil path for Two Birds One Arrow
* include Minecraft slot data, and a world seed for each Minecraft player slot
* Added new items: ender pearls, lapis, porkchops
* All remaining Minecraft tests
* formatting of Minecraft tests and logic for better readability
* require Wither kill for Monsters Hunted
* properly removed 8 Emeralds item from item pool
* enchanting required for wither; fishing rod required for water breathing; water breathing required for elder guardian kill
* Added 12 new advancements (ported from old achievement system)
* renamed "On a Rail" for consistency with modern advancements
* tests for the new advancements
* moved slot_data generation for minecraft into worlds/minecraft/__init__.py, added logic_version to slot_data
* output minecraft options in the spoiler log
* modified advancement goal values for new advancements
* make non-native Minecraft items appear as Shovel in ALttP, and unknown-game items as Power Stars
* fixed glowstone block logic for Not Quite Nine Lives
* setup for shuffling MC structures: building ER world and shuffling regions/entrances
* ensured Nether Fortresses can't be placed in the End
* finished logic for structure randomization
* fixed nonnative items always showing up as Hammers in ALttP shops
* output minecraft structure info in the spoiler
* generate .apmc file for communication with MC client
* fixed structure rando always using the same seed
* move stuff to worlds/minecraft/Regions.py
* make output apmc file have consistent name with other files
* added minecraft bottle macro; fixed tests imports
* generalizing MC region generation
* restructured structure shuffling in preparation for structure plando
* only output structure rando info in spoiler if they are shuffled
* Force structure rando to always be off, for the stable release
* added Minecraft options to player settings
* formally added combat_difficulty as an option
* Added Ender Dragon into playthrough, cleaned up goal map
* Added new difficulties: Easy, Normal, Hard combat
* moved .apmc generation time to prevent outputs on failed generation
* updated tests for new combat logic
* Fixed bug causing generation to fail; removed Nether Fortress event since it should no longer be needed with the fix
* moved all MC-specific functions into gen_minecraft
* renamed "logic_version" to "client_version"
* bug fixes
properly flagged event locations/items with id None
moved generation back to Main.py to fix mysterious generation failures
* moved link_minecraft_regions into minecraft init, left create_regions in Main for caching
* added seed_name, player_name, client_version to apmc file
* reenabled structure shuffle
* added entrance tests for minecraft
Co-authored-by: achuang <alexander.w.chuang@gmail.com>
											
										 
											2021-05-08 07:38:57 -04:00
										 |  |  |         create_playthrough(world) | 
					
						
							| 
									
										
										
										
											2020-08-21 18:35:48 +02:00
										 |  |  |     if args.create_spoiler:  # needs spoiler.hashes to be filled, that depend on rom_futures being done | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |         world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-21 18:35:48 +02:00
										 |  |  |     logger.info('Done. Enjoy. Total Time: %s', time.perf_counter() - start) | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  |     return world | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-22 23:18:40 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  | def create_playthrough(world): | 
					
						
							| 
									
										
										
										
											2021-04-08 19:53:24 +02:00
										 |  |  |     """Destructive to the world while it is run, damage gets repaired afterwards.""" | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |     # get locations containing progress items | 
					
						
							| 
									
										
										
										
											2021-02-27 17:11:54 +01:00
										 |  |  |     prog_locations = {location for location in world.get_filled_locations() if location.item.advancement} | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  |     state_cache = [None] | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |     collection_spheres = [] | 
					
						
							|  |  |  |     state = CollectionState(world) | 
					
						
							| 
									
										
										
										
											2021-02-27 17:11:54 +01:00
										 |  |  |     sphere_candidates = set(prog_locations) | 
					
						
							| 
									
										
										
										
											2020-08-14 00:34:41 +02:00
										 |  |  |     logging.debug('Building up collection spheres.') | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |     while sphere_candidates: | 
					
						
							| 
									
										
										
										
											2019-12-13 22:37:52 +01:00
										 |  |  |         state.sweep_for_events(key_only=True) | 
					
						
							| 
									
										
										
										
											2017-06-24 11:11:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |         # build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres | 
					
						
							| 
									
										
										
										
											2021-02-27 17:11:54 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         sphere = {location for location in sphere_candidates if state.can_reach(location)} | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         for location in sphere: | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  |             state.collect(location.item, True, location) | 
					
						
							| 
									
										
										
										
											2017-06-17 14:40:37 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-27 17:11:54 +01:00
										 |  |  |         sphere_candidates -= sphere | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |         collection_spheres.append(sphere) | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  |         state_cache.append(state.copy()) | 
					
						
							| 
									
										
										
										
											2017-05-26 09:55:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-14 00:34:41 +02:00
										 |  |  |         logging.debug('Calculated sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), | 
					
						
							|  |  |  |                       len(prog_locations)) | 
					
						
							| 
									
										
										
										
											2017-05-26 09:55:24 +02:00
										 |  |  |         if not sphere: | 
					
						
							| 
									
										
										
										
											2020-08-14 00:34:41 +02:00
										 |  |  |             logging.debug('The following items could not be reached: %s', ['%s (Player %d) at %s (Player %d)' % ( | 
					
						
							|  |  |  |                 location.item.name, location.item.player, location.name, location.player) for location in | 
					
						
							|  |  |  |                                                                            sphere_candidates]) | 
					
						
							| 
									
										
										
										
											2019-12-17 12:14:29 +01:00
										 |  |  |             if any([world.accessibility[location.item.player] != 'none' for location in sphere_candidates]): | 
					
						
							| 
									
										
										
										
											2020-08-14 00:34:41 +02:00
										 |  |  |                 raise RuntimeError(f'Not all progression items reachable ({sphere_candidates}). ' | 
					
						
							|  |  |  |                                    f'Something went terribly wrong here.') | 
					
						
							| 
									
										
										
										
											2017-06-23 22:15:29 +02:00
										 |  |  |             else: | 
					
						
							| 
									
										
										
										
											2021-03-17 10:53:40 +01:00
										 |  |  |                 world.spoiler.unreachables = sphere_candidates | 
					
						
							| 
									
										
										
										
											2017-06-23 22:15:29 +02:00
										 |  |  |                 break | 
					
						
							| 
									
										
										
										
											2021-03-17 11:46:44 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # in the second phase, we cull each sphere such that the game is still beatable, | 
					
						
							|  |  |  |     # reducing each range of influence to the bare minimum required inside it | 
					
						
							| 
									
										
										
										
											2021-03-17 10:53:40 +01:00
										 |  |  |     restore_later = {} | 
					
						
							| 
									
										
										
										
											2021-02-03 07:14:53 +01:00
										 |  |  |     for num, sphere in reversed(tuple(enumerate(collection_spheres))): | 
					
						
							| 
									
										
										
										
											2021-02-03 06:55:08 +01:00
										 |  |  |         to_delete = set() | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |         for location in sphere: | 
					
						
							|  |  |  |             # we remove the item at location and check if game is still beatable | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |             logging.debug('Checking if %s (Player %d) is required to beat the game.', location.item.name, | 
					
						
							|  |  |  |                           location.item.player) | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |             old_item = location.item | 
					
						
							|  |  |  |             location.item = None | 
					
						
							| 
									
										
										
										
											2019-07-11 00:12:09 -04:00
										 |  |  |             if world.can_beat_game(state_cache[num]): | 
					
						
							| 
									
										
										
										
											2021-02-03 06:55:08 +01:00
										 |  |  |                 to_delete.add(location) | 
					
						
							| 
									
										
										
										
											2021-03-17 10:53:40 +01:00
										 |  |  |                 restore_later[location] = old_item | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |             else: | 
					
						
							|  |  |  |                 # still required, got to keep it around | 
					
						
							|  |  |  |                 location.item = old_item | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # cull entries in spheres for spoiler walkthrough at end | 
					
						
							| 
									
										
										
										
											2021-02-03 06:55:08 +01:00
										 |  |  |         sphere -= to_delete | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-09 08:31:49 +01:00
										 |  |  |     # second phase, sphere 0 | 
					
						
							| 
									
										
										
										
											2021-03-17 11:46:44 +01:00
										 |  |  |     removed_precollected = [] | 
					
						
							| 
									
										
										
										
											2021-02-03 07:14:53 +01:00
										 |  |  |     for item in (i for i in world.precollected_items if i.advancement): | 
					
						
							| 
									
										
										
										
											2021-02-27 17:11:54 +01:00
										 |  |  |         logging.debug('Checking if %s (Player %d) is required to beat the game.', item.name, item.player) | 
					
						
							| 
									
										
										
										
											2020-01-09 08:31:49 +01:00
										 |  |  |         world.precollected_items.remove(item) | 
					
						
							|  |  |  |         world.state.remove(item) | 
					
						
							|  |  |  |         if not world.can_beat_game(): | 
					
						
							|  |  |  |             world.push_precollected(item) | 
					
						
							| 
									
										
										
										
											2021-03-17 11:46:44 +01:00
										 |  |  |         else: | 
					
						
							|  |  |  |             removed_precollected.append(item) | 
					
						
							| 
									
										
										
										
											2020-01-09 08:31:49 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-06 14:25:49 -05:00
										 |  |  |     # we are now down to just the required progress items in collection_spheres. Unfortunately | 
					
						
							|  |  |  |     # the previous pruning stage could potentially have made certain items dependant on others | 
					
						
							|  |  |  |     # in the same or later sphere (because the location had 2 ways to access but the item originally | 
					
						
							|  |  |  |     # used to access it was deemed not required.) So we need to do one final sphere collection pass | 
					
						
							|  |  |  |     # to build up the correct spheres | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-03 06:55:08 +01:00
										 |  |  |     required_locations = {item for sphere in collection_spheres for item in sphere} | 
					
						
							| 
									
										
										
										
											2018-01-06 14:25:49 -05:00
										 |  |  |     state = CollectionState(world) | 
					
						
							|  |  |  |     collection_spheres = [] | 
					
						
							|  |  |  |     while required_locations: | 
					
						
							| 
									
										
										
										
											2019-12-13 22:37:52 +01:00
										 |  |  |         state.sweep_for_events(key_only=True) | 
					
						
							| 
									
										
										
										
											2018-01-06 14:25:49 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-03 14:26:00 +01:00
										 |  |  |         sphere = set(filter(state.can_reach, required_locations)) | 
					
						
							| 
									
										
										
										
											2018-01-06 14:25:49 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |         for location in sphere: | 
					
						
							|  |  |  |             state.collect(location.item, True, location) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-27 17:11:54 +01:00
										 |  |  |         required_locations -= sphere | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-06 14:25:49 -05:00
										 |  |  |         collection_spheres.append(sphere) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |         logging.debug('Calculated final sphere %i, containing %i of %i progress items.', len(collection_spheres), | 
					
						
							|  |  |  |                       len(sphere), len(required_locations)) | 
					
						
							| 
									
										
										
										
											2018-01-06 14:25:49 -05:00
										 |  |  |         if not sphere: | 
					
						
							| 
									
										
										
										
											2021-02-25 02:07:28 +01:00
										 |  |  |             raise RuntimeError(f'Not all required items reachable. Unreachable locations: {required_locations}') | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  |     def flist_to_iter(node): | 
					
						
							|  |  |  |         while node: | 
					
						
							|  |  |  |             value, node = node | 
					
						
							|  |  |  |             yield value | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-06 16:25:14 -05:00
										 |  |  |     def get_path(state, region): | 
					
						
							|  |  |  |         reversed_path_as_flist = state.path.get(region, (region, None)) | 
					
						
							|  |  |  |         string_path_flat = reversed(list(map(str, flist_to_iter(reversed_path_as_flist)))) | 
					
						
							|  |  |  |         # Now we combine the flat string list into (region, exit) pairs | 
					
						
							|  |  |  |         pathsiter = iter(string_path_flat) | 
					
						
							|  |  |  |         pathpairs = zip_longest(pathsiter, pathsiter) | 
					
						
							|  |  |  |         return list(pathpairs) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-17 10:53:40 +01:00
										 |  |  |     world.spoiler.paths = dict() | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  |     for player in range(1, world.players + 1): | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |         world.spoiler.paths.update( | 
					
						
							|  |  |  |             {str(location): get_path(state, location.parent_region) for sphere in collection_spheres for location in | 
					
						
							|  |  |  |              sphere if location.player == player}) | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |         if player in world.alttp_player_ids: | 
					
						
							| 
									
										
										
										
											2021-03-17 10:53:40 +01:00
										 |  |  |             for path in dict(world.spoiler.paths).values(): | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |                 if any(exit == 'Pyramid Fairy' for (_, exit) in path): | 
					
						
							|  |  |  |                     if world.mode[player] != 'inverted': | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |                         world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = get_path(state, | 
					
						
							|  |  |  |                                                                                                        world.get_region( | 
					
						
							|  |  |  |                                                                                                            'Big Bomb Shop', | 
					
						
							|  |  |  |                                                                                                            player)) | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |                     else: | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |                         world.spoiler.paths[str(world.get_region('Inverted Big Bomb Shop', player))] = get_path(state, | 
					
						
							|  |  |  |                                                                                                                 world.get_region( | 
					
						
							|  |  |  |                                                                                                                     'Inverted Big Bomb Shop', | 
					
						
							|  |  |  |                                                                                                                     player)) | 
					
						
							| 
									
										
										
										
											2018-01-01 15:55:13 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-16 21:23:47 +02:00
										 |  |  |     # we can finally output our playthrough | 
					
						
							| 
									
										
										
										
											2021-03-17 10:53:40 +01:00
										 |  |  |     world.spoiler.playthrough = {"0": sorted([str(item) for item in world.precollected_items if item.advancement])} | 
					
						
							| 
									
										
										
										
											2021-02-03 14:26:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-09 08:31:49 +01:00
										 |  |  |     for i, sphere in enumerate(collection_spheres): | 
					
						
							| 
									
										
										
										
											2021-03-17 10:53:40 +01:00
										 |  |  |         world.spoiler.playthrough[str(i + 1)] = {str(location): str(location.item) for location in sorted(sphere)} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # repair the world again | 
					
						
							|  |  |  |     for location, item in restore_later.items(): | 
					
						
							|  |  |  |         location.item = item | 
					
						
							| 
									
										
										
										
											2021-03-17 11:46:44 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     for item in removed_precollected: | 
					
						
							|  |  |  |         world.push_precollected(item) |