Example #1
0
def items_for_ammo(
    ammo: Ammo,
    state: AmmoState,
    included_ammo_for_item: Dict[int, int],
    previous_pickup_for_item: Dict[int, Ammo],
    maximum_ammo: Dict[int, int],
) -> List[List[int]]:
    """
    Helper function for add_ammo.

    :param maximum_ammo:
    :param ammo:
    :param state:
    :param included_ammo_for_item:
    :param previous_pickup_for_item:
    :return: An array that lists how many of each ammo each instance of the expansions should give
    """
    ammo_per_pickup: List[List[int]] = [[] for _ in range(state.pickup_count)]

    # TODO: add test for case of 0 pickups
    if state.pickup_count == 0:
        return ammo_per_pickup

    for item in ammo.items:
        if item in previous_pickup_for_item:
            raise InvalidConfiguration(
                "Both {0.name} and {1.name} have non-zero pickup count for item {2}. This is unsupported."
                .format(ammo, previous_pickup_for_item[item], item))
        previous_pickup_for_item[item] = ammo

        if item not in included_ammo_for_item:
            raise InvalidConfiguration(
                "Invalid configuration: ammo {0.name} was configured for {1.pickup_count}"
                "expansions, but main pickup was removed".format(ammo, state))

        if maximum_ammo[item] < included_ammo_for_item[item]:
            raise InvalidConfiguration(
                "Ammo {0.name} was configured for a total of {1}, but major items gave {2}"
                .format(ammo, maximum_ammo[item],
                        included_ammo_for_item[item]))

        adjusted_count = maximum_ammo[item] - included_ammo_for_item[item]
        count_per_expansion = adjusted_count // state.pickup_count
        expansions_with_extra_count = adjusted_count - count_per_expansion * state.pickup_count

        for i, pickup_ammo in enumerate(ammo_per_pickup):
            pickup_ammo.append(count_per_expansion)
            if i < expansions_with_extra_count:
                pickup_ammo[-1] += 1

    return ammo_per_pickup
Example #2
0
def _validate_item_pool_size(item_pool: List[PickupEntry], game: GameDescription,
                             configuration: EchoesConfiguration) -> None:
    min_starting_items = configuration.major_items_configuration.minimum_random_starting_items
    if len(item_pool) > game.world_list.num_pickup_nodes + min_starting_items:
        raise InvalidConfiguration(
            "Item pool has {} items, which is more than {} (game) + {} (minimum starting items)".format(
                len(item_pool), game.world_list.num_pickup_nodes, min_starting_items))
Example #3
0
def _distribute_remaining_items(rng: Random,
                                filler_results: Dict[int, FillerPlayerResult],
                                ) -> Dict[int, GamePatches]:
    unassigned_pickup_nodes = []
    all_remaining_pickups = []
    assignments: Dict[int, PickupAssignment] = {}

    for player, filler_result in filler_results.items():
        for pickup_node in filter_unassigned_pickup_nodes(filler_result.game.world_list.all_nodes,
                                                          filler_result.patches.pickup_assignment):
            unassigned_pickup_nodes.append((player, pickup_node))

        all_remaining_pickups.extend(zip([player] * len(filler_result.unassigned_pickups),
                                         filler_result.unassigned_pickups))
        assignments[player] = {}

    rng.shuffle(unassigned_pickup_nodes)
    rng.shuffle(all_remaining_pickups)

    if len(all_remaining_pickups) > len(unassigned_pickup_nodes):
        raise InvalidConfiguration(
            "Received {} remaining pickups, but there's only {} unassigned locations.".format(
                len(all_remaining_pickups),
                len(unassigned_pickup_nodes)
            ))

    for (node_player, node), (pickup_player, pickup) in zip(unassigned_pickup_nodes, all_remaining_pickups):
        assignments[node_player][node.pickup_index] = PickupTarget(pickup, pickup_player)

    return {
        index: filler_results[index].patches.assign_pickup_assignment(assignment)
        for index, assignment in assignments.items()
    }
Example #4
0
def add_ammo(
    resource_database: ResourceDatabase,
    ammo_configuration: AmmoConfiguration,
    included_ammo_for_item: Dict[int, int],
) -> Iterator[PickupEntry]:
    """
    Creates the necessary pickups for the given ammo_configuration.
    :param resource_database:
    :param ammo_configuration:
    :param included_ammo_for_item: How much of each item was provided based on the major items.
    :return:
    """

    previous_pickup_for_item = {}

    for ammo, state in ammo_configuration.items_state.items():
        if state.pickup_count == 0:
            continue

        if state.variance != 0:
            raise InvalidConfiguration(
                "Variance was configured for {0.name}, but it is currently NYI"
                .format(ammo))

        ammo_per_pickup = items_for_ammo(ammo, state, included_ammo_for_item,
                                         previous_pickup_for_item,
                                         ammo_configuration.maximum_ammo)

        for ammo_quantity in ammo_per_pickup:
            yield create_ammo_expansion(ammo, ammo_quantity,
                                        state.requires_major_item,
                                        resource_database)
Example #5
0
def _validate_item_pool_size(item_pool: List[PickupEntry],
                             game: GameDescription) -> None:
    num_pickup_nodes = len(list(filter_pickup_nodes(
        game.world_list.all_nodes)))
    if len(item_pool) > num_pickup_nodes:
        raise InvalidConfiguration(
            "Item pool has {0} items, but there's only {1} pickups spots in the game"
            .format(len(item_pool), num_pickup_nodes))
Example #6
0
def add_major_items(
    resource_database: ResourceDatabase,
    major_items_configuration: MajorItemsConfiguration,
    ammo_configuration: AmmoConfiguration,
) -> PoolResults:
    """

    :param resource_database:
    :param major_items_configuration:
    :param ammo_configuration:
    :return:
    """

    item_pool: List[PickupEntry] = []
    new_assignment: Dict[PickupIndex, PickupEntry] = {}
    initial_resources: CurrentResources = {}

    for item, state in major_items_configuration.items_state.items():
        if len(item.ammo_index) != len(state.included_ammo):
            raise InvalidConfiguration(
                "Item {0.name} uses {0.ammo_index} as ammo, but there's only {1} values in included_ammo"
                .format(item, len(state.included_ammo)))

        ammo, locked_ammo = _find_ammo_for(item.ammo_index, ammo_configuration)

        if state.include_copy_in_original_location:
            if item.original_index is None:
                raise InvalidConfiguration(
                    "Item {0.name} does not exist in the original game, cannot use state {1}"
                    .format(item, state), )
            new_assignment[item.original_index] = create_major_item(
                item, state, True, resource_database, ammo, locked_ammo)

        for _ in range(state.num_shuffled_pickups):
            item_pool.append(
                create_major_item(item, state, True, resource_database, ammo,
                                  locked_ammo))

        for _ in range(state.num_included_in_starting_items):
            add_resource_gain_to_current_resources(
                create_major_item(item, state, False, resource_database, ammo,
                                  locked_ammo).all_resources,
                initial_resources)

    return PoolResults(item_pool, new_assignment, initial_resources)
Example #7
0
def _assign_remaining_items(
    rng: Random,
    world_list: WorldList,
    pickup_assignment: PickupAssignment,
    remaining_items: List[PickupEntry],
    randomization_mode: RandomizationMode,
) -> PickupAssignment:
    """

    :param rng:
    :param world_list:
    :param pickup_assignment:
    :param remaining_items:
    :return:
    """

    unassigned_pickup_nodes = list(
        filter_unassigned_pickup_nodes(world_list.all_nodes,
                                       pickup_assignment))

    num_etm = len(unassigned_pickup_nodes) - len(remaining_items)
    if num_etm < 0:
        raise InvalidConfiguration(
            "Received {} remaining items, but there's only {} unassigned pickups"
            .format(len(remaining_items), len(unassigned_pickup_nodes)))

    # Shuffle the items to add and the spots to choose from
    rng.shuffle(remaining_items)
    rng.shuffle(unassigned_pickup_nodes)

    assignment = {}

    if randomization_mode is RandomizationMode.MAJOR_MINOR_SPLIT:
        remaining_majors = [
            item for item in remaining_items if not item.is_expansion
        ] + ([None] * num_etm)
        unassigned_major_locations = [
            pickup_node for pickup_node in unassigned_pickup_nodes
            if pickup_node.major_location
        ]

        for pickup_node, item in zip(unassigned_major_locations,
                                     remaining_majors):
            if item is not None:
                assignment[pickup_node.pickup_index] = item
                remaining_items.remove(item)
            unassigned_pickup_nodes.remove(pickup_node)

    assignment.update({
        pickup_node.pickup_index: item
        for pickup_node, item in zip(unassigned_pickup_nodes, remaining_items)
    })
    return assignment
Example #8
0
def add_sky_temple_key_distribution_logic(
    resource_database: ResourceDatabase,
    mode: LayoutSkyTempleKeyMode,
) -> PoolResults:
    """
    Adds the given Sky Temple Keys to the item pool
    :param resource_database:
    :param mode:
    :return:
    """

    item_pool: List[PickupEntry] = []
    new_assignment: Dict[PickupIndex, PickupEntry] = {}
    initial_resources: CurrentResources = {}

    if mode == LayoutSkyTempleKeyMode.ALL_BOSSES or mode == LayoutSkyTempleKeyMode.ALL_GUARDIANS:
        locations_to_place = _GUARDIAN_INDICES[:]
        if mode == LayoutSkyTempleKeyMode.ALL_BOSSES:
            locations_to_place += _SUB_GUARDIAN_INDICES

        for key_number, location in enumerate(locations_to_place):
            new_assignment[location] = create_sky_temple_key(
                key_number, resource_database)
        first_automatic_key = len(locations_to_place)

    else:
        keys_to_place = mode.value
        if not isinstance(keys_to_place, int):
            raise InvalidConfiguration(
                "Unknown Sky Temple Key mode: {}".format(mode))

        for key_number in range(keys_to_place):
            item_pool.append(
                create_sky_temple_key(key_number, resource_database))
        first_automatic_key = keys_to_place

    for automatic_key_number in range(first_automatic_key, 9):
        add_resource_gain_to_current_resources(
            create_sky_temple_key(automatic_key_number,
                                  resource_database).all_resources,
            initial_resources)

    return PoolResults(item_pool, new_assignment, initial_resources)
Example #9
0
def _distribute_remaining_items(
    rng: Random,
    filler_results: FillerResults,
) -> FillerResults:
    unassigned_pickup_nodes = []
    all_remaining_pickups = []
    assignments: Dict[int, list[PickupTargetAssociation]] = {}

    for player, filler_result in filler_results.player_results.items():
        for pickup_node in filter_unassigned_pickup_nodes(
                filler_result.game.world_list.iterate_nodes(),
                filler_result.patches.pickup_assignment):
            unassigned_pickup_nodes.append((player, pickup_node))

        all_remaining_pickups.extend(
            zip([player] * len(filler_result.unassigned_pickups),
                filler_result.unassigned_pickups))
        assignments[player] = []

    rng.shuffle(unassigned_pickup_nodes)
    rng.shuffle(all_remaining_pickups)

    if len(all_remaining_pickups) > len(unassigned_pickup_nodes):
        raise InvalidConfiguration(
            "Received {} remaining pickups, but there's only {} unassigned locations."
            .format(len(all_remaining_pickups), len(unassigned_pickup_nodes)))

    for (node_player, node), (pickup_player,
                              pickup) in zip(unassigned_pickup_nodes,
                                             all_remaining_pickups):
        assignments[node_player].append(
            (node.pickup_index, PickupTarget(pickup, pickup_player)))

    return dataclasses.replace(
        filler_results,
        player_results={
            player: dataclasses.replace(
                result,
                patches=result.patches.assign_new_pickups(assignments[player]))
            for player, result in filler_results.player_results.items()
        })
Example #10
0
def create_major_item(
    item: MajorItem,
    state: MajorItemState,
    include_percentage: bool,
    resource_database: ResourceDatabase,
    ammo: Optional[Ammo],
    ammo_requires_major_item: bool,
) -> PickupEntry:
    """
    Creates a Pickup for the given MajorItem
    :param include_percentage:
    :param state:
    :param item:
    :param resource_database:
    :param ammo:
    :param ammo_requires_major_item:
    :return:
    """
    def _create_resources(
            base_resource: Optional[int],
            temporary_ammo: bool = False) -> Tuple[ResourceQuantity, ...]:
        resources = []

        if base_resource is not None:
            resources.append((resource_database.get_item(base_resource), 1))

        for ammo_index, ammo_count in zip(
                ammo.temporaries if temporary_ammo else item.ammo_index,
                state.included_ammo):
            resources.append(
                (resource_database.get_item(ammo_index), ammo_count))

        if include_percentage:
            resources.append((resource_database.item_percentage, 1))

        return tuple(resources)

    if item.progression:
        if ammo_requires_major_item and ammo.unlocked_by != item.progression[
                0] and ammo.unlocked_by is not None:
            if len(item.progression) != 1:
                raise InvalidConfiguration((
                    "Item {item.name} uses ammo {ammo.name} that is locked behind {ammo.unlocked_by},"
                    "but it also has progression. This is unsupported."
                ).format(
                    ammo=ammo,
                    item=item,
                ))

            name = resource_database.get_item(item.progression[0]).long_name
            conditional_resources = (ConditionalResources(
                name=name,
                item=None,
                resources=_create_resources(item.progression[0], True)),
                                     ConditionalResources(
                                         name=name,
                                         item=resource_database.get_item(
                                             ammo.unlocked_by),
                                         resources=_create_resources(
                                             item.progression[0])))
        else:
            conditional_resources = tuple(
                ConditionalResources(
                    name=resource_database.get_item(
                        item.progression[i]).long_name,
                    item=resource_database.get_item(item.progression[i - 1]
                                                    ) if i > 0 else None,
                    resources=_create_resources(progression))
                for i, progression in enumerate(item.progression))
    else:
        conditional_resources = (ConditionalResources(
            name=item.name, item=None, resources=_create_resources(None)), )

    if item.converts_indices:
        assert len(item.converts_indices) == len(item.ammo_index)
        convert_resources = tuple(
            ResourceConversion(
                source=resource_database.get_item(source),
                target=resource_database.get_item(target),
            )
            for source, target in zip(item.converts_indices, item.ammo_index))
    else:
        convert_resources = tuple()

    return PickupEntry(
        name=item.name,
        resources=conditional_resources,
        model_index=item.model_index,
        item_category=item.item_category,
        broad_category=item.broad_category,
        probability_offset=item.probability_offset,
        probability_multiplier=item.probability_multiplier,
        convert_resources=convert_resources,
    )