Example #1
0
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
Example #2
0
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