Example #1
0
    def run_tests(self, access_pool):
        for exit in self.remove_exits:
            self.world.get_entrance(exit, 1).connected_region = self.world.get_region('Menu', 1)

        for location, access, *item_pool in access_pool:
            items = item_pool[0]
            all_except = item_pool[1] if len(item_pool) > 1 else None
            with self.subTest(location=location, access=access, items=items, all_except=all_except):
                if all_except and len(all_except) > 0:
                    items = self.world.itempool[:]
                    items = [item for item in items if item.name not in all_except and not ("Bottle" in item.name and "AnyBottle" in all_except)]
                    items.extend(ItemFactory(item_pool[0], 1))
                else:
                    items = ItemFactory(items, 1)
                state = CollectionState(self.world)
                state.reachable_regions[1].add(self.world.get_region('Menu', 1))
                for region_name in self.starting_regions:
                    region = self.world.get_region(region_name, 1)
                    state.reachable_regions[1].add(region)
                    for exit in region.exits:
                        if exit.connected_region is not None:
                            state.blocked_connections[1].add(exit)

                for item in items:
                    item.advancement = True
                    state.collect(item)

                self.assertEqual(self.world.get_location(location, 1).can_reach(state), access)
Example #2
0
 def setUp(self):
     self.world = MultiWorld(1)
     args = Namespace()
     for name, option in AutoWorld.AutoWorldRegister.world_types[
             "A Link to the Past"].options.items():
         setattr(args, name, {1: option.from_any(option.default)})
     self.world.set_options(args)
     self.world.set_default_common_options()
     self.world.logic[1] = "owglitches"
     self.world.mode[1] = "inverted"
     self.world.difficulty_requirements[1] = difficulties['normal']
     create_inverted_regions(self.world, 1)
     create_dungeons(self.world, 1)
     create_shops(self.world, 1)
     link_inverted_entrances(self.world, 1)
     self.world.worlds[1].create_items()
     self.world.required_medallions[1] = ['Ether', 'Quake']
     self.world.itempool.extend(get_dungeon_item_pool(self.world))
     self.world.itempool.extend(
         ItemFactory([
             'Green Pendant', 'Red Pendant', 'Blue Pendant',
             'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2',
             'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'
         ], 1))
     self.world.get_location('Agahnim 1', 1).item = None
     self.world.get_location('Agahnim 2', 1).item = None
     self.world.precollected_items[1].clear()
     self.world.itempool.append(ItemFactory('Pegasus Boots', 1))
     mark_light_world_regions(self.world, 1)
     self.world.worlds[1].set_rules()
Example #3
0
 def _get_items(self, item_pool, all_except):
     if all_except and len(all_except) > 0:
         items = self.world.itempool[:]
         items = [item for item in items if
                  item.name not in all_except and not ("Bottle" in item.name and "AnyBottle" in all_except)]
         items.extend(ItemFactory(item_pool[0], 1))
     else:
         items = ItemFactory(item_pool[0], 1)
     return self.get_state(items)
Example #4
0
 def setUp(self):
     self.world = MultiWorld(1)
     args = Namespace()
     for name, option in AutoWorld.AutoWorldRegister.world_types[
             "A Link to the Past"].options.items():
         setattr(args, name, {1: option.from_any(option.default)})
     self.world.set_options(args)
     self.world.set_default_common_options()
     self.starting_regions = []  # Where to start exploring
     self.remove_exits = []  # Block dungeon exits
     self.world.difficulty_requirements[1] = difficulties['normal']
     create_regions(self.world, 1)
     create_dungeons(self.world, 1)
     create_shops(self.world, 1)
     for exitname, regionname in mandatory_connections:
         connect_simple(self.world, exitname, regionname, 1)
     connect_simple(self.world, 'Big Bomb Shop', 'Big Bomb Shop', 1)
     self.world.get_region('Menu', 1).exits = []
     self.world.swamp_patch_required[1] = True
     self.world.worlds[1].set_rules()
     self.world.worlds[1].create_items()
     self.world.itempool.extend(get_dungeon_item_pool(self.world))
     self.world.itempool.extend(
         ItemFactory([
             'Green Pendant', 'Red Pendant', 'Blue Pendant',
             'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2',
             'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'
         ], 1))
Example #5
0
def set_up_take_anys(world, player):
    # these are references, do not modify these lists in-place
    if world.mode[player] == 'inverted':
        take_any_locs = take_any_locations_inverted
    else:
        take_any_locs = take_any_locations

    regions = world.random.sample(take_any_locs, 5)

    old_man_take_any = Region("Old Man Sword Cave", RegionType.Cave,
                              'the sword cave', player)
    world.regions.append(old_man_take_any)
    world.dynamic_regions.append(old_man_take_any)

    reg = regions.pop()
    entrance = world.get_region(reg, player).entrances[0]
    connect_entrance(world, entrance.name, old_man_take_any.name, player)
    entrance.target = 0x58
    old_man_take_any.shop = TakeAny(old_man_take_any, 0x0112, 0xE2, True, True,
                                    total_shop_slots)
    world.shops.append(old_man_take_any.shop)

    swords = [
        item for item in world.itempool
        if item.type == 'Sword' and item.player == player
    ]
    if swords:
        sword = world.random.choice(swords)
        world.itempool.remove(sword)
        world.itempool.append(ItemFactory('Rupees (20)', player))
        old_man_take_any.shop.add_inventory(0,
                                            sword.name,
                                            0,
                                            0,
                                            create_location=True)
    else:
        old_man_take_any.shop.add_inventory(0, 'Rupees (300)', 0, 0)

    for num in range(4):
        take_any = Region("Take-Any #{}".format(num + 1), RegionType.Cave,
                          'a cave of choice', player)
        world.regions.append(take_any)
        world.dynamic_regions.append(take_any)

        target, room_id = world.random.choice([(0x58, 0x0112), (0x60, 0x010F),
                                               (0x46, 0x011F)])
        reg = regions.pop()
        entrance = world.get_region(reg, player).entrances[0]
        connect_entrance(world, entrance.name, take_any.name, player)
        entrance.target = target
        take_any.shop = TakeAny(take_any, room_id, 0xE3, True, True,
                                total_shop_slots + num + 1)
        world.shops.append(take_any.shop)
        take_any.shop.add_inventory(0, 'Blue Potion', 0, 0)
        take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0)

    world.initialize_regions()
Example #6
0
 def setUp(self):
     self.world = MultiWorld(1)
     self.world.difficulty_requirements[1] = difficulties['normal']
     self.world.logic[1] = "owglitches"
     create_regions(self.world, 1)
     create_dungeons(self.world, 1)
     create_shops(self.world, 1)
     link_entrances(self.world, 1)
     generate_itempool(self.world, 1)
     self.world.required_medallions[1] = ['Ether', 'Quake']
     self.world.itempool.extend(get_dungeon_item_pool(self.world))
     self.world.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1))
     self.world.get_location('Agahnim 1', 1).item = None
     self.world.get_location('Agahnim 2', 1).item = None
     self.world.precollected_items.clear()
     self.world.itempool.append(ItemFactory('Pegasus Boots', 1))
     mark_dark_world_regions(self.world, 1)
     set_rules(self.world, 1)
Example #7
0
def fill_prizes(world, attempts=15):
    all_state = world.get_all_state(keys=True)
    for player in world.alttp_player_ids:
        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
        }
        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:
                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')
Example #8
0
def FillDisabledShopSlots(world):
    shop_slots: Set[ALttPLocation] = {
        location
        for shop_locations in (shop.region.locations for shop in world.shops)
        for location in shop_locations
        if location.shop_slot is not None and location.shop_slot_disabled
    }
    for location in shop_slots:
        location.shop_slot_disabled = True
        shop: Shop = location.parent_region.shop
        location.item = ItemFactory(shop.inventory[location.shop_slot]['item'],
                                    location.player)
        location.item_rule = lambda item: item.name == location.item.name and item.player == location.player
Example #9
0
 def setUp(self):
     self.world = MultiWorld(1)
     self.starting_regions = []  # Where to start exploring
     self.remove_exits = []      # Block dungeon exits
     self.world.difficulty_requirements[1] = difficulties['normal']
     create_regions(self.world, 1)
     create_dungeons(self.world, 1)
     create_shops(self.world, 1)
     for exitname, regionname in mandatory_connections:
         connect_simple(self.world, exitname, regionname, 1)
     connect_simple(self.world, 'Big Bomb Shop', 'Big Bomb Shop', 1)
     self.world.get_region('Menu', 1).exits = []
     self.world.swamp_patch_required[1] = True
     set_rules(self.world, 1)
     generate_itempool(self.world, 1)
     self.world.itempool.extend(get_dungeon_item_pool(self.world))
     self.world.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1))
Example #10
0
def create_dynamic_shop_locations(world, player):
    for shop in world.shops:
        if shop.region.player == player:
            for i, item in enumerate(shop.inventory):
                if item is None:
                    continue
                if item['create_location']:
                    loc = ALttPLocation(
                        player,
                        f"{shop.region.name} {shop.slot_names[i]}",
                        parent=shop.region)
                    shop.region.locations.append(loc)

                    world.clear_location_cache()

                    world.push_item(loc, ItemFactory(item['item'], player),
                                    False)
                    loc.shop_slot = i
                    loc.event = True
                    loc.locked = True
Example #11
0
 def setUp(self):
     self.world = MultiWorld(1)
     self.world.difficulty_requirements[1] = difficulties['normal']
     self.world.mode[1] = "inverted"
     create_inverted_regions(self.world, 1)
     create_dungeons(self.world, 1)
     create_shops(self.world, 1)
     link_inverted_entrances(self.world, 1)
     generate_itempool(self.world, 1)
     self.world.required_medallions[1] = ['Ether', 'Quake']
     self.world.itempool.extend(get_dungeon_item_pool(self.world))
     self.world.itempool.extend(
         ItemFactory([
             'Green Pendant', 'Red Pendant', 'Blue Pendant',
             'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2',
             'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'
         ], 1))
     self.world.get_location('Agahnim 1', 1).item = None
     self.world.get_location('Agahnim 2', 1).item = None
     mark_light_world_regions(self.world, 1)
     set_rules(self.world, 1)
Example #12
0
def distribute_planned(world):
    world_name_lookup = world.world_name_lookup

    for player in world.player_ids:
        placement: PlandoItem
        for placement in world.plando_items[player]:
            if placement.location in key_drop_data:
                placement.warn(
                    f"Can't place '{placement.item}' at '{placement.location}', as key drop shuffle locations are not supported yet."
                )
                continue
            item = ItemFactory(placement.item, player)
            target_world: int = placement.world
            if target_world is False or world.players == 1:
                target_world = player  # in own world
            elif target_world is True:  # in any other world
                unfilled = list(
                    location
                    for location in world.get_unfilled_locations_for_players(
                        placement.location,
                        set(world.player_ids) - {player})
                    if location.item_rule(item))
                if not unfilled:
                    placement.failed(
                        f"Could not find a world with an unfilled location {placement.location}",
                        FillError)
                    continue

                target_world = world.random.choice(unfilled).player

            elif target_world is None:  # any random world
                unfilled = list(
                    location
                    for location in world.get_unfilled_locations_for_players(
                        placement.location, set(world.player_ids))
                    if location.item_rule(item))
                if not unfilled:
                    placement.failed(
                        f"Could not find a world with an unfilled location {placement.location}",
                        FillError)
                    continue

                target_world = world.random.choice(unfilled).player

            elif type(target_world) == int:  # target world by player id
                if target_world not in range(1, world.players + 1):
                    placement.failed(
                        f"Cannot place item in world {target_world} as it is not in range of (1, {world.players})",
                        ValueError)
                    continue
            else:  # find world by name
                if target_world not in world_name_lookup:
                    placement.failed(
                        f"Cannot place item to {target_world}'s world as that world does not exist.",
                        ValueError)
                    continue
                target_world = world_name_lookup[target_world]

            location = world.get_location(placement.location, target_world)
            if location.item:
                placement.failed(
                    f"Cannot place item into already filled location {location}."
                )
                continue

            if location.can_fill(world.state, item, False):
                world.push_item(location, item, collect=False)
                location.event = True  # flag location to be checked during fill
                location.locked = True
                logging.debug(f"Plando placed {item} at {location}")
            else:
                placement.failed(
                    f"Can't place {item} at {location} due to fill condition not met."
                )
                continue

            if placement.from_pool:  # Should happen AFTER the item is placed, in case it was allowed to skip failed placement.
                try:
                    world.itempool.remove(item)
                except ValueError:
                    placement.warn(
                        f"Could not remove {item} from pool as it's already missing from it."
                    )
Example #13
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
Example #14
0
 def _get_items_partial(self, item_pool, missing_item):
     new_items = item_pool[0].copy()
     new_items.remove(missing_item)
     items = ItemFactory(new_items, 1)
     return self.get_state(items)
Example #15
0
def distribute_items_restrictive(world,
                                 gftower_trash=False,
                                 fill_locations=None):
    # If not passed in, then get a shuffled list of locations to fill in
    if not fill_locations:
        fill_locations = world.get_unfilled_locations()
        world.random.shuffle(fill_locations)

    # get items to distribute
    world.random.shuffle(world.itempool)
    progitempool = []
    localrestitempool = {player: [] for player in range(1, world.players + 1)}
    restitempool = []

    for item in world.itempool:
        if item.advancement:
            progitempool.append(item)
        elif item.name in world.local_items[item.player]:
            localrestitempool[item.player].append(item)
        else:
            restitempool.append(item)

    standard_keyshuffle_players = set()

    # fill in gtower locations with trash first
    for player in world.alttp_player_ids:
        if not gftower_trash or not world.ganonstower_vanilla[player] or \
                world.logic[player] in {'owglitches', "nologic"}:
            gtower_trash_count = 0
        elif 'triforcehunt' in world.goal[player] and (
                'local' in world.goal[player] or world.players == 1):
            gtower_trash_count = world.random.randint(
                world.crystals_needed_for_gt[player] * 2,
                world.crystals_needed_for_gt[player] * 4)
        else:
            gtower_trash_count = world.random.randint(
                0, world.crystals_needed_for_gt[player] * 2)

        if gtower_trash_count:
            gtower_locations = [
                location for location in fill_locations if
                'Ganons Tower' in location.name and location.player == player
            ]
            world.random.shuffle(gtower_locations)
            trashcnt = 0
            localrest = localrestitempool[player]
            if localrest:
                gt_item_pool = restitempool + localrest
                world.random.shuffle(gt_item_pool)
            else:
                gt_item_pool = restitempool.copy()

            while gtower_locations and gt_item_pool and trashcnt < gtower_trash_count:
                spot_to_fill = gtower_locations.pop()
                item_to_place = gt_item_pool.pop()
                if item_to_place in localrest:
                    localrest.remove(item_to_place)
                else:
                    restitempool.remove(item_to_place)
                world.push_item(spot_to_fill, item_to_place, False)
                fill_locations.remove(spot_to_fill)
                trashcnt += 1
        if world.mode[
                player] == 'standard' and world.keyshuffle[player] is True:
            standard_keyshuffle_players.add(player)

    # Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots
    if standard_keyshuffle_players:
        progitempool.sort(
            key=lambda item: 1 if item.name == 'Small Key (Hyrule Castle)' and
            item.player in standard_keyshuffle_players else 0)

    world.random.shuffle(fill_locations)
    fill_restrictive(world, world.state, fill_locations, progitempool)

    if any(localrestitempool.values()
           ):  # we need to make sure some fills are limited to certain worlds
        local_locations = {player: [] for player in world.player_ids}
        for location in fill_locations:
            local_locations[location.player].append(location)
        for locations in local_locations.values():
            world.random.shuffle(locations)

        for player, items in localrestitempool.items(
        ):  # items already shuffled
            player_local_locations = local_locations[player]
            for item_to_place in items:
                if not player_local_locations:
                    logging.warning(
                        f"Ran out of local locations for player {player}, "
                        f"cannot place {item_to_place}.")
                    break
                spot_to_fill = player_local_locations.pop()
                world.push_item(spot_to_fill, item_to_place, False)
                fill_locations.remove(spot_to_fill)

    world.random.shuffle(fill_locations)

    restitempool, fill_locations = fast_fill(world, restitempool,
                                             fill_locations)
    unplaced = [item for item in progitempool + restitempool]
    unfilled = [location.name for location in fill_locations]

    for location in fill_locations:
        world.push_item(location, ItemFactory('Nothing', location.player),
                        False)

    if unplaced or unfilled:
        logging.warning(
            f'Unplaced items({len(unplaced)}): {unplaced} - Unfilled Locations({len(unfilled)}): {unfilled}'
        )
Example #16
0
def create_dungeons(world, player):
    def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys,
                     dungeon_items):
        dungeon = Dungeon(name, dungeon_regions, big_key,
                          [] if world.smallkey_shuffle[player]
                          == smallkey_shuffle.option_universal else small_keys,
                          dungeon_items, player)
        for item in dungeon.all_items:
            item.dungeon = dungeon
            item.world = world
        dungeon.boss = BossFactory(default_boss,
                                   player) if default_boss else None
        for region in dungeon.regions:
            world.get_region(region, player).dungeon = dungeon
            dungeon.world = world
        return dungeon

    ES = make_dungeon('Hyrule Castle', None, [
        'Hyrule Castle', 'Sewers', 'Sewer Drop', 'Sewers (Dark)', 'Sanctuary'
    ], None, [ItemFactory('Small Key (Hyrule Castle)', player)],
                      [ItemFactory('Map (Hyrule Castle)', player)])
    EP = make_dungeon(
        'Eastern Palace', 'Armos Knights', ['Eastern Palace'],
        ItemFactory('Big Key (Eastern Palace)', player), [],
        ItemFactory(['Map (Eastern Palace)', 'Compass (Eastern Palace)'],
                    player))
    DP = make_dungeon(
        'Desert Palace', 'Lanmolas', [
            'Desert Palace North', 'Desert Palace Main (Inner)',
            'Desert Palace Main (Outer)', 'Desert Palace East'
        ], ItemFactory('Big Key (Desert Palace)', player),
        [ItemFactory('Small Key (Desert Palace)', player)],
        ItemFactory(['Map (Desert Palace)', 'Compass (Desert Palace)'],
                    player))
    ToH = make_dungeon(
        'Tower of Hera', 'Moldorm', [
            'Tower of Hera (Bottom)', 'Tower of Hera (Basement)',
            'Tower of Hera (Top)'
        ], ItemFactory('Big Key (Tower of Hera)', player),
        [ItemFactory('Small Key (Tower of Hera)', player)],
        ItemFactory(['Map (Tower of Hera)', 'Compass (Tower of Hera)'],
                    player))
    PoD = make_dungeon(
        'Palace of Darkness', 'Helmasaur King', [
            'Palace of Darkness (Entrance)', 'Palace of Darkness (Center)',
            'Palace of Darkness (Big Key Chest)',
            'Palace of Darkness (Bonk Section)', 'Palace of Darkness (North)',
            'Palace of Darkness (Maze)',
            'Palace of Darkness (Harmless Hellway)',
            'Palace of Darkness (Final Section)'
        ], ItemFactory('Big Key (Palace of Darkness)', player),
        ItemFactory(['Small Key (Palace of Darkness)'] * 6, player),
        ItemFactory(
            ['Map (Palace of Darkness)', 'Compass (Palace of Darkness)'],
            player))
    TT = make_dungeon(
        'Thieves Town', 'Blind',
        ['Thieves Town (Entrance)', 'Thieves Town (Deep)', 'Blind Fight'],
        ItemFactory('Big Key (Thieves Town)',
                    player), [ItemFactory('Small Key (Thieves Town)', player)],
        ItemFactory(['Map (Thieves Town)', 'Compass (Thieves Town)'], player))
    SW = make_dungeon(
        'Skull Woods', 'Mothula', [
            'Skull Woods Final Section (Entrance)',
            'Skull Woods First Section', 'Skull Woods Second Section',
            'Skull Woods Second Section (Drop)',
            'Skull Woods Final Section (Mothula)',
            'Skull Woods First Section (Right)',
            'Skull Woods First Section (Left)',
            'Skull Woods First Section (Top)'
        ], ItemFactory('Big Key (Skull Woods)', player),
        ItemFactory(['Small Key (Skull Woods)'] * 3, player),
        ItemFactory(['Map (Skull Woods)', 'Compass (Skull Woods)'], player))
    SP = make_dungeon(
        'Swamp Palace', 'Arrghus', [
            'Swamp Palace (Entrance)', 'Swamp Palace (First Room)',
            'Swamp Palace (Starting Area)', 'Swamp Palace (Center)',
            'Swamp Palace (North)'
        ], ItemFactory('Big Key (Swamp Palace)', player),
        [ItemFactory('Small Key (Swamp Palace)', player)],
        ItemFactory(['Map (Swamp Palace)', 'Compass (Swamp Palace)'], player))
    IP = make_dungeon(
        'Ice Palace', 'Kholdstare', [
            'Ice Palace (Entrance)', 'Ice Palace (Main)', 'Ice Palace (East)',
            'Ice Palace (East Top)', 'Ice Palace (Kholdstare)'
        ], ItemFactory('Big Key (Ice Palace)', player),
        ItemFactory(['Small Key (Ice Palace)'] * 2, player),
        ItemFactory(['Map (Ice Palace)', 'Compass (Ice Palace)'], player))
    MM = make_dungeon(
        'Misery Mire', 'Vitreous', [
            'Misery Mire (Entrance)', 'Misery Mire (Main)',
            'Misery Mire (West)', 'Misery Mire (Final Area)',
            'Misery Mire (Vitreous)'
        ], ItemFactory('Big Key (Misery Mire)', player),
        ItemFactory(['Small Key (Misery Mire)'] * 3, player),
        ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'], player))
    TR = make_dungeon(
        'Turtle Rock', 'Trinexx', [
            'Turtle Rock (Entrance)', 'Turtle Rock (First Section)',
            'Turtle Rock (Chain Chomp Room)', 'Turtle Rock (Second Section)',
            'Turtle Rock (Big Chest)', 'Turtle Rock (Crystaroller Room)',
            'Turtle Rock (Dark Room)', 'Turtle Rock (Eye Bridge)',
            'Turtle Rock (Trinexx)'
        ], ItemFactory('Big Key (Turtle Rock)', player),
        ItemFactory(['Small Key (Turtle Rock)'] * 4, player),
        ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], player))

    if world.mode[player] != 'inverted':
        AT = make_dungeon(
            'Agahnims Tower', 'Agahnim', ['Agahnims Tower', 'Agahnim 1'], None,
            ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), [])
        GT = make_dungeon(
            'Ganons Tower', 'Agahnim2', [
                'Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)',
                'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)',
                'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)',
                'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)',
                'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)',
                'Ganons Tower (Moldorm)', 'Agahnim 2'
            ], ItemFactory('Big Key (Ganons Tower)', player),
            ItemFactory(['Small Key (Ganons Tower)'] * 4, player),
            ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'],
                        player))
    else:
        AT = make_dungeon(
            'Inverted Agahnims Tower', 'Agahnim',
            ['Inverted Agahnims Tower', 'Agahnim 1'], None,
            ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), [])
        GT = make_dungeon(
            'Inverted Ganons Tower', 'Agahnim2', [
                'Inverted Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)',
                'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)',
                'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)',
                'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)',
                'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)',
                'Ganons Tower (Moldorm)', 'Agahnim 2'
            ], ItemFactory('Big Key (Ganons Tower)', player),
            ItemFactory(['Small Key (Ganons Tower)'] * 4, player),
            ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'],
                        player))

    GT.bosses['bottom'] = BossFactory('Armos Knights', player)
    GT.bosses['middle'] = BossFactory('Lanmolas', player)
    GT.bosses['top'] = BossFactory('Moldorm', player)

    for dungeon in [ES, EP, DP, ToH, AT, PoD, TT, SW, SP, IP, MM, TR, GT]:
        world.dungeons[dungeon.name, dungeon.player] = dungeon
Example #17
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 = MultiWorld(args.multi)

    logger = logging.getLogger('')
    world.seed = get_seed(seed)
    if args.race:
        world.secure()
    else:
        world.random.seed(world.seed)

    world.shuffle = args.shuffle.copy()
    world.logic = args.logic.copy()
    world.mode = args.mode.copy()
    world.swordless = args.swordless.copy()
    world.difficulty = args.difficulty.copy()
    world.item_functionality = args.item_functionality.copy()
    world.timer = args.timer.copy()
    world.progressive = args.progressive.copy()
    world.goal = args.goal.copy()
    world.local_items = args.local_items.copy()
    if hasattr(args, "algorithm"):  # current GUI options
        world.algorithm = args.algorithm
        world.shuffleganon = args.shuffleganon
        world.custom = args.custom
        world.customitemarray = args.customitemarray

    world.accessibility = args.accessibility.copy()
    world.retro = args.retro.copy()

    world.hints = args.hints.copy()

    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: world.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: world.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.open_pyramid.copy()
    world.boss_shuffle = args.shufflebosses.copy()
    world.enemy_shuffle = args.enemy_shuffle.copy()
    world.enemy_health = args.enemy_health.copy()
    world.enemy_damage = args.enemy_damage.copy()
    world.killable_thieves = args.killable_thieves.copy()
    world.bush_shuffle = args.bush_shuffle.copy()
    world.tile_shuffle = args.tile_shuffle.copy()
    world.beemizer = args.beemizer.copy()
    world.timer = args.timer.copy()
    world.countdown_start_time = args.countdown_start_time.copy()
    world.red_clock_time = args.red_clock_time.copy()
    world.blue_clock_time = args.blue_clock_time.copy()
    world.green_clock_time = args.green_clock_time.copy()
    world.shufflepots = args.shufflepots.copy()
    world.progressive = args.progressive.copy()
    world.dungeon_counters = args.dungeon_counters.copy()
    world.glitch_boots = args.glitch_boots.copy()
    world.triforce_pieces_available = args.triforce_pieces_available.copy()
    world.triforce_pieces_required = args.triforce_pieces_required.copy()
    world.shop_shuffle = args.shop_shuffle.copy()
    world.shop_shuffle_slots = args.shop_shuffle_slots.copy()
    world.progression_balancing = args.progression_balancing.copy()
    world.shuffle_prizes = args.shuffle_prizes.copy()
    world.sprite_pool = args.sprite_pool.copy()
    world.dark_room_logic = args.dark_room_logic.copy()
    world.plando_items = args.plando_items.copy()
    world.plando_texts = args.plando_texts.copy()
    world.plando_connections = args.plando_connections.copy()
    world.er_seeds = getattr(args, "er_seeds", {})
    world.restrict_dungeon_item_on_boss = args.restrict_dungeon_item_on_boss.copy(
    )
    world.required_medallions = args.required_medallions.copy()
    world.game = args.game.copy()
    import Options
    for hk_option in Options.hollow_knight_options:
        setattr(world, hk_option, getattr(args, hk_option, {}))
    for factorio_option in Options.factorio_options:
        setattr(world, factorio_option, getattr(args, factorio_option, {}))
    world.glitch_triforce = args.glitch_triforce  # This is enabled/disabled globally, no per player option.

    world.rom_seeds = {
        player: random.Random(world.random.randint(0, 999999999))
        for player in range(1, world.players + 1)
    }

    for player in range(1, world.players + 1):
        world.er_seeds[player] = str(world.random.randint(0, 2**64))

        if "-" in world.shuffle[player]:
            shuffle, seed = world.shuffle[player].split("-", 1)
            world.shuffle[player] = shuffle
            if shuffle == "vanilla":
                world.er_seeds[player] = "vanilla"
            elif seed.startswith("group-") or args.race:
                # renamed from team to group to not confuse with existing team name use
                world.er_seeds[player] = get_same_seed(
                    world, (shuffle, seed, world.retro[player],
                            world.mode[player], world.logic[player]))
            else:  # not a race or group seed, use set seed as is.
                world.er_seeds[player] = seed
        elif world.shuffle[player] == "vanilla":
            world.er_seeds[player] = "vanilla"

    logger.info('Archipelago 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 world.alttp_player_ids:
        world.difficulty_requirements[player] = difficulties[
            world.difficulty[player]]

    for player in world.player_ids:
        for tok in filter(None, args.startinventory[player].split(',')):
            item = ItemFactory(tok.strip(), player)
            if item:
                world.push_precollected(item)

        # enforce pre-defined local items.
        if world.goal[player] in [
                "localtriforcehunt", "localganontriforcehunt"
        ]:
            world.local_items[player].add('Triforce Piece')

        # items can't be both local and non-local, prefer local
        world.non_local_items[player] -= world.local_items[player]

        # dungeon items can't be in non-local if the appropriate dungeon item shuffle setting is not set.
        if not world.mapshuffle[player]:
            world.non_local_items[player] -= item_name_groups['Maps']

        if not world.compassshuffle[player]:
            world.non_local_items[player] -= item_name_groups['Compasses']

        if not world.keyshuffle[player]:
            world.non_local_items[player] -= item_name_groups['Small Keys']

        if not world.bigkeyshuffle[player]:
            world.non_local_items[player] -= item_name_groups['Big Keys']

        # Not possible to place pendants/crystals out side of boss prizes yet.
        world.non_local_items[player] -= item_name_groups['Pendants']
        world.non_local_items[player] -= item_name_groups['Crystals']

    for player in world.hk_player_ids:
        hk_create_regions(world, player)

    for player in world.factorio_player_ids:
        factorio_create_regions(world, player)

    for player in world.alttp_player_ids:
        if world.open_pyramid[player] == 'goal':
            world.open_pyramid[player] = world.goal[player] in {
                'crystals', 'ganontriforcehunt', 'localganontriforcehunt',
                'ganonpedestal'
            }
        elif world.open_pyramid[player] == 'auto':
            world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'} and \
                                         (world.shuffle[player] in {'vanilla', 'dungeonssimple', 'dungeonsfull'} or not world.shuffle_ganon)
        else:
            world.open_pyramid[player] = {
                'on': True,
                'off': False,
                'yes': True,
                'no': False
            }.get(world.open_pyramid[player], world.open_pyramid[player])

        world.triforce_pieces_available[player] = max(
            world.triforce_pieces_available[player],
            world.triforce_pieces_required[player])

        if world.mode[player] != 'inverted':
            create_regions(world, player)
        else:
            create_inverted_regions(world, player)
        create_shops(world, player)
        create_dungeons(world, player)

    logger.info('Shuffling the World about.')

    for player in world.alttp_player_ids:
        if world.logic[player] not in ["noglitches", "minorglitches"] and world.shuffle[player] in \
                {"vanilla", "dungeonssimple", "dungeonsfull", "simple", "restricted", "full"}:
            world.fix_fake_world[player] = False

        # seeded entrance shuffle
        old_random = world.random
        world.random = random.Random(world.er_seeds[player])

        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)

        world.random = old_random
        plando_connect(world, player)

    logger.info('Generating Item Pool.')

    for player in world.alttp_player_ids:
        generate_itempool(world, player)

    logger.info('Calculating Access Rules.')
    if world.players > 1:
        for player in world.player_ids:
            locality_rules(world, player)

    for player in world.alttp_player_ids:
        set_rules(world, player)

    for player in world.hk_player_ids:
        gen_hollow(world, player)

    for player in world.factorio_player_ids:
        gen_factorio(world, player)

    logger.info("Running Item Plando")

    for item in world.itempool:
        item.world = world

    distribute_planned(world)

    logger.info('Placing Dungeon Prizes.')

    fill_prizes(world)

    logger.info('Placing Dungeon Items.')

    if world.algorithm in ['balanced', 'vt26'] or any(
            list(args.mapshuffle.values()) +
            list(args.compassshuffle.values()) +
            list(args.keyshuffle.values()) +
            list(args.bigkeyshuffle.values())):
        fill_dungeons_restrictive(world)
    else:
        fill_dungeons(world)

    logger.info('Fill the world.')

    if world.algorithm == 'flood':
        flood_items(
            world)  # different algo, biased towards early game progress items
    elif world.algorithm == 'vt25':
        distribute_items_restrictive(world, False)
    elif world.algorithm == 'vt26':
        distribute_items_restrictive(world, True)
    elif world.algorithm == 'balanced':
        distribute_items_restrictive(world, True)

    logger.info("Filling Shop Slots")

    ShopSlotFill(world)

    if world.players > 1:
        balance_multiworld_progression(world)

    logger.info('Generating output files.')

    outfilebase = 'AP_%s' % (args.outputname
                             if args.outputname else world.seed)

    rom_names = []

    def _gen_rom(team: int, player: int):
        use_enemizer = (world.boss_shuffle[player] != 'none'
                        or world.enemy_shuffle[player]
                        or world.enemy_health[player] != 'default'
                        or world.enemy_damage[player] != 'default'
                        or world.shufflepots[player]
                        or world.bush_shuffle[player]
                        or world.killable_thieves[player])

        rom = LocalRom(args.rom)

        patch_rom(world, rom, player, team, use_enemizer)

        if use_enemizer:
            patch_enemizer(world, team, player, rom, args.enemizercli)

        if args.race:
            patch_race_rom(rom, world, player)

        world.spoiler.hashes[(player, team)] = get_hash_string(rom.hash)

        palettes_options = {}
        palettes_options['dungeon'] = args.uw_palettes[player]
        palettes_options['overworld'] = args.ow_palettes[player]
        palettes_options['hud'] = args.hud_palettes[player]
        palettes_options['sword'] = args.sword_palettes[player]
        palettes_options['shield'] = args.shield_palettes[player]
        palettes_options['link'] = args.link_palettes[player]

        apply_rom_settings(rom,
                           args.heartbeep[player],
                           args.heartcolor[player],
                           args.quickswap[player],
                           args.fastmenu[player],
                           args.disablemusic[player],
                           args.sprite[player],
                           palettes_options,
                           world,
                           player,
                           True,
                           reduceflashing=args.reduceflashing[player]
                           or args.race,
                           triforcehud=args.triforcehud[player])

        mcsb_name = ''
        if all([
                world.mapshuffle[player], world.compassshuffle[player],
                world.keyshuffle[player], world.bigkeyshuffle[player]
        ]):
            mcsb_name = '-keysanity'
        elif [
                world.mapshuffle[player], world.compassshuffle[player],
                world.keyshuffle[player], world.bigkeyshuffle[player]
        ].count(True) == 1:
            mcsb_name = '-mapshuffle' if world.mapshuffle[player] else \
                '-compassshuffle' if world.compassshuffle[player] else \
                '-universal_keys' if world.keyshuffle[player] == "universal" 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 '',
                'U' if world.keyshuffle[player] == "universal" else
                'S' if world.keyshuffle[player] else '',
                'B' if world.bigkeyshuffle[player] else '')

        outfilepname = f'_T{team + 1}' if world.teams > 1 else ''
        outfilepname += f'_P{player}'
        outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" \
            if world.player_names[player][team] != 'Player%d' % player else ''
        outfilestuffs = {
            "logic": world.logic[player],  # 0
            "difficulty": world.difficulty[player],  # 1
            "item_functionality": world.item_functionality[player],  # 2
            "mode": world.mode[player],  # 3
            "goal": world.goal[player],  # 4
            "timer": str(world.timer[player]),  # 5
            "shuffle": world.shuffle[player],  # 6
            "algorithm": world.algorithm,  # 7
            "mscb": mcsb_name,  # 8
            "retro": world.retro[player],  # 9
            "progressive": world.progressive,  # A
            "hints": 'True' if world.hints[player] else 'False'  # B
        }
        #                  0  1  2  3  4 5  6  7 8 9 A B
        outfilesuffix = (
            '_%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s' % (
                #  0          1      2      3    4     5    6      7     8        9         A     B           C
                # _noglitches_normal-normal-open-ganon-ohko_simple-balanced-keysanity-retro-prog_random-nohints
                # _noglitches_normal-normal-open-ganon     _simple-balanced-keysanity-retro
                # _noglitches_normal-normal-open-ganon     _simple-balanced-keysanity      -prog_random
                # _noglitches_normal-normal-open-ganon     _simple-balanced-keysanity                  -nohints
                outfilestuffs["logic"],  # 0
                outfilestuffs["difficulty"],  # 1
                outfilestuffs["item_functionality"],  # 2
                outfilestuffs["mode"],  # 3
                outfilestuffs["goal"],  # 4
                "" if outfilestuffs["timer"] in ['False', 'none', 'display']
                else "-" + outfilestuffs["timer"],  # 5
                outfilestuffs["shuffle"],  # 6
                outfilestuffs["algorithm"],  # 7
                outfilestuffs["mscb"],  # 8
                "-retro" if outfilestuffs["retro"] == "True" else "",  # 9
                "-prog_" + outfilestuffs["progressive"] if
                outfilestuffs["progressive"] in ['off', 'random'] else "",  # A
                "-nohints" if not outfilestuffs["hints"] == "True" else ""
            )  # B
        ) if not args.outputname else ''
        rompath = output_path(
            f'{outfilebase}{outfilepname}{outfilesuffix}.sfc')
        rom.write_to_file(rompath, hide_enemizer=True)
        if args.create_diff:
            Patch.create_patch_file(rompath)
        return player, team, bytes(rom.name)

    pool = concurrent.futures.ThreadPoolExecutor()
    multidata_task = None
    check_accessibility_task = pool.submit(world.fulfills_accessibility)
    if not args.suppress_rom:

        rom_futures = []
        mod_futures = []
        for team in range(world.teams):
            for player in world.alttp_player_ids:
                rom_futures.append(pool.submit(_gen_rom, team, player))
        for player in world.factorio_player_ids:
            mod_futures.append(
                pool.submit(
                    generate_mod, world, player,
                    str(args.outputname if args.outputname else world.seed)))

        def get_entrance_to_region(region: Region):
            for entrance in region.entrances:
                if entrance.parent_region.type in (RegionType.DarkWorld,
                                                   RegionType.LightWorld):
                    return entrance
            for entrance in region.entrances:  # BFS might be better here, trying DFS for now.
                return get_entrance_to_region(entrance.parent_region)

        # collect ER hint info
        er_hint_data = {
            player: {}
            for player in range(1, world.players + 1)
            if world.shuffle[player] != "vanilla" or world.retro[player]
        }
        from worlds.alttp.Regions import RegionType
        for region in world.regions:
            if region.player in er_hint_data and region.locations:
                main_entrance = get_entrance_to_region(region)
                for location in region.locations:
                    if type(location.address
                            ) == int:  # skips events and crystals
                        if lookup_vanilla_location_to_entrance[
                                location.address] != main_entrance.name:
                            er_hint_data[region.player][
                                location.address] = main_entrance.name

        ordered_areas = ('Light World', 'Dark World', 'Hyrule Castle',
                         'Agahnims Tower', 'Eastern Palace', 'Desert Palace',
                         'Tower of Hera', 'Palace of Darkness', 'Swamp Palace',
                         'Skull Woods', 'Thieves Town', 'Ice Palace',
                         'Misery Mire', 'Turtle Rock', 'Ganons Tower', "Total")

        checks_in_area = {
            player: {area: list()
                     for area in ordered_areas}
            for player in range(1, world.players + 1)
        }

        for player in range(1, world.players + 1):
            checks_in_area[player]["Total"] = 0

        for location in [
                loc for loc in world.get_filled_locations()
                if type(loc.address) is int
        ]:
            main_entrance = get_entrance_to_region(location.parent_region)
            if location.game != Games.LTTP:
                checks_in_area[location.player]["Light World"].append(
                    location.address)
            elif location.parent_region.dungeon:
                dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower',
                               'Inverted Ganons Tower': 'Ganons Tower'}\
                    .get(location.parent_region.dungeon.name, location.parent_region.dungeon.name)
                checks_in_area[location.player][dungeonname].append(
                    location.address)
            elif main_entrance.parent_region.type == RegionType.LightWorld:
                checks_in_area[location.player]["Light World"].append(
                    location.address)
            elif main_entrance.parent_region.type == RegionType.DarkWorld:
                checks_in_area[location.player]["Dark World"].append(
                    location.address)
            checks_in_area[location.player]["Total"] += 1

        oldmancaves = []
        takeanyregions = [
            "Old Man Sword Cave", "Take-Any #1", "Take-Any #2", "Take-Any #3",
            "Take-Any #4"
        ]
        for index, take_any in enumerate(takeanyregions):
            for region in [
                    world.get_region(take_any, player)
                    for player in range(1, world.players + 1)
                    if world.retro[player]
            ]:
                item = ItemFactory(
                    region.shop.inventory[(
                        0 if take_any == "Old Man Sword Cave" else 1)]['item'],
                    region.player)
                player = region.player
                location_id = SHOP_ID_START + total_shop_slots + index

                main_entrance = get_entrance_to_region(region)
                if main_entrance.parent_region.type == RegionType.LightWorld:
                    checks_in_area[player]["Light World"].append(location_id)
                else:
                    checks_in_area[player]["Dark World"].append(location_id)
                checks_in_area[player]["Total"] += 1

                er_hint_data[player][location_id] = main_entrance.name
                oldmancaves.append(
                    ((location_id, player), (item.code, player)))

        precollected_items = [[] for player in range(world.players)]
        for item in world.precollected_items:
            precollected_items[item.player - 1].append(item.code)

        FillDisabledShopSlots(world)

        def write_multidata(roms, mods):
            import base64
            for future in roms:
                rom_name = future.result()
                rom_names.append(rom_name)
            client_versions = {}
            minimum_versions = {
                "server": (0, 0, 3),
                "clients": client_versions
            }
            games = {}
            for slot in world.player_ids:
                client_versions[slot] = (0, 0, 3)
                games[slot] = world.game[slot]
            connect_names = {
                base64.b64encode(rom_name).decode(): (team, slot)
                for slot, team, rom_name in rom_names
            }

            for i, team in enumerate(parsed_names):
                for player, name in enumerate(team, 1):
                    if player not in world.alttp_player_ids:
                        connect_names[name] = (i, player)

            multidata = zlib.compress(
                pickle.dumps({
                    "games":
                    games,
                    "names":
                    parsed_names,
                    "connect_names":
                    connect_names,
                    "remote_items": {
                        player
                        for player in range(1, world.players + 1)
                        if world.remote_items[player]
                        or world.game[player] != "A Link to the Past"
                    },
                    "locations": {(location.address, location.player):
                                  (location.item.code, location.item.player)
                                  for location in world.get_filled_locations()
                                  if type(location.address) is int},
                    "checks_in_area":
                    checks_in_area,
                    "server_options":
                    get_options()["server_options"],
                    "er_hint_data":
                    er_hint_data,
                    "precollected_items":
                    precollected_items,
                    "version":
                    tuple(_version_tuple),
                    "tags": ["AP"],
                    "minimum_versions":
                    minimum_versions,
                    "seed_name":
                    str(args.outputname if args.outputname else world.seed)
                }), 9)

            with open(output_path('%s.archipelago' % outfilebase), 'wb') as f:
                f.write(bytes([1]))  # version of format
                f.write(multidata)
            for future in mods:
                future.result()  # collect errors if they occured

        multidata_task = pool.submit(write_multidata, rom_futures, mod_futures)
    if not check_accessibility_task.result():
        if not world.can_beat_game():
            raise Exception("Game appears as unbeatable. Aborting.")
        else:
            logger.warning(
                "Location Accessibility requirements not fulfilled.")
    if multidata_task:
        multidata_task.result()  # retrieve exception if one exists
    pool.shutdown()  # wait for all queued tasks to complete
    if not args.skip_playthrough:
        logger.info('Calculating playthrough.')
    create_playthrough(world)
    if args.create_spoiler:  # needs spoiler.hashes to be filled, that depend on rom_futures being done
        world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))

    logger.info('Done. Enjoy. Total Time: %s', time.perf_counter() - start)
    return world
Example #18
0
def create_shops(world, player: int):
    option = world.shop_shuffle[player]

    player_shop_table = shop_table.copy()
    if "w" in option:
        player_shop_table["Potion Shop"] = player_shop_table[
            "Potion Shop"]._replace(locked=False)
        dynamic_shop_slots = total_dynamic_shop_slots + 3
    else:
        dynamic_shop_slots = total_dynamic_shop_slots

    num_slots = min(dynamic_shop_slots, world.shop_item_slots[player])
    single_purchase_slots: List[bool] = [True] * num_slots + [False] * (
        dynamic_shop_slots - num_slots)
    world.random.shuffle(single_purchase_slots)

    if 'g' in option or 'f' in option:
        default_shop_table = [
            i for l in [
                shop_generation_types[x]
                for x in ['arrows', 'bombs', 'potions', 'shields', 'bottle']
                if not world.retro_bow[player] or x != 'arrows'
            ] for i in l
        ]
        new_basic_shop = world.random.sample(default_shop_table, k=3)
        new_dark_shop = world.random.sample(default_shop_table, k=3)
        for name, shop in player_shop_table.items():
            typ, shop_id, keeper, custom, locked, items, sram_offset = shop
            if not locked:
                new_items = world.random.sample(default_shop_table, k=3)
                if 'f' not in option:
                    if items == _basic_shop_defaults:
                        new_items = new_basic_shop
                    elif items == _dark_world_shop_defaults:
                        new_items = new_dark_shop
                keeper = world.random.choice([0xA0, 0xC1, 0xFF])
                player_shop_table[name] = ShopData(typ, shop_id, keeper,
                                                   custom, locked, new_items,
                                                   sram_offset)
    if world.mode[player] == "inverted":
        # make sure that blue potion is available in inverted, special case locked = None; lock when done.
        player_shop_table["Dark Lake Hylia Shop"] = \
            player_shop_table["Dark Lake Hylia Shop"]._replace(items=_inverted_hylia_shop_defaults, locked=None)
    chance_100 = int(world.retro_bow[player]) * 0.25 + int(
        world.smallkey_shuffle[player] ==
        smallkey_shuffle.option_universal) * 0.5
    for region_name, (room_id, type, shopkeeper, custom, locked, inventory,
                      sram_offset) in player_shop_table.items():
        region = world.get_region(region_name, player)
        shop: Shop = shop_class_mapping[type](region, room_id, shopkeeper,
                                              custom, locked, sram_offset)
        # special case: allow shop slots, but do not allow overwriting of base inventory behind them
        if locked is None:
            shop.locked = True
        region.shop = shop
        world.shops.append(shop)
        for index, item in enumerate(inventory):
            shop.add_inventory(index, *item)
            if not locked and num_slots:
                slot_name = f"{region.name} {shop.slot_names[index]}"
                loc = ALttPLocation(player,
                                    slot_name,
                                    address=shop_table_by_location[slot_name],
                                    parent=region,
                                    hint_text="for sale")
                loc.shop_slot = index
                loc.locked = True
                if single_purchase_slots.pop():
                    if world.goal[player] != 'icerodhunt':
                        if world.random.random() < chance_100:
                            additional_item = 'Rupees (100)'
                        else:
                            additional_item = 'Rupees (50)'
                    else:
                        additional_item = GetBeemizerItem(
                            world, player, 'Nothing')
                    loc.item = ItemFactory(additional_item, player)
                else:
                    loc.item = ItemFactory(
                        GetBeemizerItem(world, player, 'Nothing'), player)
                    loc.shop_slot_disabled = True
                loc.item.world = world
                shop.region.locations.append(loc)
                world.clear_location_cache()
Example #19
0
def shuffle_shops(world, items, player: int):
    option = world.shop_shuffle[player]
    if 'u' in option:
        progressive = world.progressive[player]
        progressive = world.random.choice([
            True, False
        ]) if progressive == 'grouped_random' else progressive == 'on'
        progressive &= world.goal == 'icerodhunt'
        new_items = ["Bomb Upgrade (+5)"] * 6
        new_items.append(
            "Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)")

        if not world.retro_bow[player]:
            new_items += ["Arrow Upgrade (+5)"] * 6
            new_items.append(
                "Arrow Upgrade (+5)" if progressive else "Arrow Upgrade (+10)")

        world.random.shuffle(
            new_items
        )  # Decide what gets tossed randomly if it can't insert everything.

        capacityshop: Optional[Shop] = None
        for shop in world.shops:
            if shop.type == ShopType.UpgradeShop and shop.region.player == player and \
                    shop.region.name == "Capacity Upgrade":
                shop.clear_inventory()
                capacityshop = shop

        if world.goal[player] != 'icerodhunt':
            for i, item in enumerate(items):
                if item.name in trap_replaceable:
                    items[i] = ItemFactory(new_items.pop(), player)
                    if not new_items:
                        break
            else:
                logging.warning(
                    f"Not all upgrades put into Player{player}' item pool. Putting remaining items in Capacity Upgrade shop instead."
                )
                bombupgrades = sum(1 for item in new_items
                                   if 'Bomb Upgrade' in item)
                arrowupgrades = sum(1 for item in new_items
                                    if 'Arrow Upgrade' in item)
                if bombupgrades:
                    capacityshop.add_inventory(1, 'Bomb Upgrade (+5)', 100,
                                               bombupgrades)
                if arrowupgrades:
                    capacityshop.add_inventory(1, 'Arrow Upgrade (+5)', 100,
                                               arrowupgrades)
        else:
            for item in new_items:
                world.push_precollected(ItemFactory(item, player))

    if any(setting in option for setting in 'ipP'):
        shops = []
        upgrade_shops = []
        total_inventory = []
        for shop in world.shops:
            if shop.region.player == player:
                if shop.type == ShopType.UpgradeShop:
                    upgrade_shops.append(shop)
                elif shop.type == ShopType.Shop and not shop.locked:
                    shops.append(shop)
                    total_inventory.extend(shop.inventory)

        if 'p' in option:

            def price_adjust(price: int) -> int:
                # it is important that a base price of 0 always returns 0 as new price!
                adjust = 2 if price < 100 else 5
                return int((price / adjust) *
                           (0.5 + world.random.random() * 1.5)) * adjust

            def adjust_item(item):
                if item:
                    item["price"] = price_adjust(item["price"])
                    item['replacement_price'] = price_adjust(item["price"])

            for item in total_inventory:
                adjust_item(item)
            for shop in upgrade_shops:
                for item in shop.inventory:
                    adjust_item(item)

        if 'P' in option:
            for item in total_inventory:
                price_to_funny_price(world, item, player)
            # Don't apply to upgrade shops
            # Upgrade shop is only one place, and will generally be too easy to
            # replenish hearts and bombs

        if 'i' in option:
            world.random.shuffle(total_inventory)

            i = 0
            for shop in shops:
                slots = shop.slots
                shop.inventory = total_inventory[i:i + slots]
                i += slots