def copy_world(world): # ToDo: Not good yet ret = World(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.retro, world.custom, world.customitemarray, world.hints) ret.teams = world.teams ret.player_names = copy.deepcopy(world.player_names) ret.remote_items = world.remote_items.copy() ret.required_medallions = world.required_medallions.copy() ret.swamp_patch_required = world.swamp_patch_required.copy() ret.ganon_at_pyramid = world.ganon_at_pyramid.copy() ret.powder_patch_required = world.powder_patch_required.copy() ret.ganonstower_vanilla = world.ganonstower_vanilla.copy() ret.treasure_hunt_count = world.treasure_hunt_count.copy() ret.treasure_hunt_icon = world.treasure_hunt_icon.copy() ret.sewer_light_cone = world.sewer_light_cone.copy() ret.light_world_light_cone = world.light_world_light_cone ret.dark_world_light_cone = world.dark_world_light_cone ret.seed = world.seed ret.can_access_trock_eyebridge = world.can_access_trock_eyebridge.copy() ret.can_access_trock_front = world.can_access_trock_front.copy() ret.can_access_trock_big_chest = world.can_access_trock_big_chest.copy() ret.can_access_trock_middle = world.can_access_trock_middle.copy() ret.can_take_damage = world.can_take_damage ret.difficulty_requirements = world.difficulty_requirements.copy() ret.fix_fake_world = world.fix_fake_world.copy() ret.lamps_needed_for_dark_rooms = world.lamps_needed_for_dark_rooms ret.mapshuffle = world.mapshuffle.copy() ret.compassshuffle = world.compassshuffle.copy() ret.keyshuffle = world.keyshuffle.copy() ret.bigkeyshuffle = world.bigkeyshuffle.copy() ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy() ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy() ret.open_pyramid = world.open_pyramid.copy() ret.boss_shuffle = world.boss_shuffle.copy() ret.enemy_shuffle = world.enemy_shuffle.copy() ret.enemy_health = world.enemy_health.copy() ret.enemy_damage = world.enemy_damage.copy() ret.beemizer = world.beemizer.copy() ret.timer = world.timer.copy() ret.shufflepots = world.shufflepots.copy() ret.extendedmsu = world.extendedmsu.copy() for player in range(1, world.players + 1): if world.mode[player] != 'inverted': create_regions(ret, player) else: create_inverted_regions(ret, player) create_shops(ret, player) create_dungeons(ret, player) copy_dynamic_regions_and_locations(world, ret) # copy bosses for dungeon in world.dungeons: for level, boss in dungeon.bosses.items(): ret.get_dungeon(dungeon.name, dungeon.player).bosses[level] = boss for shop in world.shops: copied_shop = ret.get_region(shop.region.name, shop.region.player).shop copied_shop.inventory = copy.copy(shop.inventory) # connect copied world for region in world.regions: copied_region = ret.get_region(region.name, region.player) copied_region.is_light_world = region.is_light_world copied_region.is_dark_world = region.is_dark_world for entrance in region.entrances: ret.get_entrance(entrance.name, entrance.player).connect(copied_region) # fill locations for location in world.get_locations(): if location.item is not None: item = Item(location.item.name, location.item.advancement, location.item.priority, location.item.type, player=location.item.player) ret.get_location(location.name, location.player).item = item item.location = ret.get_location(location.name, location.player) item.world = ret if location.event: ret.get_location(location.name, location.player).event = True if location.locked: ret.get_location(location.name, location.player).locked = True # copy remaining itempool. No item in itempool should have an assigned location for item in world.itempool: ret.itempool.append( Item(item.name, item.advancement, item.priority, item.type, player=item.player)) for item in world.precollected_items: ret.push_precollected(ItemFactory(item.name, item.player)) # copy progress items in state ret.state.prog_items = world.state.prog_items.copy() ret.state.stale = {player: True for player in range(1, world.players + 1)} for player in range(1, world.players + 1): set_rules(ret, player) return ret
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('') if seed is None: random.seed(None) world.seed = random.randint(0, 999999999) else: world.seed = int(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.extendedmsu = args.extendedmsu.copy() world.glitch_boots = args.glitch_boots.copy() 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) 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: logger.info('Balancing multiworld progression.') balance_multiworld_progression(world) logger.info('Patching ROM.') outfilebase = 'ER_%s' % (args.outputname if args.outputname else world.seed) rom_names = [] jsonout = {} 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 = JsonRom() if args.jsonout or use_enemizer else LocalRom( args.rom, extendedmsu=args.extendedmsu[player]) patch_rom(world, rom, player, team, use_enemizer) if use_enemizer and (args.enemizercli or not args.jsonout): patch_enemizer(world, player, rom, args.rom, args.enemizercli, args.shufflepots[player], sprite_random_on_hit, extendedmsu=args.extendedmsu[player]) if not args.jsonout: rom = LocalRom.fromJsonRom(rom, args.rom, 0x400000, args.extendedmsu[player]) 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]) if args.jsonout: jsonout[f'patch_t{team}_p{player}'] = rom.patches else: 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) 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"] }).encode("utf-8")) if args.jsonout: jsonout["multidata"] = list(multidata) else: 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.jsonout: print(json.dumps({**jsonout, 'spoiler': world.spoiler.to_json()})) elif args.create_spoiler and not args.skip_playthrough: 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