Esempio n. 1
0
    def fill(self, window, worlds, location_pools, item_pools):
        search = Search.max_explore([world.state for world in worlds], itertools.chain.from_iterable(item_pools))
        if not search.can_beat_game(False):
            raise FillError('Item pool does not contain items required to beat game!')

        for world_dist in self.world_dists:
            world_dist.fill(window, worlds, location_pools, item_pools)
Esempio n. 2
0
def fill_prizes(world, attempts=15):
    all_state = world.get_all_state(keys=True)
    for player in range(1, world.players + 1):
        crystals = ItemFactory(['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6'], player)
        crystal_locations = [world.get_location('Turtle Rock - Prize', player), world.get_location('Eastern Palace - Prize', player), world.get_location('Desert Palace - Prize', player), world.get_location('Tower of Hera - Prize', player), world.get_location('Palace of Darkness - Prize', player),
                             world.get_location('Thieves\' Town - Prize', player), world.get_location('Skull Woods - Prize', player), world.get_location('Swamp Palace - Prize', player), world.get_location('Ice Palace - Prize', player),
                             world.get_location('Misery Mire - Prize', player)]
        placed_prizes = [loc.item.name for loc in crystal_locations if loc.item is not None]
        unplaced_prizes = [crystal for crystal in crystals if crystal.name not in placed_prizes]
        empty_crystal_locations = [loc for loc in crystal_locations if loc.item is None]
        for attempt in range(attempts):
            try:
                prizepool = list(unplaced_prizes)
                prize_locs = list(empty_crystal_locations)
                world.random.shuffle(prizepool)
                world.random.shuffle(prize_locs)
                fill_restrictive(world, all_state, prize_locs, prizepool, True)
            except FillError as e:
                logging.getLogger('').exception("Failed to place dungeon prizes (%s). Will retry %s more times", e,
                                                attempts - attempt)
                for location in empty_crystal_locations:
                    location.item = None
                continue
            break
        else:
            raise FillError('Unable to place dungeon prizes')
Esempio n. 3
0
    def fill(self, window, worlds, location_pools, item_pools):
        world = worlds[self.id]
        for (location_name, record) in pattern_dict_items(self.locations):
            if record.item is None:
                continue

            player_id = self.id if record.player is None else record.player - 1

            location_matcher = lambda loc: loc.world.id == world.id and loc.name == location_name
            location = pull_first_element(location_pools, location_matcher)
            if location is None:
                try:
                    location = LocationFactory(location_name)
                except KeyError:
                    raise RuntimeError('Unknown location in world %d: %s' %
                                       (world.id + 1, name))
                if location.type == 'Boss':
                    continue
                else:
                    raise RuntimeError(
                        'Location already filled in world %d: %s' %
                        (self.id + 1, location_name))

            try:
                item = self.pool_remove_item(item_pools,
                                             record.item,
                                             1,
                                             world_id=player_id)[0]
            except KeyError:
                try:
                    self.pool_remove_item(item_pools,
                                          "#Junk",
                                          1,
                                          world_id=player_id)
                    item_matcher = lambda item: pattern_matcher(record.item)(
                        item.name)
                    item = random.choice(
                        list(ItemIterator(item_matcher, worlds[player_id])))
                except KeyError:
                    raise RuntimeError(
                        'Too many items were added to world %d, and not enough junk is available to be removed.'
                        % (self.id + 1))

            if record.price is not None:
                item.price = record.price
            location.world.push_item(location, item, True)
            if item.advancement:
                playthrough = Playthrough.max_explore(
                    [world.state for world in worlds],
                    itertools.chain.from_iterable(item_pools))
                if not playthrough.can_beat_game(False):
                    raise FillError(
                        '%s in world %d is not reachable without %s in world %d!'
                        %
                        (location.name, self.id + 1, item.name, player_id + 1))
            window.fillcount += 1
            window.update_progress(5 + (
                (window.fillcount / window.locationcount) * 30))
Esempio n. 4
0
 def pre_fill(self):
     from Fill import fill_restrictive, FillError
     attempts = 5
     world = self.world
     player = self.player
     all_state = world.get_all_state(use_cache=True)
     crystals = [
         self.create_item(name) for name in [
             'Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1',
             'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7',
             'Crystal 5', 'Crystal 6'
         ]
     ]
     crystal_locations = [
         world.get_location('Turtle Rock - Prize', player),
         world.get_location('Eastern Palace - Prize', player),
         world.get_location('Desert Palace - Prize', player),
         world.get_location('Tower of Hera - Prize', player),
         world.get_location('Palace of Darkness - Prize', player),
         world.get_location('Thieves\' Town - Prize', player),
         world.get_location('Skull Woods - Prize', player),
         world.get_location('Swamp Palace - Prize', player),
         world.get_location('Ice Palace - Prize', player),
         world.get_location('Misery Mire - Prize', player)
     ]
     placed_prizes = {
         loc.item.name
         for loc in crystal_locations if loc.item
     }
     unplaced_prizes = [
         crystal for crystal in crystals
         if crystal.name not in placed_prizes
     ]
     empty_crystal_locations = [
         loc for loc in crystal_locations if not loc.item
     ]
     for attempt in range(attempts):
         try:
             prizepool = unplaced_prizes.copy()
             prize_locs = empty_crystal_locations.copy()
             world.random.shuffle(prize_locs)
             fill_restrictive(world,
                              all_state,
                              prize_locs,
                              prizepool,
                              True,
                              lock=True)
         except FillError as e:
             lttp_logger.exception(
                 "Failed to place dungeon prizes (%s). Will retry %s more times",
                 e, attempts - attempt)
             for location in empty_crystal_locations:
                 location.item = None
             continue
         break
     else:
         raise FillError('Unable to place dungeon prizes')
Esempio n. 5
0
    def fill(self, window, worlds, location_pools, item_pools):
        max_states = State.get_states_with_items(
            [world.state for world in worlds],
            reduce(lambda a, b: a + b, item_pools))
        if not State.can_beat_game(max_states, True):
            raise FillError(
                'Item pool does not contain items required to beat game!')

        for world_dist in self.world_dists:
            world_dist.fill(window, worlds, location_pools, item_pools)
Esempio n. 6
0
def fill_songs(world, attempts=15):
    songs = ItemFactory(songlist)
    song_locations = [
        world.get_location('Song from Composer Grave'),
        world.get_location('Impa at Castle'),
        world.get_location('Song from Malon'),
        world.get_location('Song from Saria'),
        world.get_location('Song from Ocarina of Time'),
        world.get_location('Song at Windmill'),
        world.get_location('Sheik Forest Song'),
        world.get_location('Sheik at Temple'),
        world.get_location('Sheik in Crater'),
        world.get_location('Sheik in Ice Cavern'),
        world.get_location('Sheik in Kakariko'),
        world.get_location('Sheik at Colossus')
    ]
    placed_prizes = [
        loc.item.name for loc in song_locations if loc.item is not None
    ]
    unplaced_prizes = [
        song for song in songs if song.name not in placed_prizes
    ]
    empty_song_locations = [loc for loc in song_locations if loc.item is None]

    while attempts:
        attempts -= 1
        try:
            prizepool = list(unplaced_prizes)
            prize_locs = list(empty_song_locations)
            random.shuffle(prizepool)
            random.shuffle(prize_locs)
            fill_restrictive(
                world, world.get_all_state(keys=True), prize_locs, prizepool
            )  #TODO: Set keys to true once keys are properly implemented
        except FillError:
            logging.getLogger('').info(
                "Failed to place songs. Will retry %s more times", attempts)
            for location in empty_song_locations:
                location.item = None
            continue
        break
    else:
        raise FillError('Unable to place songs')
Esempio n. 7
0
def fill_songs(world, attempts=15):
    songs = ItemFactory(songlist)
    song_locations = [
        world.get_location('Song from Skull Kid'),
        world.get_location('Song from HMS'),
        world.get_location('Song from Owl Tablet'),
        world.get_location('Song from Romani'),
        world.get_location('Song at Grave'),
        world.get_location('Song from Monkey'),
        world.get_location('Song from Baby Goron'),
        world.get_location('Song from Goron Elder'),
        world.get_location('Song from Zora Eggs'),
        world.get_location('Song from Igos'),
        world.get_location('Song from the Giants')
    ]
    placed_prizes = [
        loc.item.name for loc in song_locations if loc.item is not None
    ]
    unplaced_prizes = [
        song for song in songs if song.name not in placed_prizes
    ]
    empty_song_locations = [loc for loc in song_locations if loc.item is None]

    while attempts:
        attempts -= 1
        try:
            prizepool = list(unplaced_prizes)
            prize_locs = list(empty_song_locations)
            random.shuffle(prizepool)
            random.shuffle(prize_locs)
            fill_restrictive(
                world, world.get_all_state(keys=True), prize_locs, prizepool
            )  #TODO: Set keys to true once keys are properly implemented
        except FillError:
            logging.getLogger('').info(
                "Failed to place songs. Will retry %s more times", attempts)
            for location in empty_song_locations:
                location.item = None
            continue
        break
    else:
        raise FillError('Unable to place songs')
Esempio n. 8
0
def generate_itempool(world, player: int):
    if world.difficulty[player] not in difficulties:
        raise NotImplementedError(f"Diffulty {world.difficulty[player]}")
    if world.goal[player] not in {
            'ganon', 'pedestal', 'bosses', 'triforcehunt', 'localtriforcehunt',
            'icerodhunt', 'ganontriforcehunt', 'localganontriforcehunt',
            'crystals', 'ganonpedestal'
    }:
        raise NotImplementedError(
            f"Goal {world.goal[player]} for player {player}")
    if world.mode[player] not in {'open', 'standard', 'inverted'}:
        raise NotImplementedError(
            f"Mode {world.mode[player]} for player {player}")
    if world.timer[player] not in {
            False, 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'
    }:
        raise NotImplementedError(
            f"Timer {world.mode[player]} for player {player}")

    if world.timer[player] in ['ohko', 'timed-ohko']:
        world.can_take_damage[player] = False
    if world.goal[player] in [
            'pedestal', 'triforcehunt', 'localtriforcehunt', 'icerodhunt'
    ]:
        world.push_item(world.get_location('Ganon', player),
                        ItemFactory('Nothing', player), False)
    else:
        world.push_item(world.get_location('Ganon', player),
                        ItemFactory('Triforce', player), False)

    if world.goal[player] in ['triforcehunt', 'localtriforcehunt']:
        region = world.get_region('Light World', player)

        loc = ALttPLocation(player, "Murahdahla", parent=region)
        loc.access_rule = lambda state: state.has_triforce_pieces(
            state.world.treasure_hunt_count[player], player)

        region.locations.append(loc)
        world.dynamic_locations.append(loc)

        world.clear_location_cache()

        world.push_item(loc, ItemFactory('Triforce', player), False)
        loc.event = True
        loc.locked = True

    if world.goal[player] == 'icerodhunt':
        world.progression_balancing[player] = False
        loc = world.get_location('Turtle Rock - Boss', player)
        world.push_item(loc, ItemFactory('Triforce', player), False)
        if world.boss_shuffle[player] != 'none':
            if 'turtle rock-' not in world.boss_shuffle[player]:
                world.boss_shuffle[
                    player] = f'Turtle Rock-Trinexx;{world.boss_shuffle[player]}'
            else:
                logging.warning(
                    f'Cannot guarantee that Trinexx is the boss of Turtle Rock for player {player}'
                )
        loc.event = True
        loc.locked = True
        forbid_items_for_player(
            loc, {
                'Red Pendant', 'Green Pendant', 'Blue Pendant', 'Crystal 5',
                'Crystal 6'
            }, player)
        itemdiff = difficulties[world.difficulty[player]]
        itempool = []
        itempool.extend(itemdiff.alwaysitems)
        itempool.remove('Ice Rod')

        itempool.extend(['Single Arrow', 'Sanctuary Heart Container'])
        itempool.extend(['Boss Heart Container'] *
                        itemdiff.boss_heart_container_limit)
        itempool.extend(['Piece of Heart'] * itemdiff.heart_piece_limit)
        itempool.extend(itemdiff.bottles)
        itempool.extend(itemdiff.basicbow)
        itempool.extend(itemdiff.basicarmor)
        if not world.swordless[player]:
            itempool.extend(itemdiff.basicsword)
        itempool.extend(itemdiff.basicmagic)
        itempool.extend(itemdiff.basicglove)
        itempool.extend(itemdiff.basicshield)
        itempool.extend(itemdiff.legacyinsanity)
        itempool.extend(['Rupees (300)'] * 34)
        itempool.extend(['Bombs (10)'] * 5)
        itempool.extend(['Arrows (10)'] * 7)
        if world.keyshuffle[player] == 'universal':
            itempool.extend(itemdiff.universal_keys)
            itempool.append('Small Key (Universal)')

        for item in itempool:
            world.push_precollected(ItemFactory(item, player))

    world.get_location('Ganon', player).event = True
    world.get_location('Ganon', player).locked = True
    event_pairs = [('Agahnim 1', 'Beat Agahnim 1'),
                   ('Agahnim 2', 'Beat Agahnim 2'),
                   ('Dark Blacksmith Ruins', 'Pick Up Purple Chest'),
                   ('Frog', 'Get Frog'), ('Missing Smith', 'Return Smith'),
                   ('Floodgate', 'Open Floodgate'),
                   ('Agahnim 1', 'Beat Agahnim 1'),
                   ('Flute Activation Spot', 'Activated Flute')]
    for location_name, event_name in event_pairs:
        location = world.get_location(location_name, player)
        event = ItemFactory(event_name, player)
        world.push_item(location, event, False)
        location.event = location.locked = True

    # set up item pool
    additional_triforce_pieces = 0
    if world.custom:
        (pool, placed_items, precollected_items, clock_mode,
         treasure_hunt_count,
         treasure_hunt_icon) = make_custom_item_pool(world, player)
        world.rupoor_cost = min(world.customitemarray[67], 9999)
    else:
        pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, \
        treasure_hunt_icon, additional_triforce_pieces = get_pool_core(world, player)

    for item in precollected_items:
        world.push_precollected(ItemFactory(item, player))

    if world.mode[player] == 'standard' and not world.state.has_melee_weapon(
            player):
        if "Link's Uncle" not in placed_items:
            found_sword = False
            found_bow = False
            possible_weapons = []
            for item in pool:
                if item in [
                        'Progressive Sword', 'Fighter Sword', 'Master Sword',
                        'Tempered Sword', 'Golden Sword'
                ]:
                    if not found_sword:
                        found_sword = True
                        possible_weapons.append(item)
                if item in ['Progressive Bow', 'Bow'] and not found_bow:
                    found_bow = True
                    possible_weapons.append(item)
                if item in [
                        'Hammer', 'Bombs (10)', 'Fire Rod', 'Cane of Somaria',
                        'Cane of Byrna'
                ]:
                    if item not in possible_weapons:
                        possible_weapons.append(item)
            starting_weapon = world.random.choice(possible_weapons)
            placed_items["Link's Uncle"] = starting_weapon
            pool.remove(starting_weapon)
        if placed_items["Link's Uncle"] in [
                'Bow', 'Progressive Bow', 'Bombs (10)', 'Cane of Somaria',
                'Cane of Byrna'
        ] and world.enemy_health[player] not in ['default', 'easy']:
            world.escape_assist[player].append('bombs')

    for (location, item) in placed_items.items():
        world.push_item(world.get_location(location, player),
                        ItemFactory(item, player), False)
        world.get_location(location, player).event = True
        world.get_location(location, player).locked = True

    items = ItemFactory(pool, player)

    if clock_mode is not None:
        world.clock_mode[player] = clock_mode

    if treasure_hunt_count is not None:
        world.treasure_hunt_count[player] = treasure_hunt_count % 999
    if treasure_hunt_icon is not None:
        world.treasure_hunt_icon[player] = treasure_hunt_icon

    dungeon_items = [
        item for item in get_dungeon_item_pool(world)
        if item.player == player and (
            (item.smallkey and world.keyshuffle[player]) or
            (item.bigkey and world.bigkeyshuffle[player]) or
            (item.map and world.mapshuffle[player]) or
            (item.compass and world.compassshuffle[player])
            or world.goal[player] == 'icerodhunt')
    ]

    if world.goal[player] == 'icerodhunt':
        for item in dungeon_items:
            world.itempool.append(
                ItemFactory(GetBeemizerItem(world, player, 'Nothing'), player))
            world.push_precollected(item)
    else:
        world.itempool.extend([item for item in dungeon_items])

    # logic has some branches where having 4 hearts is one possible requirement (of several alternatives)
    # rather than making all hearts/heart pieces progression items (which slows down generation considerably)
    # We mark one random heart container as an advancement item (or 4 heart pieces in expert mode)
    if world.goal[player] != 'icerodhunt' and world.difficulty[player] in [
            'easy', 'normal', 'hard'
    ] and not (world.custom and world.customitemarray[30] == 0):
        next(item for item in items
             if item.name == 'Boss Heart Container').advancement = True
    elif world.goal[player] != 'icerodhunt' and world.difficulty[player] in [
            'expert'
    ] and not (world.custom and world.customitemarray[29] < 4):
        adv_heart_pieces = (item for item in items
                            if item.name == 'Piece of Heart')
        for i in range(4):
            next(adv_heart_pieces).advancement = True

    progressionitems = []
    nonprogressionitems = []
    for item in items:
        if item.advancement or item.type:
            progressionitems.append(item)
        else:
            nonprogressionitems.append(
                GetBeemizerItem(world, item.player, item))
    world.random.shuffle(nonprogressionitems)

    if additional_triforce_pieces:
        if additional_triforce_pieces > len(nonprogressionitems):
            raise FillError(
                f"Not enough non-progression items to replace with Triforce pieces found for player "
                f"{world.get_player_names(player)}.")
        progressionitems += [ItemFactory("Triforce Piece", player)
                             ] * additional_triforce_pieces
        nonprogressionitems.sort(key=lambda item: int("Heart" in item.name)
                                 )  # try to keep hearts in the pool
        nonprogressionitems = nonprogressionitems[additional_triforce_pieces:]
        world.random.shuffle(nonprogressionitems)

    # shuffle medallions
    if world.required_medallions[player][0] == "random":
        mm_medallion = world.random.choice(['Ether', 'Quake', 'Bombos'])
    else:
        mm_medallion = world.required_medallions[player][0]
    if world.required_medallions[player][1] == "random":
        tr_medallion = world.random.choice(['Ether', 'Quake', 'Bombos'])
    else:
        tr_medallion = world.required_medallions[player][1]
    world.required_medallions[player] = (mm_medallion, tr_medallion)

    place_bosses(world, player)
    set_up_shops(world, player)

    if world.shop_shuffle[player]:
        shuffle_shops(world, nonprogressionitems, player)
    create_dynamic_shop_locations(world, player)

    world.itempool += progressionitems + nonprogressionitems

    if world.retro[player]:
        set_up_take_anys(world, player)  # depends on world.itempool to be set
Esempio n. 9
0
def place_bosses(world, player):
    if world.boss_shuffle[player] == 'none':
        return
    # Most to least restrictive order
    if world.mode[player] != 'inverted':
        boss_locations = [
            ['Ganons Tower', 'top'],
            ['Tower of Hera', None],
            ['Skull Woods', None],
            ['Ganons Tower', 'middle'],
            ['Eastern Palace', None],
            ['Desert Palace', None],
            ['Palace of Darkness', None],
            ['Swamp Palace', None],
            ['Thieves Town', None],
            ['Ice Palace', None],
            ['Misery Mire', None],
            ['Turtle Rock', None],
            ['Ganons Tower', 'bottom'],
        ]
    else:
        boss_locations = [
            ['Inverted Ganons Tower', 'top'],
            ['Tower of Hera', None],
            ['Skull Woods', None],
            ['Inverted Ganons Tower', 'middle'],
            ['Eastern Palace', None],
            ['Desert Palace', None],
            ['Palace of Darkness', None],
            ['Swamp Palace', None],
            ['Thieves Town', None],
            ['Ice Palace', None],
            ['Misery Mire', None],
            ['Turtle Rock', None],
            ['Inverted Ganons Tower', 'bottom'],
        ]

    all_bosses = sorted(
        boss_table.keys())  #s orted to be deterministic on older pythons
    placeable_bosses = [
        boss for boss in all_bosses
        if boss not in ['Agahnim', 'Agahnim2', 'Ganon']
    ]

    if world.boss_shuffle[player] in ["basic", "normal"]:
        # temporary hack for swordless kholdstare:
        if world.swords[player] == 'swordless':
            world.get_dungeon('Ice Palace',
                              player).boss = BossFactory('Kholdstare', player)
            logging.getLogger('').debug(
                'Placing boss Kholdstare at Ice Palace')
            boss_locations.remove(['Ice Palace', None])
            placeable_bosses.remove('Kholdstare')

        if world.boss_shuffle[player] == "basic":  # vanilla bosses shuffled
            bosses = placeable_bosses + [
                'Armos Knights', 'Lanmolas', 'Moldorm'
            ]
        else:  # all bosses present, the three duplicates chosen at random
            bosses = all_bosses + [
                random.choice(placeable_bosses) for _ in range(3)
            ]

        logging.getLogger('').debug('Bosses chosen %s', bosses)

        random.shuffle(bosses)
        for [loc, level] in boss_locations:
            loc_text = loc + (' (' + level + ')' if level else '')
            boss = next((b for b in bosses
                         if can_place_boss(world, player, b, loc, level)),
                        None)
            if not boss:
                raise FillError('Could not place boss for location %s' %
                                loc_text)
            bosses.remove(boss)

            logging.getLogger('').debug('Placing boss %s at %s', boss,
                                        loc_text)
            world.get_dungeon(loc, player).bosses[level] = BossFactory(
                boss, player)
    elif world.boss_shuffle[player] == "chaos":  #all bosses chosen at random
        for [loc, level] in boss_locations:
            loc_text = loc + (' (' + level + ')' if level else '')
            try:
                boss = random.choice([
                    b for b in placeable_bosses
                    if can_place_boss(world, player, b, loc, level)
                ])
            except IndexError:
                raise FillError('Could not place boss for location %s' %
                                loc_text)

            logging.getLogger('').debug('Placing boss %s at %s', boss,
                                        loc_text)
            world.get_dungeon(loc, player).bosses[level] = BossFactory(
                boss, player)
def place_bosses(world, player: int):
    if world.boss_shuffle[player] == 'none':
        return
    # Most to least restrictive order
    boss_locations = [
        ['Ganons Tower', 'top'],
        ['Tower of Hera', None],
        ['Skull Woods', None],
        ['Ganons Tower', 'middle'],
        ['Eastern Palace', None],
        ['Desert Palace', None],
        ['Palace of Darkness', None],
        ['Swamp Palace', None],
        ['Thieves Town', None],
        ['Ice Palace', None],
        ['Misery Mire', None],
        ['Turtle Rock', None],
        ['Ganons Tower', 'bottom'],
    ]

    all_bosses = sorted(boss_table.keys())  # sorted to be deterministic on older pythons
    placeable_bosses = [boss for boss in all_bosses if boss not in ['Agahnim', 'Agahnim2', 'Ganon']]

    if world.boss_shuffle[player] in ["basic", "normal"]:
        if world.boss_shuffle[player] == "basic":  # vanilla bosses shuffled
            bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm']
        else:  # all bosses present, the three duplicates chosen at random
            bosses = all_bosses + [world.random.choice(placeable_bosses) for _ in range(3)]

        logging.debug('Bosses chosen %s', bosses)

        world.random.shuffle(bosses)
        for loc, level in boss_locations:
            boss = next((b for b in bosses if can_place_boss(b, loc, level)), None)
            if not boss:
                loc_text = loc + (' (' + level + ')' if level else '')
                raise FillError('Could not place boss for location %s' % loc_text)
            bosses.remove(boss)
            place_boss(world, player, boss, loc, level)

    elif world.boss_shuffle[player] == "chaos":  # all bosses chosen at random
        for loc, level in boss_locations:
            try:
                boss = world.random.choice(
                    [b for b in placeable_bosses if can_place_boss(b, loc, level)])
            except IndexError:
                loc_text = loc + (' (' + level + ')' if level else '')
                raise FillError('Could not place boss for location %s' % loc_text)
            else:
                place_boss(world, player, boss, loc, level)

    elif world.boss_shuffle[player] == "singularity":
        primary_boss = world.random.choice(placeable_bosses)
        remaining_boss_locations = []
        for loc, level in boss_locations:
            # place that boss where it can go
            if can_place_boss(primary_boss, loc, level):
                place_boss(world, player, primary_boss, loc, level)
            else:
                remaining_boss_locations.append((loc, level))
        if remaining_boss_locations:
            # pick a boss to go into the remaining locations
            remaining_boss = world.random.choice([boss for boss in placeable_bosses if all(
                can_place_boss(boss, loc, level) for loc, level in remaining_boss_locations)])
            for loc, level in remaining_boss_locations:
                place_boss(world, player, remaining_boss, loc, level)
    else:
        raise FillError(f"Could not find boss shuffle mode {world.boss_shuffle[player]}")
Esempio n. 11
0
    def fill(self, window, worlds, location_pools, item_pools):
        world = worlds[self.id]
        locations = {}
        if self.locations:
            locations = {
                loc: self.locations[loc]
                for loc in random.sample(self.locations.keys(),
                                         len(self.locations))
            }
        for starting_item in self.starting_items:
            for _ in range(self.starting_items[starting_item].count):
                try:
                    if starting_item in item_groups['DungeonReward']:
                        continue
                    item = None
                    if starting_item in item_groups['Bottle']:
                        item = self.pool_replace_item(item_pools, "#Bottle",
                                                      self.id, "#Junk", worlds)
                    elif starting_item in item_groups['AdultTrade']:
                        item = self.pool_replace_item(item_pools,
                                                      "#AdultTrade", self.id,
                                                      "#Junk", worlds)
                    elif IsItem(starting_item):
                        try:
                            item = self.pool_replace_item(
                                item_pools, starting_item, self.id, "#Junk",
                                worlds)
                        except KeyError:
                            pass  # If a normal item exceeds the item pool count, continue.
                except KeyError:
                    raise RuntimeError(
                        'Started with too many "%s" in world %d, and not enough "%s" are available in the item pool to be removed.'
                        % (starting_item, self.id + 1, starting_item))

                if starting_item in item_groups['Song']:
                    self.song_as_items = True

                # Update item_pool
                if item is not None:
                    if item not in self.item_pool:
                        self.item_pool[item.name] = ItemPoolRecord({
                            'type': 'set',
                            'count': 1
                        })
                    else:
                        self.item_pool[item.name].count += 1
                    item_pools[5].append(ItemFactory(item.name, world))
        for (location_name,
             record) in pattern_dict_items(locations, world.itempool, []):
            if record.item is None:
                continue

            player_id = self.id if record.player is None else record.player - 1

            location_matcher = lambda loc: loc.world.id == world.id and loc.name == location_name
            location = pull_first_element(location_pools, location_matcher)
            if location is None:
                try:
                    location = LocationFactory(location_name)
                except KeyError:
                    raise RuntimeError('Unknown location in world %d: %s' %
                                       (world.id + 1, location_name))
                if location.type == 'Boss':
                    continue
                elif location.name in world.disabled_locations:
                    continue
                else:
                    raise RuntimeError(
                        'Location already filled in world %d: %s' %
                        (self.id + 1, location_name))

            if record.item in item_groups['DungeonReward']:
                raise RuntimeError(
                    'Cannot place dungeon reward %s in world %d in location %s.'
                    % (record.item, self.id + 1, location_name))

            if record.item == '#Junk' and location.type == 'Song' and not world.shuffle_song_items:
                record.item = '#JunkSong'

            ignore_pools = None
            is_invert = pattern_matcher(record.item)('!')
            if is_invert and location.type != 'Song' and not world.shuffle_song_items:
                ignore_pools = [2]
            if is_invert and location.type == 'Song' and not world.shuffle_song_items:
                ignore_pools = [i for i in range(len(item_pools)) if i != 2]

            try:
                item = self.pool_remove_item(item_pools,
                                             record.item,
                                             1,
                                             world_id=player_id,
                                             ignore_pools=ignore_pools)[0]
            except KeyError:
                if location.type == 'Shop' and "Buy" in record.item:
                    try:
                        self.pool_remove_item([item_pools[0]],
                                              "Buy *",
                                              1,
                                              world_id=player_id)
                        item = ItemFactory([record.item], world=world)[0]
                    except KeyError:
                        raise RuntimeError(
                            'Too many shop buy items were added to world %d, and not enough shop buy items are available in the item pool to be removed.'
                            % (self.id + 1))
                elif record.item in item_groups['Bottle']:
                    try:
                        item = self.pool_replace_item(item_pools, "#Bottle",
                                                      player_id, record.item,
                                                      worlds)
                    except KeyError:
                        raise RuntimeError(
                            'Too many bottles were added to world %d, and not enough bottles are available in the item pool to be removed.'
                            % (self.id + 1))
                elif record.item in item_groups['AdultTrade']:
                    try:
                        item = self.pool_replace_item(item_pools,
                                                      "#AdultTrade", player_id,
                                                      record.item, worlds)
                    except KeyError:
                        raise RuntimeError(
                            'Too many adult trade items were added to world %d, and not enough adult trade items are available in the item pool to be removed.'
                            % (self.id + 1))
                else:
                    try:
                        item = self.pool_replace_item(item_pools, "#Junk",
                                                      player_id, record.item,
                                                      worlds)
                    except KeyError:
                        raise RuntimeError(
                            'Too many items were added to world %d, and not enough junk is available to be removed.'
                            % (self.id + 1))
                # Update item_pool
                if item.name not in self.item_pool:
                    self.item_pool[item.name] = ItemPoolRecord({
                        'type': 'set',
                        'count': 1
                    })
                else:
                    self.item_pool[item.name].count += 1
            except IndexError:
                raise RuntimeError(
                    'Unknown item %s being placed on location %s in world %d.'
                    % (record.item, location, self.id + 1))

            if record.price is not None and item.type != 'Shop':
                location.price = record.price
                world.shop_prices[location.name] = record.price

            if location.type == 'Song' and item.type != 'Song':
                self.song_as_items = True
            location.world.push_item(location, item, True)

            if item.advancement:
                search = Search.max_explore(
                    [world.state for world in worlds],
                    itertools.chain.from_iterable(item_pools))
                if not search.can_beat_game(False):
                    raise FillError(
                        '%s in world %d is not reachable without %s in world %d!'
                        %
                        (location.name, self.id + 1, item.name, player_id + 1))
            window.fillcount += 1
            window.update_progress(5 + (
                (window.fillcount / window.locationcount) * 30))
Esempio n. 12
0
def generate_itempool(world, player: int):
    if world.difficulty[player] not in difficulties:
        raise NotImplementedError(f"Diffulty {world.difficulty[player]}")
    if world.goal[player] not in {
            'ganon', 'pedestal', 'dungeons', 'triforcehunt',
            'localtriforcehunt', 'ganontriforcehunt', 'localganontriforcehunt',
            'crystals', 'ganonpedestal'
    }:
        raise NotImplementedError(f"Goal {world.goal[player]}")
    if world.mode[player] not in {'open', 'standard', 'inverted'}:
        raise NotImplementedError(f"Mode {world.mode[player]}")
    if world.timer[player] not in {
            False, 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'
    }:
        raise NotImplementedError(f"Timer {world.mode[player]}")

    if world.timer[player] in ['ohko', 'timed-ohko']:
        world.can_take_damage[player] = False
    if world.goal[player] in ['pedestal', 'triforcehunt', 'localtriforcehunt']:
        world.push_item(world.get_location('Ganon', player),
                        ItemFactory('Nothing', player), False)
    else:
        world.push_item(world.get_location('Ganon', player),
                        ItemFactory('Triforce', player), False)

    if world.goal[player] in ['triforcehunt', 'localtriforcehunt']:
        region = world.get_region('Light World', player)

        loc = Location(player, "Murahdahla", parent=region)
        loc.access_rule = lambda state: state.has_triforce_pieces(
            state.world.treasure_hunt_count[player], player)

        region.locations.append(loc)
        world.dynamic_locations.append(loc)

        world.clear_location_cache()

        world.push_item(loc, ItemFactory('Triforce', player), False)
        loc.event = True
        loc.locked = True

    world.get_location('Ganon', player).event = True
    world.get_location('Ganon', player).locked = True
    world.push_item(world.get_location('Agahnim 1', player),
                    ItemFactory('Beat Agahnim 1', player), False)
    world.get_location('Agahnim 1', player).event = True
    world.get_location('Agahnim 1', player).locked = True
    world.push_item(world.get_location('Agahnim 2', player),
                    ItemFactory('Beat Agahnim 2', player), False)
    world.get_location('Agahnim 2', player).event = True
    world.get_location('Agahnim 2', player).locked = True
    world.push_item(world.get_location('Dark Blacksmith Ruins', player),
                    ItemFactory('Pick Up Purple Chest', player), False)
    world.get_location('Dark Blacksmith Ruins', player).event = True
    world.get_location('Dark Blacksmith Ruins', player).locked = True
    world.push_item(world.get_location('Frog', player),
                    ItemFactory('Get Frog', player), False)
    world.get_location('Frog', player).event = True
    world.get_location('Frog', player).locked = True
    world.push_item(world.get_location('Missing Smith', player),
                    ItemFactory('Return Smith', player), False)
    world.get_location('Missing Smith', player).event = True
    world.get_location('Missing Smith', player).locked = True
    world.push_item(world.get_location('Floodgate', player),
                    ItemFactory('Open Floodgate', player), False)
    world.get_location('Floodgate', player).event = True
    world.get_location('Floodgate', player).locked = True

    # set up item pool
    additional_triforce_pieces = 0
    if world.custom:
        (pool, placed_items, precollected_items, clock_mode,
         treasure_hunt_count,
         treasure_hunt_icon) = make_custom_item_pool(world, player)
        world.rupoor_cost = min(world.customitemarray[67], 9999)
    else:
        pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, \
        treasure_hunt_icon, additional_triforce_pieces = get_pool_core(world, player)

    for item in precollected_items:
        world.push_precollected(ItemFactory(item, player))

    if world.mode[player] == 'standard' and not world.state.has_melee_weapon(
            player):
        if "Link's Uncle" not in placed_items:
            found_sword = False
            found_bow = False
            possible_weapons = []
            for item in pool:
                if item in [
                        'Progressive Sword', 'Fighter Sword', 'Master Sword',
                        'Tempered Sword', 'Golden Sword'
                ]:
                    if not found_sword and world.swords[player] != 'swordless':
                        found_sword = True
                        possible_weapons.append(item)
                if item in ['Progressive Bow', 'Bow'] and not found_bow:
                    found_bow = True
                    possible_weapons.append(item)
                if item in [
                        'Hammer', 'Bombs (10)', 'Fire Rod', 'Cane of Somaria',
                        'Cane of Byrna'
                ]:
                    if item not in possible_weapons:
                        possible_weapons.append(item)
            starting_weapon = world.random.choice(possible_weapons)
            placed_items["Link's Uncle"] = starting_weapon
            pool.remove(starting_weapon)
        if placed_items["Link's Uncle"] in [
                'Bow', 'Progressive Bow', 'Bombs (10)', 'Cane of Somaria',
                'Cane of Byrna'
        ] and world.enemy_health[player] not in ['default', 'easy']:
            world.escape_assist[player].append('bombs')

    for (location, item) in placed_items.items():
        world.push_item(world.get_location(location, player),
                        ItemFactory(item, player), False)
        world.get_location(location, player).event = True
        world.get_location(location, player).locked = True

    items = ItemFactory(pool, player)

    if clock_mode is not None:
        world.clock_mode[player] = clock_mode

    if treasure_hunt_count is not None:
        world.treasure_hunt_count[player] = treasure_hunt_count
    if treasure_hunt_icon is not None:
        world.treasure_hunt_icon[player] = treasure_hunt_icon

    world.itempool.extend([
        item for item in get_dungeon_item_pool(world)
        if item.player == player and (
            (item.smallkey and world.keyshuffle[player]) or
            (item.bigkey and world.bigkeyshuffle[player]) or
            (item.map and world.mapshuffle[player]) or
            (item.compass and world.compassshuffle[player]))
    ])

    # logic has some branches where having 4 hearts is one possible requirement (of several alternatives)
    # rather than making all hearts/heart pieces progression items (which slows down generation considerably)
    # We mark one random heart container as an advancement item (or 4 heart pieces in expert mode)
    if world.difficulty[player] in [
            'easy', 'normal', 'hard'
    ] and not (world.custom and world.customitemarray[30] == 0):
        next(item for item in items
             if item.name == 'Boss Heart Container').advancement = True
    elif world.difficulty[player] in [
            'expert'
    ] and not (world.custom and world.customitemarray[29] < 4):
        adv_heart_pieces = (item for item in items
                            if item.name == 'Piece of Heart')
        for i in range(4):
            next(adv_heart_pieces).advancement = True

    beeweights = {
        0: {
            None: 100
        },
        1: {
            None: 75,
            'trap': 25
        },
        2: {
            None: 40,
            'trap': 40,
            'bee': 20
        },
        3: {
            'trap': 50,
            'bee': 50
        },
        4: {
            'trap': 100
        }
    }

    def beemizer(item):
        if world.beemizer[
                item.
                player] and not item.advancement and not item.priority and not item.type:
            choice = world.random.choices(
                list(beeweights[world.beemizer[item.player]].keys()),
                weights=list(
                    beeweights[world.beemizer[item.player]].values()))[0]
            return item if not choice else ItemFactory(
                "Bee Trap", player) if choice == 'trap' else ItemFactory(
                    "Bee", player)
        return item

    progressionitems = []
    nonprogressionitems = []
    for item in items:
        if item.advancement or item.priority or item.type:
            progressionitems.append(item)
        else:
            nonprogressionitems.append(beemizer(item))
    world.random.shuffle(nonprogressionitems)

    if additional_triforce_pieces:
        if additional_triforce_pieces > len(nonprogressionitems):
            raise FillError(
                f"Not enough non-progression items to replace with Triforce pieces found for player "
                f"{world.get_player_names(player)}.")
        progressionitems += [ItemFactory("Triforce Piece", player)
                             ] * additional_triforce_pieces
        nonprogressionitems.sort(key=lambda item: int("Heart" in item.name)
                                 )  # try to keep hearts in the pool
        nonprogressionitems = nonprogressionitems[additional_triforce_pieces:]
        world.random.shuffle(nonprogressionitems)

    # shuffle medallions
    mm_medallion = world.random.choice(['Ether', 'Quake', 'Bombos'])
    tr_medallion = world.random.choice(['Ether', 'Quake', 'Bombos'])
    world.required_medallions[player] = (mm_medallion, tr_medallion)

    place_bosses(world, player)
    set_up_shops(world, player)

    if world.shop_shuffle[player]:
        shuffle_shops(world, nonprogressionitems, player)
    create_dynamic_shop_locations(world, player)

    world.itempool += progressionitems + nonprogressionitems

    if world.retro[player]:
        set_up_take_anys(world, player)  # depends on world.itempool to be set
Esempio n. 13
0
    def fill(self, window, worlds, location_pools, item_pools):
        """Fills the world with restrictions defined in a plandomizer JSON file.

        :param window:
        :param worlds: A list of the world objects that define the rules of each game world.
        :param location_pools: A list containing all of the location pools.
            0: Shop Locations
            1: Song Locations
            2: Fill locations
        :param item_pools: A list containing all of the item pools.
            0: Shop Items
            1: Dungeon Items
            2: Songs
            3: Progression Items
            4: Priority Items
            5: The rest of the Item pool
        """
        world = worlds[self.id]
        locations = {}
        if self.locations:
            locations = {loc: self.locations[loc] for loc in random.sample(self.locations.keys(), len(self.locations))}
        used_items = []
        for (location_name, record) in pattern_dict_items(locations, world.itempool, used_items):
            if record.item is None:
                continue

            player_id = self.id if record.player is None else record.player - 1

            location_matcher = lambda loc: loc.world.id == world.id and loc.name.lower() == location_name.lower()
            location = pull_first_element(location_pools, location_matcher)
            if location is None:
                try:
                    location = LocationFactory(location_name)
                except KeyError:
                    raise RuntimeError('Unknown location in world %d: %s' % (world.id + 1, location_name))
                if location.type == 'Boss':
                    continue
                elif location.name in world.disabled_locations:
                    continue
                else:
                    raise RuntimeError('Location already filled in world %d: %s' % (self.id + 1, location_name))

            if record.item in item_groups['DungeonReward']:
                raise RuntimeError('Cannot place dungeon reward %s in world %d in location %s.' % (record.item, self.id + 1, location_name))

            if record.item == '#Junk' and location.type == 'Song' and not world.shuffle_song_items:
                record.item = '#JunkSong'

            ignore_pools = None
            is_invert = pattern_matcher(record.item)('!')
            if is_invert and location.type != 'Song' and not world.shuffle_song_items:
                ignore_pools = [2]
            if is_invert and location.type == 'Song' and not world.shuffle_song_items:
                ignore_pools = [i for i in range(len(item_pools)) if i != 2]

            try:
                if record.item == "#Bottle":
                    try:
                        item = self.pool_replace_item(item_pools, "#Bottle", player_id, record.item, worlds)
                        # Update item_pool
                        if item.name not in self.item_pool:
                            self.item_pool[item.name] = ItemPoolRecord()
                        else:
                            self.item_pool[item.name].count += 1
                    except KeyError:
                        raise RuntimeError(
                            'Too many bottles were added to world %d, and not enough bottles are available in the item pool to be removed.' % (
                                        self.id + 1))
                elif record.item == "#AdultTrade":
                    try:
                        item = self.pool_replace_item(item_pools, "#AdultTrade", player_id, record.item, worlds)
                        # Update item_pool
                        if item.name not in self.item_pool:
                            self.item_pool[item.name] = ItemPoolRecord()
                        else:
                            self.item_pool[item.name].count += 1
                    except KeyError:
                        raise RuntimeError(
                            'Too many adult trade items were added to world %d, and not enough adult trade items are available in the item pool to be removed.' % (
                                        self.id + 1))
                else:
                    item = self.pool_remove_item(item_pools, record.item, 1, world_id=player_id, ignore_pools=ignore_pools)[0]
            except KeyError:
                if location.type == 'Shop' and "Buy" in record.item:
                    try:
                        self.pool_remove_item([item_pools[0]], "Buy *", 1, world_id=player_id)
                        item = ItemFactory([record.item], world=world)[0]
                    except KeyError:
                        raise RuntimeError('Too many shop buy items were added to world %d, and not enough shop buy items are available in the item pool to be removed.' % (self.id + 1))
                elif record.item in item_groups['Bottle']:
                    try:
                        item = self.pool_replace_item(item_pools, "#Bottle", player_id, record.item, worlds)
                    except KeyError:
                        raise RuntimeError('Too many bottles were added to world %d, and not enough bottles are available in the item pool to be removed.' % (self.id + 1))
                elif record.item in item_groups['AdultTrade']:
                    try:
                        item = self.pool_replace_item(item_pools, "#AdultTrade", player_id, record.item, worlds)
                    except KeyError:
                        raise RuntimeError('Too many adult trade items were added to world %d, and not enough adult trade items are available in the item pool to be removed.' % (self.id + 1))
                elif record.item == "Weird Egg":
                    # If Letter has not been shown to guard before obtaining a second weird egg a softlock can occur
                    # if there are important items at deku theater or an important location locked behind the gate
                    # or if Keaton Mask gets overwritten before giving it to the guard.
                    try:
                        item = self.pool_replace_item(item_pools, "Weird Egg", player_id, record.item, worlds)
                    except KeyError:
                        raise RuntimeError('Weird Egg already placed in World %d.' % (self.id + 1))
                else:
                    try:
                        item = self.pool_replace_item(item_pools, "#Junk", player_id, record.item, worlds)
                    except KeyError:
                        raise RuntimeError('Too many items were added to world %d, and not enough junk is available to be removed.' % (self.id + 1))
                # Update item_pool
                if item.name not in self.item_pool:
                    self.item_pool[item.name] = ItemPoolRecord()
                else:
                    self.item_pool[item.name].count += 1
            except IndexError:
                raise RuntimeError('Unknown item %s being placed on location %s in world %d.' % (record.item, location, self.id + 1))

            if record.price is not None and item.type != 'Shop':
                location.price = record.price
                world.shop_prices[location.name] = record.price

            if location.type == 'Song' and item.type != 'Song':
                self.song_as_items = True
            location.world.push_item(location, item, True)

            if item.advancement:
                search = Search.max_explore([world.state for world in worlds], itertools.chain.from_iterable(item_pools))
                if not search.can_beat_game(False):
                    raise FillError('%s in world %d is not reachable without %s in world %d!' % (location.name, self.id + 1, item.name, player_id + 1))
            window.fillcount += 1
            window.update_progress(5 + ((window.fillcount / window.locationcount) * 30))
Esempio n. 14
0
def place_bosses(world, player: int):
    if world.boss_shuffle[player] == 'none':
        return
    # Most to least restrictive order
    boss_locations = [
        ['Ganons Tower', 'top'],
        ['Tower of Hera', None],
        ['Skull Woods', None],
        ['Ganons Tower', 'middle'],
        ['Eastern Palace', None],
        ['Desert Palace', None],
        ['Palace of Darkness', None],
        ['Swamp Palace', None],
        ['Thieves Town', None],
        ['Ice Palace', None],
        ['Misery Mire', None],
        ['Turtle Rock', None],
        ['Ganons Tower', 'bottom'],
    ]

    all_bosses = sorted(
        boss_table.keys())  # sorted to be deterministic on older pythons
    placeable_bosses = [
        boss for boss in all_bosses
        if boss not in ['Agahnim', 'Agahnim2', 'Ganon']
    ]

    shuffle_mode = world.boss_shuffle[player]
    already_placed_bosses = []
    if ";" in shuffle_mode:
        bosses = shuffle_mode.split(";")
        shuffle_mode = bosses.pop()
        for boss in bosses:
            if "-" in boss:
                loc, boss = boss.split("-")
                boss = boss.title()
                level = None
                if loc.split(" ")[-1] in {"top", "middle", "bottom"}:
                    # split off level
                    loc = loc.split(" ")
                    level = loc[-1]
                    loc = " ".join(loc[:-1])
                loc = loc.title()
                if can_place_boss(boss, loc, level) and [loc, level
                                                         ] in boss_locations:
                    place_boss(world, player, boss, loc, level)
                    already_placed_bosses.append(boss)
                    boss_locations.remove([loc, level])
                else:
                    Exception("Cannot place", boss, "at", loc, level,
                              "for player", player)
            else:
                boss = boss.title()
                boss_locations, already_placed_bosses = place_where_possible(
                    world, player, boss, boss_locations)

    if shuffle_mode == "none":
        return  # vanilla bosses come pre-placed

    if shuffle_mode in ["basic", "normal"]:
        if world.boss_shuffle[player] == "basic":  # vanilla bosses shuffled
            bosses = placeable_bosses + [
                'Armos Knights', 'Lanmolas', 'Moldorm'
            ]
        else:  # all bosses present, the three duplicates chosen at random
            bosses = all_bosses + [
                world.random.choice(placeable_bosses) for _ in range(3)
            ]

        # there is probably a better way to do this
        while already_placed_bosses:
            # remove already manually placed bosses, to prevent for example triple Lanmolas
            boss = already_placed_bosses.pop()
            if boss in bosses:
                bosses.remove(boss)
            # there may be more bosses than locations at this point, depending on manual placement

        logging.debug('Bosses chosen %s', bosses)

        world.random.shuffle(bosses)
        for loc, level in boss_locations:
            boss = next((b for b in bosses if can_place_boss(b, loc, level)),
                        None)
            if not boss:
                loc_text = loc + (' (' + level + ')' if level else '')
                raise FillError('Could not place boss for location %s' %
                                loc_text)
            bosses.remove(boss)
            place_boss(world, player, boss, loc, level)

    elif shuffle_mode == "chaos":  # all bosses chosen at random
        for loc, level in boss_locations:
            try:
                boss = world.random.choice([
                    b for b in placeable_bosses
                    if can_place_boss(b, loc, level)
                ])
            except IndexError:
                loc_text = loc + (' (' + level + ')' if level else '')
                raise FillError('Could not place boss for location %s' %
                                loc_text)
            else:
                place_boss(world, player, boss, loc, level)

    elif shuffle_mode == "singularity":
        primary_boss = world.random.choice(placeable_bosses)
        remaining_boss_locations, _ = place_where_possible(
            world, player, primary_boss, boss_locations)
        if remaining_boss_locations:
            # pick a boss to go into the remaining locations
            remaining_boss = world.random.choice([
                boss for boss in placeable_bosses if all(
                    can_place_boss(boss, loc, level)
                    for loc, level in remaining_boss_locations)
            ])
            remaining_boss_locations, _ = place_where_possible(
                world, player, remaining_boss, remaining_boss_locations)
            if remaining_boss_locations:
                raise Exception("Unfilled boss locations!")
    else:
        raise FillError(f"Could not find boss shuffle mode {shuffle_mode}")
Esempio n. 15
0
def place_bosses(world, player: int):
    if world.boss_shuffle[player] == 'none':
        return
    # Most to least restrictive order
    boss_locations = boss_location_table.copy()
    world.random.shuffle(boss_locations)
    boss_locations.sort(key= lambda location: -int(restrictive_boss_locations[location]))

    all_bosses = sorted(boss_table.keys())  # sorted to be deterministic on older pythons
    placeable_bosses = [boss for boss in all_bosses if boss not in ['Agahnim', 'Agahnim2', 'Ganon']]

    shuffle_mode = world.boss_shuffle[player]
    already_placed_bosses = []
    if ";" in shuffle_mode:
        bosses = shuffle_mode.split(";")
        shuffle_mode = bosses.pop()
        for boss in bosses:
            if "-" in boss:
                loc, boss = boss.split("-")
                boss = boss.title()
                level = None
                if loc.split(" ")[-1] in {"top", "middle", "bottom"}:
                    # split off level
                    loc = loc.split(" ")
                    level = loc[-1]
                    loc = " ".join(loc[:-1])
                loc = loc.title().replace("Of", "of")
                if can_place_boss(boss, loc, level) and (loc, level) in boss_locations:
                    place_boss(world, player, boss, loc, level)
                    already_placed_bosses.append(boss)
                    boss_locations.remove((loc, level))
                else:
                    raise Exception(f"Cannot place {boss} at {format_boss_location(loc, level)} for player {player}.")
            else:
                boss = boss.title()
                boss_locations, already_placed_bosses = place_where_possible(world, player, boss, boss_locations)

    if shuffle_mode == "none":
        return  # vanilla bosses come pre-placed

    if shuffle_mode in ["basic", "normal"]:
        if world.boss_shuffle[player] == "basic":  # vanilla bosses shuffled
            bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm']
        else:  # all bosses present, the three duplicates chosen at random
            bosses = placeable_bosses + world.random.sample(placeable_bosses, 3)

        # there is probably a better way to do this
        while already_placed_bosses:
            # remove already manually placed bosses, to prevent for example triple Lanmolas
            boss = already_placed_bosses.pop()
            if boss in bosses:
                bosses.remove(boss)
            # there may be more bosses than locations at this point, depending on manual placement

        logging.debug('Bosses chosen %s', bosses)

        world.random.shuffle(bosses)
        for loc, level in boss_locations:
            for _ in range(len(bosses)):
                boss = bosses.pop()
                if can_place_boss(boss, loc, level):
                    break
                # put the boss back in queue
                bosses.insert(0, boss)  # this would be faster with deque,
                # but the deque size is small enough that it should not matter

            else:
                raise FillError(f'Could not place boss for location {format_boss_location(loc, level)}')

            place_boss(world, player, boss, loc, level)

    elif shuffle_mode == "chaos":  # all bosses chosen at random
        for loc, level in boss_locations:
            try:
                boss = world.random.choice(
                    [b for b in placeable_bosses if can_place_boss(b, loc, level)])
            except IndexError:
                raise FillError(f'Could not place boss for location {format_boss_location(loc, level)}')
            else:
                place_boss(world, player, boss, loc, level)

    elif shuffle_mode == "singularity":
        primary_boss = world.random.choice(placeable_bosses)
        remaining_boss_locations, _ = place_where_possible(world, player, primary_boss, boss_locations)
        if remaining_boss_locations:
            # pick a boss to go into the remaining locations
            remaining_boss = world.random.choice([boss for boss in placeable_bosses if all(
                can_place_boss(boss, loc, level) for loc, level in remaining_boss_locations)])
            remaining_boss_locations, _ = place_where_possible(world, player, remaining_boss, remaining_boss_locations)
            if remaining_boss_locations:
                raise Exception("Unfilled boss locations!")
    else:
        raise FillError(f"Could not find boss shuffle mode {shuffle_mode}")