| 
									
										
										
										
											2022-02-05 15:49:19 +01:00
										 |  |  | import collections | 
					
						
							| 
									
										
										
										
											2023-04-10 20:18:29 -05:00
										 |  |  | import concurrent.futures | 
					
						
							| 
									
										
										
										
											2017-12-17 00:25:46 -05:00
										 |  |  | import logging | 
					
						
							| 
									
										
										
										
											2019-05-30 01:10:16 +02:00
										 |  |  | import os | 
					
						
							| 
									
										
										
										
											2021-01-03 14:32:32 +01:00
										 |  |  | import pickle | 
					
						
							| 
									
										
										
										
											2021-07-21 18:08:15 +02:00
										 |  |  | import tempfile | 
					
						
							| 
									
										
										
										
											2023-04-10 20:18:29 -05:00
										 |  |  | import time | 
					
						
							| 
									
										
										
										
											2021-07-21 18:08:15 +02:00
										 |  |  | import zipfile | 
					
						
							| 
									
										
										
										
											2023-04-10 20:18:29 -05:00
										 |  |  | import zlib | 
					
						
							| 
									
										
										
										
											2023-07-02 13:00:05 +02:00
										 |  |  | from typing import Dict, List, Optional, Set, Tuple, Union | 
					
						
							| 
									
										
										
										
											2017-12-17 00:25:46 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-08 21:23:31 +01:00
										 |  |  | import worlds | 
					
						
							| 
									
										
										
										
											2023-04-10 20:18:29 -05:00
										 |  |  | from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld, Region | 
					
						
							| 
									
										
										
										
											2024-08-13 17:17:42 -05:00
										 |  |  | from Fill import FillError, balance_multiworld_progression, distribute_items_restrictive, distribute_planned, \ | 
					
						
							|  |  |  |     flood_items | 
					
						
							| 
									
										
										
										
											2023-04-10 20:18:29 -05:00
										 |  |  | from Options import StartInventoryPool | 
					
						
							| 
									
										
										
										
											2024-05-22 14:02:18 +02:00
										 |  |  | from Utils import __version__, output_path, version_tuple, get_settings | 
					
						
							| 
									
										
										
										
											2023-11-23 16:03:56 -06:00
										 |  |  | from settings import get_settings | 
					
						
							| 
									
										
										
										
											2023-04-10 20:18:29 -05:00
										 |  |  | from worlds import AutoWorld | 
					
						
							|  |  |  | from worlds.generic.Rules import exclusion_rules, locality_rules | 
					
						
							| 
									
										
										
										
											2017-05-15 20:28:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 19:21:39 +02:00
										 |  |  | __all__ = ["main"] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-11 00:46:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = None): | 
					
						
							|  |  |  |     if not baked_server_options: | 
					
						
							| 
									
										
										
										
											2023-07-15 13:52:52 -07:00
										 |  |  |         baked_server_options = get_settings().server_options.as_dict() | 
					
						
							|  |  |  |     assert isinstance(baked_server_options, dict) | 
					
						
							| 
									
										
										
										
											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() | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     # initialize the multiworld | 
					
						
							|  |  |  |     multiworld = MultiWorld(args.multi) | 
					
						
							| 
									
										
										
										
											2020-07-14 07:01:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-06 11:32:49 +02:00
										 |  |  |     logger = logging.getLogger() | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     multiworld.set_seed(seed, args.race, str(args.outputname) if args.outputname else None) | 
					
						
							|  |  |  |     multiworld.plando_options = args.plando_options | 
					
						
							|  |  |  |     multiworld.plando_items = args.plando_items.copy() | 
					
						
							|  |  |  |     multiworld.plando_texts = args.plando_texts.copy() | 
					
						
							|  |  |  |     multiworld.plando_connections = args.plando_connections.copy() | 
					
						
							|  |  |  |     multiworld.game = args.game.copy() | 
					
						
							|  |  |  |     multiworld.player_name = args.name.copy() | 
					
						
							|  |  |  |     multiworld.sprite = args.sprite.copy() | 
					
						
							| 
									
										
										
										
											2024-04-18 18:33:16 +02:00
										 |  |  |     multiworld.sprite_pool = args.sprite_pool.copy() | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     multiworld.set_options(args) | 
					
						
							| 
									
										
										
										
											2024-09-18 04:37:10 +02:00
										 |  |  |     if args.csv_output: | 
					
						
							| 
									
										
										
										
											2024-09-17 18:33:03 -05:00
										 |  |  |         from Options import dump_player_options | 
					
						
							|  |  |  |         dump_player_options(multiworld) | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     multiworld.set_item_links() | 
					
						
							|  |  |  |     multiworld.state = CollectionState(multiworld) | 
					
						
							|  |  |  |     logger.info('Archipelago Version %s  -  Seed: %s\n', __version__, multiworld.seed) | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-28 11:23:42 +01:00
										 |  |  |     logger.info(f"Found {len(AutoWorld.AutoWorldRegister.world_types)} World Types:") | 
					
						
							| 
									
										
										
										
											2021-07-12 15:11:48 +02:00
										 |  |  |     longest_name = max(len(text) for text in AutoWorld.AutoWorldRegister.world_types) | 
					
						
							| 
									
										
										
										
											2022-10-27 09:18:25 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     max_item = 0 | 
					
						
							|  |  |  |     max_location = 0 | 
					
						
							|  |  |  |     for cls in AutoWorld.AutoWorldRegister.world_types.values(): | 
					
						
							|  |  |  |         if cls.item_id_to_name: | 
					
						
							|  |  |  |             max_item = max(max_item, max(cls.item_id_to_name)) | 
					
						
							|  |  |  |             max_location = max(max_location, max(cls.location_id_to_name)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     item_digits = len(str(max_item)) | 
					
						
							|  |  |  |     location_digits = len(str(max_location)) | 
					
						
							|  |  |  |     item_count = len(str(max(len(cls.item_names) for cls in AutoWorld.AutoWorldRegister.world_types.values()))) | 
					
						
							|  |  |  |     location_count = len(str(max(len(cls.location_names) for cls in AutoWorld.AutoWorldRegister.world_types.values()))) | 
					
						
							|  |  |  |     del max_item, max_location | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |     for name, cls in AutoWorld.AutoWorldRegister.world_types.items(): | 
					
						
							| 
									
										
										
										
											2022-10-13 07:55:00 +02:00
										 |  |  |         if not cls.hidden and len(cls.item_names) > 0: | 
					
						
							| 
									
										
										
										
											2022-10-27 09:18:25 +02:00
										 |  |  |             logger.info(f" {name:{longest_name}}: {len(cls.item_names):{item_count}} " | 
					
						
							|  |  |  |                         f"Items (IDs: {min(cls.item_id_to_name):{item_digits}} - " | 
					
						
							|  |  |  |                         f"{max(cls.item_id_to_name):{item_digits}}) | " | 
					
						
							|  |  |  |                         f"{len(cls.location_names):{location_count}} " | 
					
						
							|  |  |  |                         f"Locations (IDs: {min(cls.location_id_to_name):{location_digits}} - " | 
					
						
							|  |  |  |                         f"{max(cls.location_id_to_name):{location_digits}})") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     del item_digits, location_digits, item_count, location_count | 
					
						
							| 
									
										
										
										
											2017-05-20 14:07:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-23 16:03:56 -06:00
										 |  |  |     # This assertion method should not be necessary to run if we are not outputting any multidata. | 
					
						
							|  |  |  |     if not args.skip_output: | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |         AutoWorld.call_stage(multiworld, "assert_generate") | 
					
						
							| 
									
										
										
										
											2022-04-29 20:37:28 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     AutoWorld.call_all(multiworld, "generate_early") | 
					
						
							| 
									
										
										
										
											2021-08-28 00:26:02 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |     logger.info('') | 
					
						
							| 
									
										
										
										
											2021-05-22 06:27:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     for player in multiworld.player_ids: | 
					
						
							|  |  |  |         for item_name, count in multiworld.worlds[player].options.start_inventory.value.items(): | 
					
						
							| 
									
										
										
										
											2021-09-23 03:53:16 +02:00
										 |  |  |             for _ in range(count): | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                 multiworld.push_precollected(multiworld.create_item(item_name, player)) | 
					
						
							| 
									
										
										
										
											2023-04-10 20:18:29 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |         for item_name, count in getattr(multiworld.worlds[player].options, | 
					
						
							| 
									
										
										
										
											2024-01-13 19:57:53 -06:00
										 |  |  |                                         "start_inventory_from_pool", | 
					
						
							|  |  |  |                                         StartInventoryPool({})).value.items(): | 
					
						
							| 
									
										
										
										
											2023-04-10 21:13:33 +02:00
										 |  |  |             for _ in range(count): | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                 multiworld.push_precollected(multiworld.create_item(item_name, player)) | 
					
						
							| 
									
										
										
										
											2023-12-10 20:42:41 +01:00
										 |  |  |             # remove from_pool items also from early items handling, as starting is plenty early. | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |             early = multiworld.early_items[player].get(item_name, 0) | 
					
						
							| 
									
										
										
										
											2023-12-10 20:42:41 +01:00
										 |  |  |             if early: | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                 multiworld.early_items[player][item_name] = max(0, early-count) | 
					
						
							| 
									
										
										
										
											2023-12-10 20:42:41 +01:00
										 |  |  |                 remaining_count = count-early | 
					
						
							|  |  |  |                 if remaining_count > 0: | 
					
						
							| 
									
										
										
										
											2024-08-16 16:20:20 -04:00
										 |  |  |                     local_early = multiworld.local_early_items[player].get(item_name, 0) | 
					
						
							| 
									
										
										
										
											2023-12-10 20:42:41 +01:00
										 |  |  |                     if local_early: | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                         multiworld.early_items[player][item_name] = max(0, local_early - remaining_count) | 
					
						
							| 
									
										
										
										
											2023-12-10 20:42:41 +01:00
										 |  |  |                     del local_early | 
					
						
							|  |  |  |             del early | 
					
						
							| 
									
										
										
										
											2017-05-20 14:07:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     logger.info('Creating MultiWorld.') | 
					
						
							|  |  |  |     AutoWorld.call_all(multiworld, "create_regions") | 
					
						
							| 
									
										
										
										
											2021-04-01 11:40:58 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 15:51:50 +02:00
										 |  |  |     logger.info('Creating Items.') | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     AutoWorld.call_all(multiworld, "create_items") | 
					
						
							| 
									
										
										
										
											2019-04-18 16:11:11 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-20 14:07:40 +02:00
										 |  |  |     logger.info('Calculating Access Rules.') | 
					
						
							| 
									
										
										
										
											2022-11-28 07:03:09 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     for player in multiworld.player_ids: | 
					
						
							| 
									
										
										
										
											2022-11-28 07:03:09 +01:00
										 |  |  |         # items can't be both local and non-local, prefer local | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |         multiworld.worlds[player].options.non_local_items.value -= multiworld.worlds[player].options.local_items.value | 
					
						
							|  |  |  |         multiworld.worlds[player].options.non_local_items.value -= set(multiworld.local_early_items[player]) | 
					
						
							| 
									
										
										
										
											2022-11-28 07:03:09 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     AutoWorld.call_all(multiworld, "set_rules") | 
					
						
							| 
									
										
										
										
											2021-06-11 18:02:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     for player in multiworld.player_ids: | 
					
						
							|  |  |  |         exclusion_rules(multiworld, player, multiworld.worlds[player].options.exclude_locations.value) | 
					
						
							|  |  |  |         multiworld.worlds[player].options.priority_locations.value -= multiworld.worlds[player].options.exclude_locations.value | 
					
						
							| 
									
										
										
										
											2024-07-26 11:51:55 -04:00
										 |  |  |         world_excluded_locations = set() | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |         for location_name in multiworld.worlds[player].options.priority_locations.value: | 
					
						
							| 
									
										
										
										
											2023-09-20 02:22:01 -07:00
										 |  |  |             try: | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                 location = multiworld.get_location(location_name, player) | 
					
						
							| 
									
										
										
										
											2024-07-26 11:51:55 -04:00
										 |  |  |             except KeyError: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if location.progress_type != LocationProgressType.EXCLUDED: | 
					
						
							| 
									
										
										
										
											2023-09-20 02:22:01 -07:00
										 |  |  |                 location.progress_type = LocationProgressType.PRIORITY | 
					
						
							| 
									
										
										
										
											2024-07-26 11:51:55 -04:00
										 |  |  |             else: | 
					
						
							|  |  |  |                 logger.warning(f"Unable to prioritize location \"{location_name}\" in player {player}'s world because the world excluded it.") | 
					
						
							|  |  |  |                 world_excluded_locations.add(location_name) | 
					
						
							|  |  |  |         multiworld.worlds[player].options.priority_locations.value -= world_excluded_locations | 
					
						
							| 
									
										
										
										
											2021-07-14 08:24:34 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-28 21:06:43 -05:00
										 |  |  |     # Set local and non-local item rules. | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     if multiworld.players > 1: | 
					
						
							|  |  |  |         locality_rules(multiworld) | 
					
						
							| 
									
										
										
										
											2023-07-28 21:06:43 -05:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |         multiworld.worlds[1].options.non_local_items.value = set() | 
					
						
							|  |  |  |         multiworld.worlds[1].options.local_items.value = set() | 
					
						
							| 
									
										
										
										
											2023-07-28 21:06:43 -05:00
										 |  |  |      | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     AutoWorld.call_all(multiworld, "generate_basic") | 
					
						
							| 
									
										
										
										
											2021-04-01 11:40:58 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:13:33 +02:00
										 |  |  |     # remove starting inventory from pool items. | 
					
						
							|  |  |  |     # Because some worlds don't actually create items during create_items this has to be as late as possible. | 
					
						
							| 
									
										
										
										
											2024-11-29 22:43:01 +01:00
										 |  |  |     fallback_inventory = StartInventoryPool({}) | 
					
						
							|  |  |  |     depletion_pool: Dict[int, Dict[str, int]] = { | 
					
						
							|  |  |  |         player: getattr(multiworld.worlds[player].options, "start_inventory_from_pool", fallback_inventory).value.copy() | 
					
						
							|  |  |  |         for player in multiworld.player_ids | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     target_per_player = { | 
					
						
							|  |  |  |         player: sum(target_items.values()) for player, target_items in depletion_pool.items() if target_items | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if target_per_player: | 
					
						
							|  |  |  |         new_itempool: List[Item] = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Make new itempool with start_inventory_from_pool items removed | 
					
						
							|  |  |  |         for item in multiworld.itempool: | 
					
						
							| 
									
										
										
										
											2023-04-10 21:13:33 +02:00
										 |  |  |             if depletion_pool[item.player].get(item.name, 0): | 
					
						
							|  |  |  |                 depletion_pool[item.player][item.name] -= 1 | 
					
						
							|  |  |  |             else: | 
					
						
							| 
									
										
										
										
											2024-11-29 22:43:01 +01:00
										 |  |  |                 new_itempool.append(item) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Create filler in place of the removed items, warn if any items couldn't be found in the multiworld itempool | 
					
						
							|  |  |  |         for player, target in target_per_player.items(): | 
					
						
							|  |  |  |             unfound_items = {item: count for item, count in depletion_pool[player].items() if count} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if unfound_items: | 
					
						
							|  |  |  |                 player_name = multiworld.get_player_name(player) | 
					
						
							|  |  |  |                 logger.warning(f"{player_name} tried to remove items from their pool that don't exist: {unfound_items}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             needed_items = target_per_player[player] - sum(unfound_items.values()) | 
					
						
							|  |  |  |             new_itempool += [multiworld.worlds[player].create_filler() for _ in range(needed_items)] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         assert len(multiworld.itempool) == len(new_itempool), "Item Pool amounts should not change." | 
					
						
							|  |  |  |         multiworld.itempool[:] = new_itempool | 
					
						
							| 
									
										
										
										
											2023-04-10 21:13:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-31 05:04:21 -05:00
										 |  |  |     multiworld.link_items() | 
					
						
							| 
									
										
										
										
											2022-03-20 11:07:51 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     if any(multiworld.item_links.values()): | 
					
						
							|  |  |  |         multiworld._all_state = None | 
					
						
							| 
									
										
										
										
											2022-02-05 15:49:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-05 06:15:39 +01:00
										 |  |  |     logger.info("Running Item Plando.") | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     distribute_planned(multiworld) | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-09 06:50:11 +02:00
										 |  |  |     logger.info('Running Pre Main Fill.') | 
					
						
							| 
									
										
										
										
											2021-01-24 08:26:39 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     AutoWorld.call_all(multiworld, "pre_fill") | 
					
						
							| 
									
										
										
										
											2017-05-20 14:07:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     logger.info(f'Filling the multiworld with {len(multiworld.itempool)} items.') | 
					
						
							| 
									
										
										
										
											2017-05-20 14:07:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     if multiworld.algorithm == 'flood': | 
					
						
							|  |  |  |         flood_items(multiworld)  # different algo, biased towards early game progress items | 
					
						
							|  |  |  |     elif multiworld.algorithm == 'balanced': | 
					
						
							| 
									
										
										
										
											2024-05-22 14:02:18 +02:00
										 |  |  |         distribute_items_restrictive(multiworld, get_settings().generator.panic_method) | 
					
						
							| 
									
										
										
										
											2017-06-03 21:28:02 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     AutoWorld.call_all(multiworld, 'post_fill') | 
					
						
							| 
									
										
										
										
											2020-12-23 15:30:21 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     if multiworld.players > 1 and not args.skip_prog_balancing: | 
					
						
							|  |  |  |         balance_multiworld_progression(multiworld) | 
					
						
							| 
									
										
										
										
											2023-06-26 16:14:01 -05:00
										 |  |  |     else: | 
					
						
							|  |  |  |         logger.info("Progression balancing skipped.") | 
					
						
							| 
									
										
										
										
											2021-02-05 08:07:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 01:14:23 +01:00
										 |  |  |     # we're about to output using multithreading, so we're removing the global random state to prevent accidental use | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     multiworld.random.passthrough = False | 
					
						
							| 
									
										
										
										
											2023-02-02 01:14:23 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-23 16:03:56 -06:00
										 |  |  |     if args.skip_output: | 
					
						
							|  |  |  |         logger.info('Done. Skipped output/spoiler generation. Total Time: %s', time.perf_counter() - start) | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |         return multiworld | 
					
						
							| 
									
										
										
										
											2023-11-23 16:03:56 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     logger.info(f'Beginning output...') | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     outfilebase = 'AP_' + multiworld.seed_name | 
					
						
							| 
									
										
										
										
											2020-03-06 23:08:46 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-21 18:08:15 +02:00
										 |  |  |     output = tempfile.TemporaryDirectory() | 
					
						
							|  |  |  |     with output as temp_dir: | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |         output_players = [player for player in multiworld.player_ids if AutoWorld.World.generate_output.__code__ | 
					
						
							|  |  |  |                           is not multiworld.worlds[player].generate_output.__code__] | 
					
						
							| 
									
										
										
										
											2023-10-20 05:14:12 +02:00
										 |  |  |         with concurrent.futures.ThreadPoolExecutor(len(output_players) + 2) as pool: | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |             check_accessibility_task = pool.submit(multiworld.fulfills_accessibility) | 
					
						
							| 
									
										
										
										
											2021-09-03 20:35:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |             output_file_futures = [pool.submit(AutoWorld.call_stage, multiworld, "generate_output", temp_dir)] | 
					
						
							| 
									
										
										
										
											2023-10-20 05:14:12 +02:00
										 |  |  |             for player in output_players: | 
					
						
							| 
									
										
										
										
											2021-09-03 20:35:40 +02:00
										 |  |  |                 # skip starting a thread for methods that say "pass". | 
					
						
							| 
									
										
										
										
											2023-10-20 05:14:12 +02:00
										 |  |  |                 output_file_futures.append( | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                     pool.submit(AutoWorld.call_single, multiworld, "generate_output", player, temp_dir)) | 
					
						
							| 
									
										
										
										
											2021-09-03 20:35:40 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # collect ER hint info | 
					
						
							| 
									
										
										
										
											2022-09-18 14:30:43 +02:00
										 |  |  |             er_hint_data: Dict[int, Dict[int, str]] = {} | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |             AutoWorld.call_all(multiworld, 'extend_hint_information', er_hint_data) | 
					
						
							| 
									
										
										
										
											2021-09-03 20:35:40 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             def write_multidata(): | 
					
						
							|  |  |  |                 import NetUtils | 
					
						
							| 
									
										
										
										
											2024-12-01 09:16:36 -05:00
										 |  |  |                 from NetUtils import HintStatus | 
					
						
							| 
									
										
										
										
											2021-09-03 20:35:40 +02:00
										 |  |  |                 slot_data = {} | 
					
						
							|  |  |  |                 client_versions = {} | 
					
						
							|  |  |  |                 games = {} | 
					
						
							| 
									
										
										
										
											2022-04-08 11:16:36 +02:00
										 |  |  |                 minimum_versions = {"server": AutoWorld.World.required_server_version, "clients": client_versions} | 
					
						
							| 
									
										
										
										
											2022-01-30 13:57:12 +01:00
										 |  |  |                 slot_info = {} | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                 names = [[name for player, name in sorted(multiworld.player_name.items())]] | 
					
						
							|  |  |  |                 for slot in multiworld.player_ids: | 
					
						
							|  |  |  |                     player_world: AutoWorld.World = multiworld.worlds[slot] | 
					
						
							| 
									
										
										
										
											2022-04-08 11:16:36 +02:00
										 |  |  |                     minimum_versions["server"] = max(minimum_versions["server"], player_world.required_server_version) | 
					
						
							|  |  |  |                     client_versions[slot] = player_world.required_client_version | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                     games[slot] = multiworld.game[slot] | 
					
						
							|  |  |  |                     slot_info[slot] = NetUtils.NetworkSlot(names[0][slot - 1], multiworld.game[slot], | 
					
						
							|  |  |  |                                                            multiworld.player_types[slot]) | 
					
						
							|  |  |  |                 for slot, group in multiworld.groups.items(): | 
					
						
							|  |  |  |                     games[slot] = multiworld.game[slot] | 
					
						
							|  |  |  |                     slot_info[slot] = NetUtils.NetworkSlot(group["name"], multiworld.game[slot], multiworld.player_types[slot], | 
					
						
							| 
									
										
										
										
											2022-02-05 15:49:19 +01:00
										 |  |  |                                                            group_members=sorted(group["players"])) | 
					
						
							| 
									
										
										
										
											2022-03-18 18:19:21 +01:00
										 |  |  |                 precollected_items = {player: [item.code for item in world_precollected if type(item.code) == int] | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                                       for player, world_precollected in multiworld.precollected_items.items()} | 
					
						
							|  |  |  |                 precollected_hints = {player: set() for player in range(1, multiworld.players + 1 + len(multiworld.groups))} | 
					
						
							| 
									
										
										
										
											2022-01-14 19:27:44 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                 for slot in multiworld.player_ids: | 
					
						
							|  |  |  |                     slot_data[slot] = multiworld.worlds[slot].fill_slot_data() | 
					
						
							| 
									
										
										
										
											2021-09-03 20:35:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-01 09:16:36 -05:00
										 |  |  |                 def precollect_hint(location: Location, auto_status: HintStatus): | 
					
						
							| 
									
										
										
										
											2022-02-19 10:52:05 -08:00
										 |  |  |                     entrance = er_hint_data.get(location.player, {}).get(location.address, "") | 
					
						
							| 
									
										
										
										
											2021-10-03 14:40:25 +02:00
										 |  |  |                     hint = NetUtils.Hint(location.item.player, location.player, location.address, | 
					
						
							| 
									
										
										
										
											2024-12-01 09:16:36 -05:00
										 |  |  |                                          location.item.code, False, entrance, location.item.flags, auto_status) | 
					
						
							| 
									
										
										
										
											2021-10-03 14:40:25 +02:00
										 |  |  |                     precollected_hints[location.player].add(hint) | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                     if location.item.player not in multiworld.groups: | 
					
						
							| 
									
										
										
										
											2022-02-21 15:33:39 -08:00
										 |  |  |                         precollected_hints[location.item.player].add(hint) | 
					
						
							|  |  |  |                     else: | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                         for player in multiworld.groups[location.item.player]["players"]: | 
					
						
							| 
									
										
										
										
											2022-02-21 15:33:39 -08:00
										 |  |  |                             precollected_hints[player].add(hint) | 
					
						
							| 
									
										
										
										
											2021-10-03 14:40:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                 locations_data: Dict[int, Dict[int, Tuple[int, int, int]]] = {player: {} for player in multiworld.player_ids} | 
					
						
							|  |  |  |                 for location in multiworld.get_filled_locations(): | 
					
						
							| 
									
										
										
										
											2021-09-03 20:35:40 +02:00
										 |  |  |                     if type(location.address) == int: | 
					
						
							| 
									
										
										
										
											2022-03-26 01:12:54 +01:00
										 |  |  |                         assert location.item.code is not None, "item code None should be event, " \ | 
					
						
							| 
									
										
										
										
											2022-07-15 17:41:53 +02:00
										 |  |  |                                                                "location.address should then also be None. Location: " \ | 
					
						
							| 
									
										
										
										
											2024-11-29 18:01:24 -05:00
										 |  |  |                                                                f" {location}, Item: {location.item}" | 
					
						
							| 
									
										
										
										
											2023-11-15 20:50:00 +01:00
										 |  |  |                         assert location.address not in locations_data[location.player], ( | 
					
						
							|  |  |  |                             f"Locations with duplicate address. {location} and " | 
					
						
							|  |  |  |                             f"{locations_data[location.player][location.address]}") | 
					
						
							| 
									
										
										
										
											2022-01-18 05:52:29 +01:00
										 |  |  |                         locations_data[location.player][location.address] = \ | 
					
						
							| 
									
										
										
										
											2022-01-18 06:16:16 +01:00
										 |  |  |                             location.item.code, location.item.player, location.item.flags | 
					
						
							| 
									
										
										
										
											2024-12-01 09:16:36 -05:00
										 |  |  |                         auto_status = HintStatus.HINT_AVOID if location.item.trap else HintStatus.HINT_PRIORITY | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                         if location.name in multiworld.worlds[location.player].options.start_location_hints: | 
					
						
							| 
									
										
										
										
											2024-12-01 09:16:36 -05:00
										 |  |  |                             if not location.item.trap:  # Unspecified status for location hints, except traps | 
					
						
							|  |  |  |                                 auto_status = HintStatus.HINT_UNSPECIFIED | 
					
						
							|  |  |  |                             precollect_hint(location, auto_status) | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                         elif location.item.name in multiworld.worlds[location.item.player].options.start_hints: | 
					
						
							| 
									
										
										
										
											2024-12-01 09:16:36 -05:00
										 |  |  |                             precollect_hint(location, auto_status) | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                         elif any([location.item.name in multiworld.worlds[player].options.start_hints | 
					
						
							|  |  |  |                                   for player in multiworld.groups.get(location.item.player, {}).get("players", [])]): | 
					
						
							| 
									
										
										
										
											2024-12-01 09:16:36 -05:00
										 |  |  |                             precollect_hint(location, auto_status) | 
					
						
							| 
									
										
										
										
											2021-09-03 20:35:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  |                 # embedded data package | 
					
						
							|  |  |  |                 data_package = { | 
					
						
							|  |  |  |                     game_world.game: worlds.network_data_package["games"][game_world.game] | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                     for game_world in multiworld.worlds.values() | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-12-08 21:23:31 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-02 13:00:05 +02:00
										 |  |  |                 checks_in_area: Dict[int, Dict[str, Union[int, List[int]]]] = {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-27 18:43:25 +02:00
										 |  |  |                 # get spheres -> filter address==None -> skip empty | 
					
						
							|  |  |  |                 spheres: List[Dict[int, Set[int]]] = [] | 
					
						
							| 
									
										
										
										
											2024-11-30 04:11:03 +01:00
										 |  |  |                 for sphere in multiworld.get_sendable_spheres(): | 
					
						
							| 
									
										
										
										
											2024-05-27 18:43:25 +02:00
										 |  |  |                     current_sphere: Dict[int, Set[int]] = collections.defaultdict(set) | 
					
						
							|  |  |  |                     for sphere_location in sphere: | 
					
						
							| 
									
										
										
										
											2024-11-30 04:11:03 +01:00
										 |  |  |                         current_sphere[sphere_location.player].add(sphere_location.address) | 
					
						
							| 
									
										
										
										
											2024-05-27 18:43:25 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                     if current_sphere: | 
					
						
							|  |  |  |                         spheres.append(dict(current_sphere)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-03 20:35:40 +02:00
										 |  |  |                 multidata = { | 
					
						
							|  |  |  |                     "slot_data": slot_data, | 
					
						
							| 
									
										
										
										
											2022-01-30 13:57:12 +01:00
										 |  |  |                     "slot_info": slot_info, | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                     "connect_names": {name: (0, player) for player, name in multiworld.player_name.items()}, | 
					
						
							| 
									
										
										
										
											2021-09-03 20:35:40 +02:00
										 |  |  |                     "locations": locations_data, | 
					
						
							|  |  |  |                     "checks_in_area": checks_in_area, | 
					
						
							| 
									
										
										
										
											2023-07-15 13:52:52 -07:00
										 |  |  |                     "server_options": baked_server_options, | 
					
						
							| 
									
										
										
										
											2021-09-03 20:35:40 +02:00
										 |  |  |                     "er_hint_data": er_hint_data, | 
					
						
							|  |  |  |                     "precollected_items": precollected_items, | 
					
						
							|  |  |  |                     "precollected_hints": precollected_hints, | 
					
						
							|  |  |  |                     "version": tuple(version_tuple), | 
					
						
							|  |  |  |                     "tags": ["AP"], | 
					
						
							|  |  |  |                     "minimum_versions": minimum_versions, | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                     "seed_name": multiworld.seed_name, | 
					
						
							| 
									
										
										
										
											2024-05-27 18:43:25 +02:00
										 |  |  |                     "spheres": spheres, | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  |                     "datapackage": data_package, | 
					
						
							| 
									
										
										
										
											2024-10-01 14:08:13 -05:00
										 |  |  |                     "race_mode": int(multiworld.is_race), | 
					
						
							| 
									
										
										
										
											2021-09-03 20:35:40 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                 AutoWorld.call_all(multiworld, "modify_multidata", multidata) | 
					
						
							| 
									
										
										
										
											2021-09-03 20:35:40 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 multidata = zlib.compress(pickle.dumps(multidata), 9) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 with open(os.path.join(temp_dir, f'{outfilebase}.archipelago'), 'wb') as f: | 
					
						
							| 
									
										
										
										
											2022-02-02 16:29:29 +01:00
										 |  |  |                     f.write(bytes([3]))  # version of format | 
					
						
							| 
									
										
										
										
											2021-09-03 20:35:40 +02:00
										 |  |  |                     f.write(multidata) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-09 05:02:53 +02:00
										 |  |  |             output_file_futures.append(pool.submit(write_multidata)) | 
					
						
							| 
									
										
										
										
											2021-09-03 20:35:40 +02:00
										 |  |  |             if not check_accessibility_task.result(): | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |                 if not multiworld.can_beat_game(): | 
					
						
							| 
									
										
										
										
											2024-08-13 17:17:42 -05:00
										 |  |  |                     raise FillError("Game appears as unbeatable. Aborting.", multiworld=multiworld) | 
					
						
							| 
									
										
										
										
											2021-09-03 20:35:40 +02:00
										 |  |  |                 else: | 
					
						
							|  |  |  |                     logger.warning("Location Accessibility requirements not fulfilled.") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-05 15:49:19 +01:00
										 |  |  |             # retrieve exceptions via .result() if they occurred. | 
					
						
							| 
									
										
										
										
											2021-09-13 03:38:18 +02:00
										 |  |  |             for i, future in enumerate(concurrent.futures.as_completed(output_file_futures), start=1): | 
					
						
							|  |  |  |                 if i % 10 == 0 or i == len(output_file_futures): | 
					
						
							| 
									
										
										
										
											2021-09-03 20:35:40 +02:00
										 |  |  |                     logger.info(f'Generating output files ({i}/{len(output_file_futures)}).') | 
					
						
							|  |  |  |                 future.result() | 
					
						
							| 
									
										
										
										
											2021-08-27 14:52:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-13 01:32:32 +02:00
										 |  |  |         if args.spoiler > 1: | 
					
						
							| 
									
										
										
										
											2021-07-21 18:08:15 +02:00
										 |  |  |             logger.info('Calculating playthrough.') | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |             multiworld.spoiler.create_playthrough(create_paths=args.spoiler > 2) | 
					
						
							| 
									
										
										
										
											2021-08-27 14:52:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-13 01:32:32 +02:00
										 |  |  |         if args.spoiler: | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |             multiworld.spoiler.to_file(os.path.join(temp_dir, '%s_Spoiler.txt' % outfilebase)) | 
					
						
							| 
									
										
										
										
											2021-08-27 14:52:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |         zipfilename = output_path(f"AP_{multiworld.seed_name}.zip") | 
					
						
							| 
									
										
										
										
											2022-08-11 01:02:06 +02:00
										 |  |  |         logger.info(f"Creating final archive at {zipfilename}") | 
					
						
							| 
									
										
										
										
											2021-07-25 16:15:51 +02:00
										 |  |  |         with zipfile.ZipFile(zipfilename, mode="w", compression=zipfile.ZIP_DEFLATED, | 
					
						
							| 
									
										
										
										
											2021-07-21 18:08:15 +02:00
										 |  |  |                              compresslevel=9) as zf: | 
					
						
							|  |  |  |             for file in os.scandir(temp_dir): | 
					
						
							| 
									
										
										
										
											2021-08-27 14:52:33 +02:00
										 |  |  |                 zf.write(file.path, arcname=file.name) | 
					
						
							| 
									
										
										
										
											2019-04-18 11:23:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-21 18:35:48 +02:00
										 |  |  |     logger.info('Done. Enjoy. Total Time: %s', time.perf_counter() - start) | 
					
						
							| 
									
										
										
										
											2024-02-04 18:38:00 -05:00
										 |  |  |     return multiworld |