def write_multidata(roms): for future in roms: rom_name = future.result() rom_names.append(rom_name) multidata = zlib.compress( json.dumps({ "names": parsed_names, # backwards compat for < 2.4.1 "roms": [(slot, team, list(name.encode())) for (slot, team, name) in rom_names], "rom_strings": rom_names, "remote_items": [ player for player in range(1, world.players + 1) if world.remote_items[player] ], "locations": [((location.address, location.player), (location.item.code, location.item.player)) for location in world.get_filled_locations() if type(location.address) is int], "server_options": get_options()["server_options"], "er_hint_data": er_hint_data, "precollected_items": precollected_items, "version": _version_tuple, "tags": ["ER"] }).encode("utf-8"), 9) with open(output_path('%s.multidata' % outfilebase), 'wb') as f: f.write(multidata)
def write_multidata(roms): for future in roms: rom_name = future.result() rom_names.append(rom_name) multidatatags = ["ER"] if args.race: multidatatags.append("Race") if args.create_spoiler: multidatatags.append("Spoiler") if not args.skip_playthrough: multidatatags.append("Play through") minimum_versions = {"server": (1, 0, 0)} minimum_versions["clients"] = client_versions = [] for (slot, team, name) in rom_names: if world.shop_shuffle_slots[slot]: client_versions.append([team, slot, [3, 6, 1]]) multidata = zlib.compress( json.dumps({ "names": parsed_names, # backwards compat for < 2.4.1 "roms": [(slot, team, list(name.encode())) for (slot, team, name) in rom_names], "rom_strings": rom_names, "remote_items": [ player for player in range(1, world.players + 1) if world.remote_items[player] ], "locations": [((location.address, location.player), (location.item.code, location.item.player)) for location in world.get_filled_locations() if type(location.address) is int] + oldmancaves, "checks_in_area": checks_in_area, "server_options": get_options()["server_options"], "er_hint_data": er_hint_data, "precollected_items": precollected_items, "version": _version_tuple, "tags": multidatatags, "minimum_versions": minimum_versions }).encode("utf-8"), 9) with open(output_path('%s.multidata' % outfilebase), 'wb') as f: f.write(multidata)
if __name__ == "__main__": try: print(f"{__author__}'s MultiMystery Launcher") import ModuleUpdate ModuleUpdate.update() parser = argparse.ArgumentParser(add_help=False) parser.add_argument('--disable_autohost', action='store_true') args = parser.parse_args() from Utils import get_public_ipv4, get_options from Patch import create_patch_file options = get_options() multi_mystery_options = options["multi_mystery_options"] output_path = multi_mystery_options["output_path"] enemizer_path = multi_mystery_options["enemizer_path"] player_files_path = multi_mystery_options["player_files_path"] race = multi_mystery_options["race"] create_spoiler = multi_mystery_options["create_spoiler"] zip_roms = multi_mystery_options["zip_roms"] zip_diffs = multi_mystery_options["zip_diffs"] zip_spoiler = multi_mystery_options["zip_spoiler"] zip_multidata = multi_mystery_options["zip_multidata"] zip_format = multi_mystery_options["zip_format"] #zip_password = multi_mystery_options["zip_password"] not at this time player_name = multi_mystery_options["player_name"] meta_file_path = multi_mystery_options["meta_file_path"]
def main(args, seed=None): if args.outputpath: os.makedirs(args.outputpath, exist_ok=True) output_path.cached_path = args.outputpath start = time.perf_counter() # initialize the world world = World(args.multi, args.shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive.copy(), args.goal, args.algorithm, args.accessibility, args.shuffleganon, args.retro, args.custom, args.customitemarray, args.hints) logger = logging.getLogger('') world.seed = get_seed(seed) random.seed(world.seed) world.remote_items = args.remote_items.copy() world.mapshuffle = args.mapshuffle.copy() world.compassshuffle = args.compassshuffle.copy() world.keyshuffle = args.keyshuffle.copy() world.bigkeyshuffle = args.bigkeyshuffle.copy() world.crystals_needed_for_ganon = { player: random.randint(0, 7) if args.crystals_ganon[player] == 'random' else int(args.crystals_ganon[player]) for player in range(1, world.players + 1) } world.crystals_needed_for_gt = { player: random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player]) for player in range(1, world.players + 1) } world.open_pyramid = args.openpyramid.copy() world.boss_shuffle = args.shufflebosses.copy() world.enemy_shuffle = args.shuffleenemies.copy() world.enemy_health = args.enemy_health.copy() world.enemy_damage = args.enemy_damage.copy() world.beemizer = args.beemizer.copy() world.timer = args.timer.copy() world.shufflepots = args.shufflepots.copy() world.progressive = args.progressive.copy() world.dungeon_counters = args.dungeon_counters.copy() world.glitch_boots = args.glitch_boots.copy() world.triforce_pieces_available = args.triforce_pieces_available.copy() world.triforce_pieces_required = args.triforce_pieces_required.copy() world.progression_balancing = { player: not balance for player, balance in args.skip_progression_balancing.items() } world.rom_seeds = { player: random.randint(0, 999999999) for player in range(1, world.players + 1) } logger.info('ALttP Berserker\'s Multiworld Version %s - Seed: %s\n', __version__, world.seed) parsed_names = parse_player_names(args.names, world.players, args.teams) world.teams = len(parsed_names) for i, team in enumerate(parsed_names, 1): if world.players > 1: logger.info('%s%s', 'Team%d: ' % i if world.teams > 1 else 'Players: ', ', '.join(team)) for player, name in enumerate(team, 1): world.player_names[player].append(name) logger.info('') for player in range(1, world.players + 1): world.difficulty_requirements[player] = difficulties[ world.difficulty[player]] if world.mode[player] == 'standard' and world.enemy_shuffle[ player] != 'none': world.escape_assist[player].append( 'bombs' ) # enemized escape assumes infinite bombs available and will likely be unbeatable without it for tok in filter(None, args.startinventory[player].split(',')): item = ItemFactory(tok.strip(), player) if item: world.push_precollected(item) world.local_items[player] = { item.strip() for item in args.local_items[player].split(',') } world.triforce_pieces_available[player] = max( world.triforce_pieces_available[player], world.triforce_pieces_required[player]) if world.mode[player] != 'inverted': create_regions(world, player) else: create_inverted_regions(world, player) create_shops(world, player) create_dungeons(world, player) logger.info('Shuffling the World about.') for player in range(1, world.players + 1): if world.mode[player] != 'inverted': link_entrances(world, player) mark_light_world_regions(world, player) else: link_inverted_entrances(world, player) mark_dark_world_regions(world, player) logger.info('Generating Item Pool.') for player in range(1, world.players + 1): generate_itempool(world, player) logger.info('Calculating Access Rules.') for player in range(1, world.players + 1): set_rules(world, player) logger.info('Placing Dungeon Prizes.') fill_prizes(world) logger.info('Placing Dungeon Items.') shuffled_locations = None if args.algorithm in ['balanced', 'vt26'] or any( list(args.mapshuffle.values()) + list(args.compassshuffle.values()) + list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())): shuffled_locations = world.get_unfilled_locations() random.shuffle(shuffled_locations) fill_dungeons_restrictive(world, shuffled_locations) else: fill_dungeons(world) logger.info('Fill the world.') if args.algorithm == 'flood': flood_items( world) # different algo, biased towards early game progress items elif args.algorithm == 'vt21': distribute_items_cutoff(world, 1) elif args.algorithm == 'vt22': distribute_items_cutoff(world, 0.66) elif args.algorithm == 'freshness': distribute_items_staleness(world) elif args.algorithm == 'vt25': distribute_items_restrictive(world, False) elif args.algorithm == 'vt26': distribute_items_restrictive(world, True, shuffled_locations) elif args.algorithm == 'balanced': distribute_items_restrictive(world, True) if world.players > 1: balance_multiworld_progression(world) logger.info('Patching ROM.') outfilebase = 'BM_%s' % (args.outputname if args.outputname else world.seed) rom_names = [] def _gen_rom(team: int, player: int): sprite_random_on_hit = type( args.sprite[player]) is str and args.sprite[player].lower( ) == 'randomonhit' use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player] != 'none' or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default' or args.shufflepots[player] or sprite_random_on_hit) rom = LocalRom(args.rom) patch_rom(world, rom, player, team, use_enemizer) if use_enemizer: patch_enemizer(world, player, rom, args.enemizercli, sprite_random_on_hit) if args.race: patch_race_rom(rom) world.spoiler.hashes[(player, team)] = get_hash_string(rom.hash) apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player], args.fastmenu[player], args.disablemusic[player], args.sprite[player], args.ow_palettes[player], args.uw_palettes[player]) mcsb_name = '' if all([ world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player] ]): mcsb_name = '-keysanity' elif [ world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player] ].count(True) == 1: mcsb_name = '-mapshuffle' if world.mapshuffle[ player] else '-compassshuffle' if world.compassshuffle[ player] else '-keyshuffle' if world.keyshuffle[ player] else '-bigkeyshuffle' elif any([ world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player] ]): mcsb_name = '-%s%s%s%sshuffle' % ( 'M' if world.mapshuffle[player] else '', 'C' if world.compassshuffle[player] else '', 'S' if world.keyshuffle[player] else '', 'B' if world.bigkeyshuffle[player] else '') outfilepname = f'_T{team + 1}' if world.teams > 1 else '' if world.players > 1: outfilepname += f'_P{player}' if world.players > 1 or world.teams > 1: outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" if world.player_names[ player][team] != 'Player%d' % player else '' outfilesuffix = ( '_%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s' % (world.logic[player], world.difficulty[player], world.difficulty_adjustments[player], world.mode[player], world.goal[player], "" if world.timer[player] in [False, 'display'] else "-" + world.timer[player], world.shuffle[player], world.algorithm, mcsb_name, "-retro" if world.retro[player] else "", "-prog_" + world.progressive[player] if world.progressive[player] in ['off', 'random'] else "", "-nohints" if not world.hints[player] else "")) if not args.outputname else '' rompath = output_path( f'{outfilebase}{outfilepname}{outfilesuffix}.sfc') rom.write_to_file(rompath) if args.create_diff: import Patch Patch.create_patch_file(rompath) return (player, team, list(rom.name)) if not args.suppress_rom: import concurrent.futures futures = [] with concurrent.futures.ThreadPoolExecutor() as pool: for team in range(world.teams): for player in range(1, world.players + 1): futures.append(pool.submit(_gen_rom, team, player)) for future in futures: rom_name = future.result() rom_names.append(rom_name) def get_entrance_to_region(region: Region): for entrance in region.entrances: if entrance.parent_region.type in (RegionType.DarkWorld, RegionType.LightWorld): return entrance for entrance in region.entrances: # BFS might be better here, trying DFS for now. return get_entrance_to_region(entrance.parent_region) # collect ER hint info er_hint_data = { player: {} for player in range(1, world.players + 1) if world.shuffle[player] != "vanilla" } from Regions import RegionType for region in world.regions: if region.player in er_hint_data and region.locations: main_entrance = get_entrance_to_region(region) for location in region.locations: if type(location.address ) == int: # skips events and crystals if lookup_vanilla_location_to_entrance[ location.address] != main_entrance.name: er_hint_data[region.player][ location.address] = main_entrance.name precollected_items = [[] for player in range(world.players)] for item in world.precollected_items: precollected_items[item.player - 1].append(item.code) multidata = zlib.compress( json.dumps({ "names": parsed_names, "roms": rom_names, "remote_items": [ player for player in range(1, world.players + 1) if world.remote_items[player] ], "locations": [((location.address, location.player), (location.item.code, location.item.player)) for location in world.get_filled_locations() if type(location.address) is int], "server_options": get_options()["server_options"], "er_hint_data": er_hint_data, "precollected_items": precollected_items }).encode("utf-8"), 9) with open(output_path('%s.multidata' % outfilebase), 'wb') as f: f.write(multidata) if not args.skip_playthrough: logger.info('Calculating playthrough.') create_playthrough(world) if args.create_spoiler: world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase)) logger.info('Done. Enjoy.') logger.debug('Total Time: %s', time.perf_counter() - start) return world
import zlib with open(rom, 'rb') as fr: multidata = zlib.decompress(fr.read()).decode("utf-8") with open(rom + '.txt', 'w') as fw: fw.write(multidata) multidata = json.loads(multidata) for romname in multidata['roms']: Utils.persistent_store( "servers", "".join(chr(byte) for byte in romname[2]), address) from Utils import get_options multidata["server_options"] = get_options( )["server_options"] multidata = zlib.compress( json.dumps(multidata).encode("utf-8"), 9) with open(rom + "_updated.archipelago", 'wb') as f: f.write(multidata) elif rom.endswith(".zip"): print(f"Updating host in patch files contained in {rom}") def _handle_zip_file_entry(zfinfo: zipfile.ZipInfo, server: str): data = zfr.read(zfinfo) if zfinfo.filename.endswith(".apbp"): data = update_patch_data(data, server) with ziplock: zfw.writestr(zfinfo, data)
def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = None): if not baked_server_options: baked_server_options = get_options()["server_options"] if args.outputpath: os.makedirs(args.outputpath, exist_ok=True) output_path.cached_path = args.outputpath start = time.perf_counter() # initialize the world world = MultiWorld(args.multi) logger = logging.getLogger() world.set_seed(seed, args.race, str(args.outputname if args.outputname else world.seed)) 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() world.open_pyramid = args.open_pyramid.copy() world.boss_shuffle = args.shufflebosses.copy() world.enemy_health = args.enemy_health.copy() world.enemy_damage = args.enemy_damage.copy() world.beemizer_total_chance = args.beemizer_total_chance.copy() world.beemizer_trap_chance = args.beemizer_trap_chance.copy() world.timer = args.timer.copy() 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() world.dungeon_counters = args.dungeon_counters.copy() world.triforce_pieces_available = args.triforce_pieces_available.copy() world.triforce_pieces_required = args.triforce_pieces_required.copy() world.shop_shuffle = args.shop_shuffle.copy() world.shuffle_prizes = args.shuffle_prizes.copy() world.sprite_pool = args.sprite_pool.copy() world.dark_room_logic = args.dark_room_logic.copy() world.plando_items = args.plando_items.copy() world.plando_texts = args.plando_texts.copy() world.plando_connections = args.plando_connections.copy() world.required_medallions = args.required_medallions.copy() world.game = args.game.copy() world.player_name = args.name.copy() world.enemizer = args.enemizercli world.sprite = args.sprite.copy() world.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option. world.set_options(args) world.set_item_links() world.state = CollectionState(world) logger.info('Archipelago Version %s - Seed: %s\n', __version__, world.seed) logger.info("Found World Types:") longest_name = max(len(text) for text in AutoWorld.AutoWorldRegister.world_types) numlength = 8 for name, cls in AutoWorld.AutoWorldRegister.world_types.items(): if not cls.hidden: logger.info(f" {name:{longest_name}}: {len(cls.item_names):3} " f"Items (IDs: {min(cls.item_id_to_name):{numlength}} - " f"{max(cls.item_id_to_name):{numlength}}) | " f"{len(cls.location_names):3} " f"Locations (IDs: {min(cls.location_id_to_name):{numlength}} - " f"{max(cls.location_id_to_name):{numlength}})") AutoWorld.call_stage(world, "assert_generate") AutoWorld.call_all(world, "generate_early") logger.info('') for player in world.player_ids: for item_name, count in world.start_inventory[player].value.items(): for _ in range(count): world.push_precollected(world.create_item(item_name, player)) for player in world.player_ids: if player in world.get_game_players("A Link to the Past"): # enforce pre-defined local items. if world.goal[player] in ["localtriforcehunt", "localganontriforcehunt"]: world.local_items[player].value.add('Triforce Piece') # Not possible to place pendants/crystals out side of boss prizes yet. world.non_local_items[player].value -= item_name_groups['Pendants'] world.non_local_items[player].value -= item_name_groups['Crystals'] # items can't be both local and non-local, prefer local world.non_local_items[player].value -= world.local_items[player].value logger.info('Creating World.') AutoWorld.call_all(world, "create_regions") logger.info('Creating Items.') AutoWorld.call_all(world, "create_items") logger.info('Calculating Access Rules.') if world.players > 1: for player in world.player_ids: locality_rules(world, player) group_locality_rules(world) else: world.non_local_items[1].value = set() world.local_items[1].value = set() AutoWorld.call_all(world, "set_rules") for player in world.player_ids: exclusion_rules(world, player, world.exclude_locations[player].value) 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 AutoWorld.call_all(world, "generate_basic") # temporary home for item links, should be moved out of Main for group_id, group in world.groups.items(): def find_common_pool(players: Set[int], shared_pool: Set[str]): classifications = collections.defaultdict(int) 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 classifications[item.name] |= item.classification for player in players.copy(): if all([counters[player][item] == 0 for item in shared_pool]): players.remove(player) del(counters[player]) if not players: return None, None 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: del(counters[player][item]) return counters, classifications common_item_count, classifications = find_common_pool(group["players"], group["item_pool"]) if not common_item_count: continue new_itempool = [] for item_name, item_count in next(iter(common_item_count.values())).items(): for _ in range(item_count): new_item = group["world"].create_item(item_name) # mangle together all original classification bits new_item.classification |= classifications[item_name] new_itempool.append(new_item) region = Region("Menu", RegionType.Generic, "ItemLink", group_id, world) world.regions.append(region) locations = region.locations = [] for item in world.itempool: 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) loc.access_rule = lambda state, item_name = item.name, group_id_ = group_id, count_ = count: \ state.has(item_name, group_id_, count_) locations.append(loc) loc.place_locked_item(item) 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): items_to_add = [] for player in group["players"]: if group["replacement_items"][player]: items_to_add.append(AutoWorld.call_single(world, "create_item", player, group["replacement_items"][player])) else: items_to_add.append(AutoWorld.call_single(world, "create_filler", player)) world.random.shuffle(items_to_add) world.itempool.extend(items_to_add[:itemcount - len(world.itempool)]) if any(world.item_links.values()): world._recache() world._all_state = None logger.info("Running Item Plando") for item in world.itempool: item.world = world distribute_planned(world) logger.info('Running Pre Main Fill.') AutoWorld.call_all(world, "pre_fill") logger.info(f'Filling the world with {len(world.itempool)} items.') if world.algorithm == 'flood': flood_items(world) # different algo, biased towards early game progress items elif world.algorithm == 'balanced': distribute_items_restrictive(world) AutoWorld.call_all(world, 'post_fill') if world.players > 1: balance_multiworld_progression(world) logger.info(f'Beginning output...') outfilebase = 'AP_' + world.seed_name output = tempfile.TemporaryDirectory() with output as temp_dir: with concurrent.futures.ThreadPoolExecutor(world.players + 2) as pool: check_accessibility_task = pool.submit(world.fulfills_accessibility) output_file_futures = [pool.submit(AutoWorld.call_stage, world, "generate_output", temp_dir)] 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__: output_file_futures.append( pool.submit(AutoWorld.call_single, world, "generate_output", player, temp_dir)) def get_entrance_to_region(region: Region): for entrance in region.entrances: if entrance.parent_region.type in (RegionType.DarkWorld, RegionType.LightWorld, RegionType.Generic): return entrance for entrance in region.entrances: # BFS might be better here, trying DFS for now. return get_entrance_to_region(entrance.parent_region) # collect ER hint info er_hint_data = {player: {} for player in world.get_game_players("A Link to the Past") if world.shuffle[player] != "vanilla" or world.retro_caves[player]} for region in world.regions: if region.player in er_hint_data and region.locations: main_entrance = get_entrance_to_region(region) for location in region.locations: if type(location.address) == int: # skips events and crystals if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name: er_hint_data[region.player][location.address] = main_entrance.name checks_in_area = {player: {area: list() for area in ordered_areas} for player in range(1, world.players + 1)} for player in range(1, world.players + 1): checks_in_area[player]["Total"] = 0 for location in world.get_filled_locations(): if type(location.address) is int: main_entrance = get_entrance_to_region(location.parent_region) if location.game != "A Link to the Past": checks_in_area[location.player]["Light World"].append(location.address) elif location.parent_region.dungeon: dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower', 'Inverted Ganons Tower': 'Ganons Tower'} \ .get(location.parent_region.dungeon.name, location.parent_region.dungeon.name) checks_in_area[location.player][dungeonname].append(location.address) elif location.parent_region.type == RegionType.LightWorld: checks_in_area[location.player]["Light World"].append(location.address) elif location.parent_region.type == RegionType.DarkWorld: checks_in_area[location.player]["Dark World"].append(location.address) elif main_entrance.parent_region.type == RegionType.LightWorld: checks_in_area[location.player]["Light World"].append(location.address) elif main_entrance.parent_region.type == RegionType.DarkWorld: checks_in_area[location.player]["Dark World"].append(location.address) checks_in_area[location.player]["Total"] += 1 oldmancaves = [] takeanyregions = ["Old Man Sword Cave", "Take-Any #1", "Take-Any #2", "Take-Any #3", "Take-Any #4"] for index, take_any in enumerate(takeanyregions): for region in [world.get_region(take_any, player) for player in world.get_game_players("A Link to the Past") if world.retro_caves[player]]: item = world.create_item( region.shop.inventory[(0 if take_any == "Old Man Sword Cave" else 1)]['item'], region.player) player = region.player location_id = SHOP_ID_START + total_shop_slots + index main_entrance = get_entrance_to_region(region) if main_entrance.parent_region.type == RegionType.LightWorld: checks_in_area[player]["Light World"].append(location_id) else: checks_in_area[player]["Dark World"].append(location_id) checks_in_area[player]["Total"] += 1 er_hint_data[player][location_id] = main_entrance.name oldmancaves.append(((location_id, player), (item.code, player))) FillDisabledShopSlots(world) def write_multidata(): import NetUtils slot_data = {} client_versions = {} games = {} minimum_versions = {"server": AutoWorld.World.required_server_version, "clients": client_versions} slot_info = {} names = [[name for player, name in sorted(world.player_name.items())]] for slot in world.player_ids: 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 games[slot] = world.game[slot] 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"])) precollected_items = {player: [item.code for item in world_precollected if type(item.code) == int] for player, world_precollected in world.precollected_items.items()} precollected_hints = {player: set() for player in range(1, world.players + 1 + len(world.groups))} for slot in world.player_ids: slot_data[slot] = world.worlds[slot].fill_slot_data() def precollect_hint(location): entrance = er_hint_data.get(location.player, {}).get(location.address, "") hint = NetUtils.Hint(location.item.player, location.player, location.address, location.item.code, False, entrance, location.item.flags) precollected_hints[location.player].add(hint) if location.item.player not in world.groups: precollected_hints[location.item.player].add(hint) else: for player in world.groups[location.item.player]["players"]: precollected_hints[player].add(hint) locations_data: Dict[int, Dict[int, Tuple[int, int, int]]] = {player: {} for player in world.player_ids} for location in world.get_filled_locations(): if type(location.address) == int: assert location.item.code is not None, "item code None should be event, " \ "location.address should then also be None" locations_data[location.player][location.address] = \ location.item.code, location.item.player, location.item.flags if location.name in world.start_location_hints[location.player]: precollect_hint(location) elif location.item.name in world.start_hints[location.item.player]: precollect_hint(location) elif any([location.item.name in world.start_hints[player] for player in world.groups.get(location.item.player, {}).get("players", [])]): precollect_hint(location) multidata = { "slot_data": slot_data, "slot_info": slot_info, "names": names, # TODO: remove around 0.2.5 in favor of slot_info "games": games, # TODO: remove around 0.2.5 in favor of slot_info "connect_names": {name: (0, player) for player, name in world.player_name.items()}, "remote_items": {player for player in world.player_ids if world.worlds[player].remote_items}, "remote_start_inventory": {player for player in world.player_ids if world.worlds[player].remote_start_inventory}, "locations": locations_data, "checks_in_area": checks_in_area, "server_options": baked_server_options, "er_hint_data": er_hint_data, "precollected_items": precollected_items, "precollected_hints": precollected_hints, "version": tuple(version_tuple), "tags": ["AP"], "minimum_versions": minimum_versions, "seed_name": world.seed_name } 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: f.write(bytes([3])) # version of format f.write(multidata) multidata_task = pool.submit(write_multidata) 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.") # retrieve exceptions via .result() if they occurred. multidata_task.result() for i, future in enumerate(concurrent.futures.as_completed(output_file_futures), start=1): if i % 10 == 0 or i == len(output_file_futures): logger.info(f'Generating output files ({i}/{len(output_file_futures)}).') future.result() if args.spoiler > 1: logger.info('Calculating playthrough.') create_playthrough(world) if args.spoiler: world.spoiler.to_file(os.path.join(temp_dir, '%s_Spoiler.txt' % outfilebase)) zipfilename = output_path(f"AP_{world.seed_name}.zip") logger.info(f'Creating final archive at {zipfilename}.') with zipfile.ZipFile(zipfilename, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zf: for file in os.scandir(temp_dir): zf.write(file.path, arcname=file.name) logger.info('Done. Enjoy. Total Time: %s', time.perf_counter() - start) return world
def mystery_argparse(): options = get_options() defaults = options["generator"] def resolve_path(path: str, resolver: Callable[[str], str]) -> str: return path if os.path.isabs(path) else resolver(path) parser = argparse.ArgumentParser( description="CMD Generation Interface, defaults come from host.yaml.") parser.add_argument( '--weights_file_path', default=defaults["weights_file_path"], help= 'Path to the weights file to use for rolling game settings, urls are also valid' ) parser.add_argument( '--samesettings', help='Rolls settings per weights file rather than per player', action='store_true') parser.add_argument('--player_files_path', default=resolve_path(defaults["player_files_path"], user_path), help="Input directory for player files.") parser.add_argument('--seed', help='Define seed number to generate.', type=int) parser.add_argument('--multi', default=defaults["players"], type=lambda value: max(int(value), 1)) parser.add_argument('--spoiler', type=int, default=defaults["spoiler"]) parser.add_argument('--lttp_rom', default=options["lttp_options"]["rom_file"], help="Path to the 1.0 JP LttP Baserom." ) # absolute, relative to cwd or relative to app path parser.add_argument('--sm_rom', default=options["sm_options"]["rom_file"], help="Path to the 1.0 JP SM Baserom.") parser.add_argument('--enemizercli', default=resolve_path(defaults["enemizer_path"], local_path)) parser.add_argument( '--outputpath', default=resolve_path(options["general_options"]["output_path"], user_path), help="Path to output folder. Absolute or relative to cwd." ) # absolute or relative to cwd parser.add_argument('--race', action='store_true', default=defaults["race"]) parser.add_argument('--meta_file_path', default=defaults["meta_file_path"]) parser.add_argument('--log_level', default='info', help='Sets log level') parser.add_argument( '--yaml_output', default=0, type=lambda value: max(int(value), 0), help= 'Output rolled mystery results to yaml up to specified number (made for async multiworld)' ) parser.add_argument( '--plando', default=defaults["plando_options"], help= 'List of options that can be set manually. Can be combined, for example "bosses, items"' ) args = parser.parse_args() if not os.path.isabs(args.weights_file_path): args.weights_file_path = os.path.join(args.player_files_path, args.weights_file_path) if not os.path.isabs(args.meta_file_path): args.meta_file_path = os.path.join(args.player_files_path, args.meta_file_path) args.plando: Set[str] = { arg.strip().lower() for arg in args.plando.split(",") } return args, options
def write_multidata(roms, mods): import base64 for future in roms: rom_name = future.result() rom_names.append(rom_name) client_versions = {} minimum_versions = { "server": (0, 0, 3), "clients": client_versions } games = {} for slot in world.player_ids: client_versions[slot] = (0, 0, 3) games[slot] = world.game[slot] connect_names = { base64.b64encode(rom_name).decode(): (team, slot) for slot, team, rom_name in rom_names } for i, team in enumerate(parsed_names): for player, name in enumerate(team, 1): if player not in world.alttp_player_ids: connect_names[name] = (i, player) multidata = zlib.compress( pickle.dumps({ "games": games, "names": parsed_names, "connect_names": connect_names, "remote_items": { player for player in range(1, world.players + 1) if world.remote_items[player] or world.game[player] != "A Link to the Past" }, "locations": {(location.address, location.player): (location.item.code, location.item.player) for location in world.get_filled_locations() if type(location.address) is int}, "checks_in_area": checks_in_area, "server_options": get_options()["server_options"], "er_hint_data": er_hint_data, "precollected_items": precollected_items, "version": tuple(_version_tuple), "tags": ["AP"], "minimum_versions": minimum_versions, "seed_name": str(args.outputname if args.outputname else world.seed) }), 9) with open(output_path('%s.archipelago' % outfilebase), 'wb') as f: f.write(bytes([1])) # version of format f.write(multidata) for future in mods: future.result() # collect errors if they occured