2022-02-05 14:49:19 +00:00
|
|
|
import collections
|
2023-04-11 01:18:29 +00:00
|
|
|
import concurrent.futures
|
2017-12-17 05:25:46 +00:00
|
|
|
import logging
|
2019-05-29 23:10:16 +00:00
|
|
|
import os
|
2021-01-03 13:32:32 +00:00
|
|
|
import pickle
|
2021-07-21 16:08:15 +00:00
|
|
|
import tempfile
|
2023-04-11 01:18:29 +00:00
|
|
|
import time
|
2021-07-21 16:08:15 +00:00
|
|
|
import zipfile
|
2023-04-11 01:18:29 +00:00
|
|
|
import zlib
|
2023-07-02 11:00:05 +00:00
|
|
|
from typing import Dict, List, Optional, Set, Tuple, Union
|
2017-12-17 05:25:46 +00:00
|
|
|
|
2022-12-08 20:23:31 +00:00
|
|
|
import worlds
|
2023-04-11 01:18:29 +00:00
|
|
|
from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld, Region
|
|
|
|
from Fill import balance_multiworld_progression, distribute_items_restrictive, distribute_planned, flood_items
|
|
|
|
from Options import StartInventoryPool
|
2023-07-05 20:39:35 +00:00
|
|
|
from settings import get_settings
|
|
|
|
from Utils import __version__, output_path, version_tuple
|
2023-04-11 01:18:29 +00:00
|
|
|
from worlds import AutoWorld
|
|
|
|
from worlds.generic.Rules import exclusion_rules, locality_rules
|
2017-05-15 18:28:04 +00:00
|
|
|
|
2023-05-20 17:21:39 +00:00
|
|
|
__all__ = ["main"]
|
|
|
|
|
2021-10-10 22:46:18 +00:00
|
|
|
|
|
|
|
def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = None):
|
|
|
|
if not baked_server_options:
|
2023-07-15 20:52:52 +00:00
|
|
|
baked_server_options = get_settings().server_options.as_dict()
|
|
|
|
assert isinstance(baked_server_options, dict)
|
2019-12-28 16:12:27 +00:00
|
|
|
if args.outputpath:
|
2020-01-10 06:25:16 +00:00
|
|
|
os.makedirs(args.outputpath, exist_ok=True)
|
2019-12-28 16:12:27 +00:00
|
|
|
output_path.cached_path = args.outputpath
|
|
|
|
|
2020-01-02 01:38:26 +00:00
|
|
|
start = time.perf_counter()
|
2017-05-15 18:28:04 +00:00
|
|
|
# initialize the world
|
2021-03-14 07:38:02 +00:00
|
|
|
world = MultiWorld(args.multi)
|
2020-07-14 05:01:51 +00:00
|
|
|
|
2021-10-06 09:32:49 +00:00
|
|
|
logger = logging.getLogger()
|
2023-03-04 15:34:10 +00:00
|
|
|
world.set_seed(seed, args.race, str(args.outputname) if args.outputname else None)
|
2023-01-17 16:25:59 +00:00
|
|
|
world.plando_options = args.plando_options
|
2017-05-20 12:07:40 +00:00
|
|
|
|
2021-03-14 07:38:02 +00:00
|
|
|
world.shuffle = args.shuffle.copy()
|
|
|
|
world.logic = args.logic.copy()
|
|
|
|
world.mode = args.mode.copy()
|
|
|
|
world.difficulty = args.difficulty.copy()
|
|
|
|
world.item_functionality = args.item_functionality.copy()
|
|
|
|
world.timer = args.timer.copy()
|
|
|
|
world.goal = args.goal.copy()
|
2019-12-17 14:55:53 +00:00
|
|
|
world.boss_shuffle = args.shufflebosses.copy()
|
|
|
|
world.enemy_health = args.enemy_health.copy()
|
|
|
|
world.enemy_damage = args.enemy_damage.copy()
|
2021-11-03 05:34:11 +00:00
|
|
|
world.beemizer_total_chance = args.beemizer_total_chance.copy()
|
|
|
|
world.beemizer_trap_chance = args.beemizer_trap_chance.copy()
|
2020-10-28 23:20:59 +00: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-04-12 22:46:32 +00:00
|
|
|
world.dungeon_counters = args.dungeon_counters.copy()
|
2020-06-17 08:02:54 +00:00
|
|
|
world.triforce_pieces_available = args.triforce_pieces_available.copy()
|
2020-06-07 13:22:24 +00:00
|
|
|
world.triforce_pieces_required = args.triforce_pieces_required.copy()
|
2020-08-23 13:03:06 +00:00
|
|
|
world.shop_shuffle = args.shop_shuffle.copy()
|
2020-09-20 02:35:45 +00:00
|
|
|
world.shuffle_prizes = args.shuffle_prizes.copy()
|
2020-10-06 20:22:03 +00:00
|
|
|
world.sprite_pool = args.sprite_pool.copy()
|
2020-10-07 17:51:46 +00:00
|
|
|
world.dark_room_logic = args.dark_room_logic.copy()
|
2021-01-02 11:49:43 +00:00
|
|
|
world.plando_items = args.plando_items.copy()
|
2021-01-02 15:44:58 +00:00
|
|
|
world.plando_texts = args.plando_texts.copy()
|
2021-01-02 21:41:03 +00:00
|
|
|
world.plando_connections = args.plando_connections.copy()
|
2021-01-02 22:00:14 +00:00
|
|
|
world.required_medallions = args.required_medallions.copy()
|
2021-02-21 19:17:24 +00:00
|
|
|
world.game = args.game.copy()
|
2021-08-09 07:15:41 +00:00
|
|
|
world.player_name = args.name.copy()
|
|
|
|
world.sprite = args.sprite.copy()
|
2021-03-22 20:14:19 +00:00
|
|
|
world.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option.
|
2019-08-11 12:55:38 +00:00
|
|
|
|
2022-02-17 05:07:11 +00:00
|
|
|
world.set_options(args)
|
2022-02-17 06:07:34 +00:00
|
|
|
world.set_item_links()
|
2022-02-17 05:07:11 +00:00
|
|
|
world.state = CollectionState(world)
|
2021-01-03 13:32:32 +00:00
|
|
|
logger.info('Archipelago Version %s - Seed: %s\n', __version__, world.seed)
|
2020-01-14 09:42:27 +00:00
|
|
|
|
2023-02-28 10:23:42 +00:00
|
|
|
logger.info(f"Found {len(AutoWorld.AutoWorldRegister.world_types)} World Types:")
|
2021-07-12 13:11:48 +00:00
|
|
|
longest_name = max(len(text) for text in AutoWorld.AutoWorldRegister.world_types)
|
2022-10-27 07:18:25 +00: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 12:22:44 +00:00
|
|
|
for name, cls in AutoWorld.AutoWorldRegister.world_types.items():
|
2022-10-13 05:55:00 +00:00
|
|
|
if not cls.hidden and len(cls.item_names) > 0:
|
2022-10-27 07:18:25 +00: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 12:07:40 +00:00
|
|
|
|
2022-04-30 01:37:28 +00:00
|
|
|
AutoWorld.call_stage(world, "assert_generate")
|
|
|
|
|
2021-08-27 22:26:02 +00:00
|
|
|
AutoWorld.call_all(world, "generate_early")
|
|
|
|
|
2020-01-14 09:42:27 +00:00
|
|
|
logger.info('')
|
2021-05-22 04:27:22 +00:00
|
|
|
|
2021-05-09 19:22:21 +00:00
|
|
|
for player in world.player_ids:
|
2021-09-23 01:53:16 +00:00
|
|
|
for item_name, count in world.start_inventory[player].value.items():
|
|
|
|
for _ in range(count):
|
|
|
|
world.push_precollected(world.create_item(item_name, player))
|
2023-04-11 01:18:29 +00:00
|
|
|
|
|
|
|
for item_name, count in world.start_inventory_from_pool.setdefault(player, StartInventoryPool({})).value.items():
|
2023-04-10 19:13:33 +00:00
|
|
|
for _ in range(count):
|
|
|
|
world.push_precollected(world.create_item(item_name, player))
|
2017-05-20 12:07:40 +00:00
|
|
|
|
2021-07-22 13:51:50 +00:00
|
|
|
logger.info('Creating World.')
|
2021-06-11 12:22:44 +00:00
|
|
|
AutoWorld.call_all(world, "create_regions")
|
2021-04-01 09:40:58 +00:00
|
|
|
|
2021-07-22 13:51:50 +00:00
|
|
|
logger.info('Creating Items.')
|
|
|
|
AutoWorld.call_all(world, "create_items")
|
2019-04-18 21:11:11 +00:00
|
|
|
|
2023-01-24 05:11:07 +00:00
|
|
|
# All worlds should have finished creating all regions, locations, and entrances.
|
|
|
|
# Recache to ensure that they are all visible for locality rules.
|
|
|
|
world._recache()
|
|
|
|
|
2017-05-20 12:07:40 +00:00
|
|
|
logger.info('Calculating Access Rules.')
|
2022-11-28 06:03:09 +00:00
|
|
|
|
|
|
|
for player in world.player_ids:
|
|
|
|
# items can't be both local and non-local, prefer local
|
|
|
|
world.non_local_items[player].value -= world.local_items[player].value
|
|
|
|
world.non_local_items[player].value -= set(world.local_early_items[player])
|
|
|
|
|
2021-06-11 16:02:48 +00:00
|
|
|
AutoWorld.call_all(world, "set_rules")
|
|
|
|
|
2021-07-14 13:24:34 +00:00
|
|
|
for player in world.player_ids:
|
2021-09-16 22:17:54 +00:00
|
|
|
exclusion_rules(world, player, world.exclude_locations[player].value)
|
2022-02-01 15:36:14 +00:00
|
|
|
world.priority_locations[player].value -= world.exclude_locations[player].value
|
|
|
|
for location_name in world.priority_locations[player].value:
|
|
|
|
world.get_location(location_name, player).progress_type = LocationProgressType.PRIORITY
|
2021-07-14 13:24:34 +00:00
|
|
|
|
2023-07-29 02:06:43 +00:00
|
|
|
# Set local and non-local item rules.
|
|
|
|
if world.players > 1:
|
|
|
|
locality_rules(world)
|
|
|
|
else:
|
|
|
|
world.non_local_items[1].value = set()
|
|
|
|
world.local_items[1].value = set()
|
|
|
|
|
2021-06-11 12:22:44 +00:00
|
|
|
AutoWorld.call_all(world, "generate_basic")
|
2021-04-01 09:40:58 +00:00
|
|
|
|
2023-04-10 19:13:33 +00: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.
|
|
|
|
if any(world.start_inventory_from_pool[player].value for player in world.player_ids):
|
|
|
|
new_items: List[Item] = []
|
|
|
|
depletion_pool: Dict[int, Dict[str, int]] = {
|
|
|
|
player: world.start_inventory_from_pool[player].value.copy() for player in world.player_ids}
|
|
|
|
for player, items in depletion_pool.items():
|
|
|
|
player_world: AutoWorld.World = world.worlds[player]
|
|
|
|
for count in items.values():
|
2023-09-16 01:32:05 +00:00
|
|
|
for _ in range(count):
|
|
|
|
new_items.append(player_world.create_filler())
|
2023-04-10 19:13:33 +00:00
|
|
|
target: int = sum(sum(items.values()) for items in depletion_pool.values())
|
2023-05-03 21:22:58 +00:00
|
|
|
for i, item in enumerate(world.itempool):
|
2023-04-10 19:13:33 +00:00
|
|
|
if depletion_pool[item.player].get(item.name, 0):
|
|
|
|
target -= 1
|
|
|
|
depletion_pool[item.player][item.name] -= 1
|
|
|
|
# quick abort if we have found all items
|
|
|
|
if not target:
|
2023-05-03 21:22:58 +00:00
|
|
|
new_items.extend(world.itempool[i+1:])
|
2023-04-10 19:13:33 +00:00
|
|
|
break
|
|
|
|
else:
|
|
|
|
new_items.append(item)
|
2023-05-03 21:22:58 +00:00
|
|
|
|
|
|
|
# leftovers?
|
|
|
|
if target:
|
|
|
|
for player, remaining_items in depletion_pool.items():
|
|
|
|
remaining_items = {name: count for name, count in remaining_items.items() if count}
|
|
|
|
if remaining_items:
|
|
|
|
raise Exception(f"{world.get_player_name(player)}"
|
|
|
|
f" is trying to remove items from their pool that don't exist: {remaining_items}")
|
2023-09-16 01:32:05 +00:00
|
|
|
assert len(world.itempool) == len(new_items), "Item Pool amounts should not change."
|
2023-04-10 19:13:33 +00:00
|
|
|
world.itempool[:] = new_items
|
|
|
|
|
2022-02-05 14:49:19 +00:00
|
|
|
# temporary home for item links, should be moved out of Main
|
2022-02-17 05:07:11 +00:00
|
|
|
for group_id, group in world.groups.items():
|
2022-10-28 19:56:50 +00:00
|
|
|
def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[
|
|
|
|
Optional[Dict[int, Dict[str, int]]], Optional[Dict[str, int]]
|
|
|
|
]:
|
|
|
|
classifications: Dict[str, int] = collections.defaultdict(int)
|
2022-02-05 14:49:19 +00:00
|
|
|
counters = {player: {name: 0 for name in shared_pool} for player in players}
|
|
|
|
for item in world.itempool:
|
|
|
|
if item.player in counters and item.name in shared_pool:
|
|
|
|
counters[item.player][item.name] += 1
|
2022-06-17 03:26:11 +00:00
|
|
|
classifications[item.name] |= item.classification
|
2022-02-05 14:49:19 +00:00
|
|
|
|
2022-02-23 00:35:41 +00:00
|
|
|
for player in players.copy():
|
|
|
|
if all([counters[player][item] == 0 for item in shared_pool]):
|
|
|
|
players.remove(player)
|
2022-10-28 19:56:50 +00:00
|
|
|
del (counters[player])
|
2022-02-23 00:35:41 +00:00
|
|
|
|
|
|
|
if not players:
|
|
|
|
return None, None
|
|
|
|
|
2022-02-05 14:49:19 +00:00
|
|
|
for item in shared_pool:
|
|
|
|
count = min(counters[player][item] for player in players)
|
|
|
|
if count:
|
|
|
|
for player in players:
|
|
|
|
counters[player][item] = count
|
|
|
|
else:
|
|
|
|
for player in players:
|
2022-10-28 19:56:50 +00:00
|
|
|
del (counters[player][item])
|
2022-06-17 03:26:11 +00:00
|
|
|
return counters, classifications
|
2022-02-05 14:49:19 +00:00
|
|
|
|
2022-06-17 03:26:11 +00:00
|
|
|
common_item_count, classifications = find_common_pool(group["players"], group["item_pool"])
|
2022-02-23 00:35:41 +00:00
|
|
|
if not common_item_count:
|
|
|
|
continue
|
2022-02-22 09:14:26 +00:00
|
|
|
|
2022-10-28 19:56:50 +00:00
|
|
|
new_itempool: List[Item] = []
|
2022-02-05 14:49:19 +00:00
|
|
|
for item_name, item_count in next(iter(common_item_count.values())).items():
|
|
|
|
for _ in range(item_count):
|
2022-02-17 05:07:11 +00:00
|
|
|
new_item = group["world"].create_item(item_name)
|
2022-06-17 03:26:11 +00:00
|
|
|
# mangle together all original classification bits
|
|
|
|
new_item.classification |= classifications[item_name]
|
2022-02-17 05:07:11 +00:00
|
|
|
new_itempool.append(new_item)
|
2022-02-05 14:49:19 +00:00
|
|
|
|
2023-02-14 00:06:43 +00:00
|
|
|
region = Region("Menu", group_id, world, "ItemLink")
|
2022-02-17 05:07:11 +00:00
|
|
|
world.regions.append(region)
|
|
|
|
locations = region.locations = []
|
2022-02-05 14:49:19 +00:00
|
|
|
for item in world.itempool:
|
2022-02-17 05:07:11 +00:00
|
|
|
count = common_item_count.get(item.player, {}).get(item.name, 0)
|
|
|
|
if count:
|
|
|
|
loc = Location(group_id, f"Item Link: {item.name} -> {world.player_name[item.player]} {count}",
|
|
|
|
None, region)
|
2022-02-22 09:14:26 +00:00
|
|
|
loc.access_rule = lambda state, item_name = item.name, group_id_ = group_id, count_ = count: \
|
|
|
|
state.has(item_name, group_id_, count_)
|
|
|
|
|
2022-02-17 05:07:11 +00:00
|
|
|
locations.append(loc)
|
|
|
|
loc.place_locked_item(item)
|
2022-02-05 14:49:19 +00:00
|
|
|
common_item_count[item.player][item.name] -= 1
|
|
|
|
else:
|
|
|
|
new_itempool.append(item)
|
|
|
|
|
|
|
|
itemcount = len(world.itempool)
|
|
|
|
world.itempool = new_itempool
|
|
|
|
|
|
|
|
while itemcount > len(world.itempool):
|
2022-03-20 15:07:51 +00:00
|
|
|
items_to_add = []
|
2022-02-05 19:15:56 +00:00
|
|
|
for player in group["players"]:
|
2022-12-07 05:37:47 +00:00
|
|
|
if group["link_replacement"]:
|
|
|
|
item_player = group_id
|
|
|
|
else:
|
|
|
|
item_player = player
|
2022-02-17 05:07:11 +00:00
|
|
|
if group["replacement_items"][player]:
|
2022-12-07 05:37:47 +00:00
|
|
|
items_to_add.append(AutoWorld.call_single(world, "create_item", item_player,
|
|
|
|
group["replacement_items"][player]))
|
2022-02-05 14:49:19 +00:00
|
|
|
else:
|
2022-12-07 05:37:47 +00:00
|
|
|
items_to_add.append(AutoWorld.call_single(world, "create_filler", item_player))
|
2022-03-20 15:07:51 +00:00
|
|
|
world.random.shuffle(items_to_add)
|
|
|
|
world.itempool.extend(items_to_add[:itemcount - len(world.itempool)])
|
|
|
|
|
2022-02-17 05:07:11 +00:00
|
|
|
if any(world.item_links.values()):
|
|
|
|
world._recache()
|
|
|
|
world._all_state = None
|
2022-02-05 14:49:19 +00:00
|
|
|
|
2021-01-02 11:49:43 +00:00
|
|
|
logger.info("Running Item Plando")
|
|
|
|
|
2021-01-04 14:14:20 +00:00
|
|
|
distribute_planned(world)
|
2021-01-02 11:49:43 +00:00
|
|
|
|
2021-08-09 04:50:11 +00:00
|
|
|
logger.info('Running Pre Main Fill.')
|
2021-01-24 07:26:39 +00:00
|
|
|
|
2021-08-09 04:50:11 +00:00
|
|
|
AutoWorld.call_all(world, "pre_fill")
|
2017-05-20 12:07:40 +00:00
|
|
|
|
2021-10-17 18:53:06 +00:00
|
|
|
logger.info(f'Filling the world with {len(world.itempool)} items.')
|
2017-05-20 12:07:40 +00:00
|
|
|
|
2021-03-14 07:38:02 +00:00
|
|
|
if world.algorithm == 'flood':
|
2017-05-20 12:07:40 +00:00
|
|
|
flood_items(world) # different algo, biased towards early game progress items
|
2021-03-14 07:38:02 +00:00
|
|
|
elif world.algorithm == 'balanced':
|
2021-08-10 07:03:44 +00:00
|
|
|
distribute_items_restrictive(world)
|
2017-06-03 19:28:02 +00:00
|
|
|
|
2021-08-29 23:16:04 +00:00
|
|
|
AutoWorld.call_all(world, 'post_fill')
|
2020-12-23 21:30:21 +00:00
|
|
|
|
2023-06-26 21:14:01 +00:00
|
|
|
if world.players > 1 and not args.skip_prog_balancing:
|
2021-02-05 07:07:12 +00:00
|
|
|
balance_multiworld_progression(world)
|
2023-06-26 21:14:01 +00:00
|
|
|
else:
|
|
|
|
logger.info("Progression balancing skipped.")
|
2021-02-05 07:07:12 +00:00
|
|
|
|
2021-09-03 15:30:10 +00:00
|
|
|
logger.info(f'Beginning output...')
|
2023-02-02 00:14:23 +00:00
|
|
|
|
|
|
|
# we're about to output using multithreading, so we're removing the global random state to prevent accidental use
|
|
|
|
world.random.passthrough = False
|
|
|
|
|
2021-05-15 22:21:00 +00:00
|
|
|
outfilebase = 'AP_' + world.seed_name
|
2020-03-06 22:08:46 +00:00
|
|
|
|
2021-07-21 16:08:15 +00:00
|
|
|
output = tempfile.TemporaryDirectory()
|
|
|
|
with output as temp_dir:
|
2021-10-10 22:46:18 +00:00
|
|
|
with concurrent.futures.ThreadPoolExecutor(world.players + 2) as pool:
|
2021-09-03 18:35:40 +00:00
|
|
|
check_accessibility_task = pool.submit(world.fulfills_accessibility)
|
|
|
|
|
2021-10-10 14:50:01 +00:00
|
|
|
output_file_futures = [pool.submit(AutoWorld.call_stage, world, "generate_output", temp_dir)]
|
2021-09-03 18:35:40 +00:00
|
|
|
for player in world.player_ids:
|
|
|
|
# skip starting a thread for methods that say "pass".
|
|
|
|
if AutoWorld.World.generate_output.__code__ is not world.worlds[player].generate_output.__code__:
|
2021-10-10 22:46:18 +00:00
|
|
|
output_file_futures.append(
|
|
|
|
pool.submit(AutoWorld.call_single, world, "generate_output", player, temp_dir))
|
2021-09-03 18:35:40 +00:00
|
|
|
|
|
|
|
# collect ER hint info
|
2022-09-18 12:30:43 +00:00
|
|
|
er_hint_data: Dict[int, Dict[int, str]] = {}
|
|
|
|
AutoWorld.call_all(world, 'extend_hint_information', er_hint_data)
|
2021-09-03 18:35:40 +00:00
|
|
|
|
|
|
|
def write_multidata():
|
|
|
|
import NetUtils
|
|
|
|
slot_data = {}
|
|
|
|
client_versions = {}
|
|
|
|
games = {}
|
2022-04-08 09:16:36 +00:00
|
|
|
minimum_versions = {"server": AutoWorld.World.required_server_version, "clients": client_versions}
|
2022-01-30 12:57:12 +00:00
|
|
|
slot_info = {}
|
|
|
|
names = [[name for player, name in sorted(world.player_name.items())]]
|
2021-09-03 18:35:40 +00:00
|
|
|
for slot in world.player_ids:
|
2022-04-08 09:16:36 +00:00
|
|
|
player_world: AutoWorld.World = world.worlds[slot]
|
|
|
|
minimum_versions["server"] = max(minimum_versions["server"], player_world.required_server_version)
|
|
|
|
client_versions[slot] = player_world.required_client_version
|
2021-09-03 18:35:40 +00:00
|
|
|
games[slot] = world.game[slot]
|
2022-02-05 14:49:19 +00:00
|
|
|
slot_info[slot] = NetUtils.NetworkSlot(names[0][slot - 1], world.game[slot],
|
|
|
|
world.player_types[slot])
|
|
|
|
for slot, group in world.groups.items():
|
|
|
|
games[slot] = world.game[slot]
|
|
|
|
slot_info[slot] = NetUtils.NetworkSlot(group["name"], world.game[slot], world.player_types[slot],
|
|
|
|
group_members=sorted(group["players"]))
|
2022-03-18 17:19:21 +00:00
|
|
|
precollected_items = {player: [item.code for item in world_precollected if type(item.code) == int]
|
2021-10-10 14:50:01 +00:00
|
|
|
for player, world_precollected in world.precollected_items.items()}
|
2022-02-05 14:49:19 +00:00
|
|
|
precollected_hints = {player: set() for player in range(1, world.players + 1 + len(world.groups))}
|
2022-01-14 18:27:44 +00:00
|
|
|
|
2021-09-03 18:35:40 +00:00
|
|
|
for slot in world.player_ids:
|
|
|
|
slot_data[slot] = world.worlds[slot].fill_slot_data()
|
|
|
|
|
2022-02-22 20:49:43 +00:00
|
|
|
def precollect_hint(location):
|
2022-02-19 18:52:05 +00:00
|
|
|
entrance = er_hint_data.get(location.player, {}).get(location.address, "")
|
2021-10-03 12:40:25 +00:00
|
|
|
hint = NetUtils.Hint(location.item.player, location.player, location.address,
|
2022-02-19 18:52:05 +00:00
|
|
|
location.item.code, False, entrance, location.item.flags)
|
2021-10-03 12:40:25 +00:00
|
|
|
precollected_hints[location.player].add(hint)
|
2022-02-22 20:49:43 +00:00
|
|
|
if location.item.player not in world.groups:
|
2022-02-21 23:33:39 +00:00
|
|
|
precollected_hints[location.item.player].add(hint)
|
|
|
|
else:
|
2022-02-22 20:49:43 +00:00
|
|
|
for player in world.groups[location.item.player]["players"]:
|
2022-02-21 23:33:39 +00:00
|
|
|
precollected_hints[player].add(hint)
|
2021-10-03 12:40:25 +00:00
|
|
|
|
2022-01-18 04:52:29 +00:00
|
|
|
locations_data: Dict[int, Dict[int, Tuple[int, int, int]]] = {player: {} for player in world.player_ids}
|
2021-09-03 18:35:40 +00:00
|
|
|
for location in world.get_filled_locations():
|
|
|
|
if type(location.address) == int:
|
2022-03-26 00:12:54 +00:00
|
|
|
assert location.item.code is not None, "item code None should be event, " \
|
2022-07-15 15:41:53 +00:00
|
|
|
"location.address should then also be None. Location: " \
|
|
|
|
f" {location}"
|
2022-01-18 04:52:29 +00:00
|
|
|
locations_data[location.player][location.address] = \
|
2022-01-18 05:16:16 +00:00
|
|
|
location.item.code, location.item.player, location.item.flags
|
2022-03-24 16:15:52 +00:00
|
|
|
if location.name in world.start_location_hints[location.player]:
|
2021-10-03 12:40:25 +00:00
|
|
|
precollect_hint(location)
|
2021-09-30 17:49:36 +00:00
|
|
|
elif location.item.name in world.start_hints[location.item.player]:
|
2021-10-03 12:40:25 +00:00
|
|
|
precollect_hint(location)
|
2022-02-22 20:49:43 +00:00
|
|
|
elif any([location.item.name in world.start_hints[player]
|
|
|
|
for player in world.groups.get(location.item.player, {}).get("players", [])]):
|
|
|
|
precollect_hint(location)
|
2021-09-03 18:35:40 +00:00
|
|
|
|
2023-03-20 16:01:08 +00:00
|
|
|
# embedded data package
|
|
|
|
data_package = {
|
|
|
|
game_world.game: worlds.network_data_package["games"][game_world.game]
|
|
|
|
for game_world in world.worlds.values()
|
|
|
|
}
|
2022-12-08 20:23:31 +00:00
|
|
|
|
2023-07-02 11:00:05 +00:00
|
|
|
checks_in_area: Dict[int, Dict[str, Union[int, List[int]]]] = {}
|
|
|
|
|
2021-09-03 18:35:40 +00:00
|
|
|
multidata = {
|
|
|
|
"slot_data": slot_data,
|
2022-01-30 12:57:12 +00:00
|
|
|
"slot_info": slot_info,
|
2021-09-03 18:35:40 +00:00
|
|
|
"connect_names": {name: (0, player) for player, name in world.player_name.items()},
|
|
|
|
"locations": locations_data,
|
|
|
|
"checks_in_area": checks_in_area,
|
2023-07-15 20:52:52 +00:00
|
|
|
"server_options": baked_server_options,
|
2021-09-03 18:35:40 +00: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,
|
2022-12-08 20:23:31 +00:00
|
|
|
"seed_name": world.seed_name,
|
2023-03-20 16:01:08 +00:00
|
|
|
"datapackage": data_package,
|
2021-09-03 18:35:40 +00:00
|
|
|
}
|
|
|
|
AutoWorld.call_all(world, "modify_multidata", multidata)
|
|
|
|
|
|
|
|
multidata = zlib.compress(pickle.dumps(multidata), 9)
|
|
|
|
|
|
|
|
with open(os.path.join(temp_dir, f'{outfilebase}.archipelago'), 'wb') as f:
|
2022-02-02 15:29:29 +00:00
|
|
|
f.write(bytes([3])) # version of format
|
2021-09-03 18:35:40 +00:00
|
|
|
f.write(multidata)
|
|
|
|
|
2023-09-09 03:02:53 +00:00
|
|
|
output_file_futures.append(pool.submit(write_multidata))
|
2021-09-03 18:35:40 +00:00
|
|
|
if not check_accessibility_task.result():
|
|
|
|
if not world.can_beat_game():
|
|
|
|
raise Exception("Game appears as unbeatable. Aborting.")
|
|
|
|
else:
|
|
|
|
logger.warning("Location Accessibility requirements not fulfilled.")
|
|
|
|
|
2022-02-05 14:49:19 +00:00
|
|
|
# retrieve exceptions via .result() if they occurred.
|
2021-09-13 01:38:18 +00: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 18:35:40 +00:00
|
|
|
logger.info(f'Generating output files ({i}/{len(output_file_futures)}).')
|
|
|
|
future.result()
|
2021-08-27 12:52:33 +00:00
|
|
|
|
2021-09-12 23:32:32 +00:00
|
|
|
if args.spoiler > 1:
|
2021-07-21 16:08:15 +00:00
|
|
|
logger.info('Calculating playthrough.')
|
2022-12-11 19:48:26 +00:00
|
|
|
world.spoiler.create_playthrough(create_paths=args.spoiler > 2)
|
2021-08-27 12:52:33 +00:00
|
|
|
|
2021-09-12 23:32:32 +00:00
|
|
|
if args.spoiler:
|
2021-07-21 16:08:15 +00:00
|
|
|
world.spoiler.to_file(os.path.join(temp_dir, '%s_Spoiler.txt' % outfilebase))
|
2021-08-27 12:52:33 +00:00
|
|
|
|
2021-07-25 14:15:51 +00:00
|
|
|
zipfilename = output_path(f"AP_{world.seed_name}.zip")
|
2022-08-10 23:02:06 +00:00
|
|
|
logger.info(f"Creating final archive at {zipfilename}")
|
2021-07-25 14:15:51 +00:00
|
|
|
with zipfile.ZipFile(zipfilename, mode="w", compression=zipfile.ZIP_DEFLATED,
|
2021-07-21 16:08:15 +00:00
|
|
|
compresslevel=9) as zf:
|
|
|
|
for file in os.scandir(temp_dir):
|
2021-08-27 12:52:33 +00:00
|
|
|
zf.write(file.path, arcname=file.name)
|
2019-04-18 09:23:24 +00:00
|
|
|
|
2020-08-21 16:35:48 +00:00
|
|
|
logger.info('Done. Enjoy. Total Time: %s', time.perf_counter() - start)
|
2017-05-15 18:28:04 +00:00
|
|
|
return world
|