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-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
|
2017-12-17 00:25:46 -05:00
|
|
|
|
2022-12-08 21:23:31 +01:00
|
|
|
import worlds
|
2025-05-14 07:40:38 -04:00
|
|
|
from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld
|
2025-05-10 17:49:49 -05:00
|
|
|
from Fill import FillError, balance_multiworld_progression, distribute_items_restrictive, flood_items, \
|
|
|
|
parse_planned_blocks, distribute_planned_blocks, resolve_early_locations_for_planned
|
2025-07-07 15:51:39 +02:00
|
|
|
from NetUtils import convert_to_base_types
|
2023-04-10 20:18:29 -05:00
|
|
|
from Options import StartInventoryPool
|
2025-07-26 15:01:40 -06:00
|
|
|
from Utils import __version__, output_path, restricted_dumps, version_tuple
|
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
|
|
|
|
2025-05-14 07:40:38 -04:00
|
|
|
def main(args, seed=None, baked_server_options: dict[str, object] | None = None):
|
2021-10-11 00:46:18 +02:00
|
|
|
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.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
|
|
|
|
|
|
|
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())))
|
|
|
|
|
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:
|
2025-04-18 20:49:08 +02:00
|
|
|
logger.info(f" {name:{longest_name}}: Items: {len(cls.item_names):{item_count}} | "
|
|
|
|
f"Locations: {len(cls.location_names):{location_count}}")
|
|
|
|
|
|
|
|
del 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.
|
2025-04-05 06:50:52 -07:00
|
|
|
if not args.skip_output and not args.spoiler_only:
|
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
|
|
|
|
2025-07-26 16:30:55 -04:00
|
|
|
# items can't be both local and non-local, prefer local
|
|
|
|
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])
|
|
|
|
|
|
|
|
# Clear non-applicable local and non-local items.
|
|
|
|
if multiworld.players == 1:
|
|
|
|
multiworld.worlds[1].options.non_local_items.value = set()
|
|
|
|
multiworld.worlds[1].options.local_items.value = set()
|
|
|
|
|
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.')
|
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.
|
2025-07-26 16:30:55 -04:00
|
|
|
# This function is called so late because worlds might otherwise overwrite item_rules which are how locality works
|
2024-02-04 18:38:00 -05:00
|
|
|
if multiworld.players > 1:
|
|
|
|
locality_rules(multiworld)
|
2025-01-20 16:07:15 +01:00
|
|
|
|
2025-05-10 17:49:49 -05:00
|
|
|
multiworld.plando_item_blocks = parse_planned_blocks(multiworld)
|
|
|
|
|
2025-01-20 16:07:15 +01:00
|
|
|
AutoWorld.call_all(multiworld, "connect_entrances")
|
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({})
|
2025-05-14 07:40:38 -04:00
|
|
|
depletion_pool: dict[int, dict[str, int]] = {
|
2024-11-29 22:43:01 +01:00
|
|
|
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:
|
2025-05-14 07:40:38 -04:00
|
|
|
new_itempool: list[Item] = []
|
2024-11-29 22:43:01 +01:00
|
|
|
|
|
|
|
# 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.")
|
2025-05-10 17:49:49 -05:00
|
|
|
resolve_early_locations_for_planned(multiworld)
|
|
|
|
distribute_planned_blocks(multiworld, [x for player in multiworld.plando_item_blocks
|
|
|
|
for x in multiworld.plando_item_blocks[player]])
|
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
|
|
|
|
2025-04-05 06:50:52 -07:00
|
|
|
if args.spoiler_only:
|
|
|
|
if args.spoiler > 1:
|
|
|
|
logger.info('Calculating playthrough.')
|
|
|
|
multiworld.spoiler.create_playthrough(create_paths=args.spoiler > 2)
|
|
|
|
|
|
|
|
multiworld.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
|
|
|
logger.info('Done. Skipped multidata modification. Total time: %s', time.perf_counter() - start)
|
|
|
|
return multiworld
|
|
|
|
|
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
|
2025-05-14 07:40:38 -04: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
|
|
|
|
2025-05-14 07:40:38 -04:00
|
|
|
locations_data: dict[int, dict[int, tuple[int, int, int]]] = {player: {} for player in multiworld.player_ids}
|
2024-02-04 18:38:00 -05:00
|
|
|
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
|
|
|
}
|
2025-05-03 11:53:52 +02:00
|
|
|
data_package["Archipelago"] = worlds.network_data_package["games"]["Archipelago"]
|
2022-12-08 21:23:31 +01:00
|
|
|
|
2025-05-14 07:40:38 -04:00
|
|
|
checks_in_area: dict[int, dict[str, int | list[int]]] = {}
|
2023-07-02 13:00:05 +02:00
|
|
|
|
2024-05-27 18:43:25 +02:00
|
|
|
# get spheres -> filter address==None -> skip empty
|
2025-05-14 07:40:38 -04:00
|
|
|
spheres: list[dict[int, set[int]]] = []
|
2024-11-30 04:11:03 +01:00
|
|
|
for sphere in multiworld.get_sendable_spheres():
|
2025-05-14 07:40:38 -04:00
|
|
|
current_sphere: dict[int, set[int]] = collections.defaultdict(set)
|
2024-05-27 18:43:25 +02:00
|
|
|
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
|
|
|
|
2025-07-07 15:51:39 +02:00
|
|
|
for key in ("slot_data", "er_hint_data"):
|
|
|
|
multidata[key] = convert_to_base_types(multidata[key])
|
|
|
|
|
2025-07-26 15:01:40 -06:00
|
|
|
multidata = zlib.compress(restricted_dumps(multidata), 9)
|
2021-09-03 20:35:40 +02:00
|
|
|
|
|
|
|
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
|