コード例 #1
0
def _patches_with_data(request, echoes_game_data, echoes_item_database):
    game = data_reader.decode_data(echoes_game_data)

    data = {
        "starting_location": "Temple Grounds/Landing Site",
        "starting_items": {},
        "elevators": {
            "Temple Grounds/Temple Transport C":
            "Great Temple/Temple Transport C",
            "Temple Grounds/Transport to Agon Wastes":
            "Agon Wastes/Transport to Temple Grounds",
            "Temple Grounds/Transport to Torvus Bog":
            "Torvus Bog/Transport to Temple Grounds",
            "Temple Grounds/Temple Transport B":
            "Great Temple/Temple Transport B",
            "Temple Grounds/Transport to Sanctuary Fortress":
            "Sanctuary Fortress/Transport to Temple Grounds",
            "Temple Grounds/Temple Transport A":
            "Great Temple/Temple Transport A",
            "Great Temple/Temple Transport A":
            "Temple Grounds/Temple Transport A",
            "Great Temple/Temple Transport C":
            "Temple Grounds/Temple Transport C",
            "Great Temple/Temple Transport B":
            "Temple Grounds/Temple Transport B",
            "Sky Temple Grounds/Sky Temple Gateway":
            "Sky Temple/Sky Temple Energy Controller",
            "Sky Temple/Sky Temple Energy Controller":
            "Sky Temple Grounds/Sky Temple Gateway",
            "Agon Wastes/Transport to Temple Grounds":
            "Temple Grounds/Transport to Agon Wastes",
            "Agon Wastes/Transport to Torvus Bog":
            "Torvus Bog/Transport to Agon Wastes",
            "Agon Wastes/Transport to Sanctuary Fortress":
            "Sanctuary Fortress/Transport to Agon Wastes",
            "Torvus Bog/Transport to Temple Grounds":
            "Temple Grounds/Transport to Torvus Bog",
            "Torvus Bog/Transport to Agon Wastes":
            "Agon Wastes/Transport to Torvus Bog",
            "Torvus Bog/Transport to Sanctuary Fortress":
            "Sanctuary Fortress/Transport to Torvus Bog",
            "Sanctuary Fortress/Transport to Temple Grounds":
            "Temple Grounds/Transport to Sanctuary Fortress",
            "Sanctuary Fortress/Transport to Agon Wastes":
            "Agon Wastes/Transport to Sanctuary Fortress",
            "Sanctuary Fortress/Transport to Torvus Bog":
            "Torvus Bog/Transport to Sanctuary Fortress",
            "Sanctuary Fortress/Aerie":
            "Sanctuary Fortress/Aerie Transport Station",
            "Sanctuary Fortress/Aerie Transport Station":
            "Sanctuary Fortress/Aerie",
        },
        "translators": {},
        "locations": {},
        "hints": {},
        "_locations_internal": "",
    }
    patches = dataclasses.replace(game.create_game_patches(), player_index=0)

    locations = collections.defaultdict(dict)
    for world, area, node in game.world_list.all_worlds_areas_nodes:
        if node.is_resource_node and isinstance(node, PickupNode):
            world_name = world.dark_name if area.in_dark_aether else world.name
            locations[world_name][game.world_list.node_name(
                node)] = game_patches_serializer._ETM_NAME

    data["locations"] = {
        world: {area: item
                for area, item in sorted(locations[world].items())}
        for world in sorted(locations.keys())
    }

    if request.param.get("starting_item"):
        item_name = request.param.get("starting_item")
        patches = patches.assign_extra_initial_items({
            find_resource_info_with_long_name(game.resource_database.item, item_name):
            1,
        })
        data["starting_items"][item_name] = 1

    if request.param.get("elevator"):
        elevator_id, elevator_source = request.param.get("elevator")
        elevator_connection = copy.copy(patches.elevator_connection)
        elevator_connection[elevator_id] = game.starting_location

        patches = dataclasses.replace(patches,
                                      elevator_connection=elevator_connection)
        data["elevators"][elevator_source] = "Temple Grounds/Landing Site"

    if request.param.get("translator"):
        gates = {}
        for index, gate_name, translator in request.param.get("translator"):
            gates[TranslatorGate(index)] = find_resource_info_with_long_name(
                game.resource_database.item, translator)
            data["translators"][gate_name] = translator

        patches = patches.assign_gate_assignment(gates)

    if request.param.get("pickup"):
        data["_locations_internal"], pickup_name = request.param.get("pickup")
        pickup = pickup_creator.create_major_item(
            echoes_item_database.major_items[pickup_name], MajorItemState(),
            True, game.resource_database, None, False)

        patches = patches.assign_new_pickups([(PickupIndex(5),
                                               PickupTarget(pickup, 0))])
        data["locations"]["Temple Grounds"][
            'Transport to Agon Wastes/Pickup (Missile)'] = pickup_name

    if request.param.get("hint"):
        asset, hint = request.param.get("hint")
        patches = patches.assign_hint(LogbookAsset(asset),
                                      Hint.from_json(hint))
        data["hints"][str(asset)] = hint

    return data, patches
コード例 #2
0
ファイル: retcon.py プロジェクト: juliabenton/randovania
def retcon_playthrough_filler(
    game: GameDescription,
    initial_state: State,
    pickups_left: List[PickupEntry],
    rng: Random,
    randomization_mode: RandomizationMode,
    minimum_random_starting_items: int,
    maximum_random_starting_items: int,
    status_update: Callable[[str], None],
) -> GamePatches:
    debug.debug_print("Major items: {}".format(
        [item.name for item in pickups_left]))
    last_message = "Starting."

    reach = advance_reach_with_possible_unsafe_resources(
        reach_with_all_safe_resources(game, initial_state))

    pickup_index_seen_count: DefaultDict[PickupIndex,
                                         int] = collections.defaultdict(int)
    scan_asset_seen_count: DefaultDict[LogbookAsset,
                                       int] = collections.defaultdict(int)
    num_random_starting_items_placed = 0

    while pickups_left:
        current_uncollected = UncollectedState.from_reach(reach)

        progression_pickups = _calculate_progression_pickups(
            pickups_left, reach)
        print_retcon_loop_start(current_uncollected, game, pickups_left, reach)

        for pickup_index in reach.state.collected_pickup_indices:
            pickup_index_seen_count[pickup_index] += 1
        print_new_resources(game, reach, pickup_index_seen_count,
                            "Pickup Index")

        for scan_asset in reach.state.collected_scan_assets:
            scan_asset_seen_count[scan_asset] += 1
        print_new_resources(game, reach, scan_asset_seen_count, "Scan Asset")

        def action_report(message: str):
            status_update("{} {}".format(last_message, message))

        actions_weights = _calculate_potential_actions(
            reach, progression_pickups, current_uncollected,
            maximum_random_starting_items - num_random_starting_items_placed,
            action_report)

        try:
            action = next(
                iterate_with_weights(items=list(actions_weights.keys()),
                                     item_weights=actions_weights,
                                     rng=rng))
        except StopIteration:
            if actions_weights:
                action = rng.choice(list(actions_weights.keys()))
            else:
                raise UnableToGenerate(
                    "Unable to generate; no actions found after placing {} items."
                    .format(len(reach.state.patches.pickup_assignment)))

        if isinstance(action, PickupEntry):
            assert action in pickups_left

            if randomization_mode is RandomizationMode.FULL:
                uncollected_indices = current_uncollected.indices

            elif randomization_mode is RandomizationMode.MAJOR_MINOR_SPLIT:
                major_indices = {
                    pickup_node.pickup_index
                    for pickup_node in filter_pickup_nodes(
                        reach.state.collected_resource_nodes)
                    if pickup_node.major_location
                }
                uncollected_indices = current_uncollected.indices & major_indices

            else:
                raise RuntimeError("Unknown randomization_mode: {}".format(
                    randomization_mode))

            if num_random_starting_items_placed >= minimum_random_starting_items and uncollected_indices:
                pickup_index_weights = _calculate_uncollected_index_weights(
                    uncollected_indices,
                    set(reach.state.patches.pickup_assignment),
                    pickup_index_seen_count,
                    randomization_mode,
                    game.world_list,
                )
                assert pickup_index_weights, "Pickups should only be added to the actions dict " \
                                             "when there are unassigned pickups"

                pickup_index = next(
                    iterate_with_weights(items=iter(uncollected_indices),
                                         item_weights=pickup_index_weights,
                                         rng=rng))

                next_state = reach.state.assign_pickup_to_index(
                    action, pickup_index)
                if current_uncollected.logbooks and _should_have_hint(
                        action.item_category):
                    hint_location: Optional[LogbookAsset] = rng.choice(
                        list(current_uncollected.logbooks))
                    next_state.patches = next_state.patches.assign_hint(
                        hint_location,
                        Hint(HintType.LOCATION, None, pickup_index))
                else:
                    hint_location = None

                print_retcon_place_pickup(action, game, pickup_index,
                                          hint_location)

            else:
                num_random_starting_items_placed += 1
                if num_random_starting_items_placed > maximum_random_starting_items:
                    raise UnableToGenerate(
                        "Attempting to place more extra starting items than the number allowed."
                    )

                if debug.debug_level() > 1:
                    print(f"\n--> Adding {action.name} as a starting item")

                next_state = reach.state.assign_pickup_to_starting_items(
                    action)

            # TODO: this item is potentially dangerous and we should remove the invalidated paths
            pickups_left.remove(action)

            last_message = "Placed {} items so far, {} left.".format(
                len(next_state.patches.pickup_assignment),
                len(pickups_left) - 1)
            status_update(last_message)

            reach.advance_to(next_state)

        else:
            last_message = "Triggered an event out of {} options.".format(
                len(actions_weights))
            status_update(last_message)
            debug_print_collect_event(action, game)
            # This action is potentially dangerous. Use `act_on` to remove invalid paths
            reach.act_on(action)

        reach = advance_reach_with_possible_unsafe_resources(reach)

        if game.victory_condition.satisfied(reach.state.resources,
                                            reach.state.energy):
            debug.debug_print("Finished because we can win")
            break

    if not pickups_left:
        debug.debug_print(
            "Finished because we have nothing else to distribute")

    return reach.state.patches
コード例 #3
0
def decode(game_modifications: dict,
           configuration: LayoutConfiguration) -> GamePatches:
    """
    Decodes a dict created by `serialize` back into a GamePatches.
    :param game_modifications:
    :param configuration:
    :return:
    """
    game = data_reader.decode_data(configuration.game_data)
    world_list = game.world_list

    # Starting Location
    starting_location = _area_name_to_area_location(
        world_list, game_modifications["starting_location"])

    # Initial items
    starting_items = {
        find_resource_info_with_long_name(game.resource_database.item,
                                          resource_name): quantity
        for resource_name, quantity in
        game_modifications["starting_items"].items()
    }

    # Elevators
    elevator_connection = {}
    for source_name, target_name in game_modifications["elevators"].items():
        source_area = _area_name_to_area_location(world_list, source_name)
        target_area = _area_name_to_area_location(world_list, target_name)

        potential_source_nodes = [
            node
            for node in world_list.area_by_area_location(source_area).nodes
            if isinstance(node, TeleporterNode)
        ]
        assert len(potential_source_nodes) == 1
        source_node = potential_source_nodes[0]
        elevator_connection[source_node.teleporter_instance_id] = target_area

    # Translator Gates
    translator_gates = {
        _find_gate_with_name(gate_name):
        find_resource_info_with_long_name(game.resource_database.item,
                                          resource_name)
        for gate_name, resource_name in
        game_modifications["translators"].items()
    }

    # Pickups
    index_to_pickup_name = {}
    for world_name, world_data in game_modifications["locations"].items():
        for area_node_name, pickup_name in world_data.items():
            if pickup_name == _ETM_NAME:
                continue

            node = world_list.node_from_name(f"{world_name}/{area_node_name}")
            assert isinstance(node, PickupNode)
            index_to_pickup_name[node.pickup_index] = pickup_name

    decoder = BitPackDecoder(
        base64.b64decode(
            game_modifications["_locations_internal"].encode("utf-8"),
            validate=True))
    pickup_assignment = dict(
        BitPackPickupEntryList.bit_pack_unpack(
            decoder, {
                "index_mapping": index_to_pickup_name,
                "database": game.resource_database,
            }).value)

    # Hints
    hints = {}
    for asset_id, hint in game_modifications["hints"].items():
        hints[LogbookAsset(int(asset_id))] = Hint.from_json(hint)

    return GamePatches(
        pickup_assignment=pickup_assignment,  # PickupAssignment
        elevator_connection=elevator_connection,  # Dict[int, AreaLocation]
        dock_connection={},  # Dict[Tuple[int, int], DockConnection]
        dock_weakness={},  # Dict[Tuple[int, int], DockWeakness]
        translator_gates=translator_gates,
        starting_items=starting_items,  # ResourceGainTuple
        starting_location=starting_location,  # AreaLocation
        hints=hints,
    )
コード例 #4
0
 def _guardian_hint(number: int):
     return Hint(HintType.GUARDIAN, PrecisionPair.detailed(),
                 PickupIndex(number))
コード例 #5
0
 def _keybearer_hint(number: int):
     return Hint(
         HintType.KEYBEARER,
         PrecisionPair(HintLocationPrecision.DETAILED,
                       HintItemPrecision.PRECISE_CATEGORY),
         PickupIndex(number))
コード例 #6
0
 def _keybearer_hint(number: int):
     return Hint(
         HintType.LOCATION,
         PrecisionPair(HintLocationPrecision.KEYBEARER,
                       HintItemPrecision.BROAD_CATEGORY,
                       include_owner=True), PickupIndex(number))
コード例 #7
0
 def _light_suit_location_hint(number: int):
     return Hint(HintType.LIGHT_SUIT_LOCATION, PrecisionPair.detailed(),
                 PickupIndex(number))
コード例 #8
0
 def _guardian_hint(number: int):
     return Hint(
         HintType.LOCATION,
         PrecisionPair(HintLocationPrecision.GUARDIAN,
                       HintItemPrecision.DETAILED,
                       include_owner=False), PickupIndex(number))
コード例 #9
0
 def _light_suit_location_hint(number: int):
     return Hint(
         HintType.LOCATION,
         PrecisionPair(HintLocationPrecision.LIGHT_SUIT_LOCATION,
                       HintItemPrecision.DETAILED,
                       include_owner=False), PickupIndex(number))
コード例 #10
0
def test_add_default_hints_to_patches(echoes_game_description, empty_patches,
                                      is_multiworld):
    # Setup
    rng = MagicMock()

    def _light_suit_location_hint(number: int):
        return Hint(
            HintType.LOCATION,
            PrecisionPair(HintLocationPrecision.LIGHT_SUIT_LOCATION,
                          HintItemPrecision.DETAILED,
                          include_owner=False), PickupIndex(number))

    def _guardian_hint(number: int):
        return Hint(
            HintType.LOCATION,
            PrecisionPair(HintLocationPrecision.GUARDIAN,
                          HintItemPrecision.DETAILED,
                          include_owner=False), PickupIndex(number))

    def _keybearer_hint(number: int):
        return Hint(
            HintType.LOCATION,
            PrecisionPair(HintLocationPrecision.KEYBEARER,
                          HintItemPrecision.BROAD_CATEGORY,
                          include_owner=True), PickupIndex(number))

    expected = {
        # Keybearer
        LogbookAsset(0xE3B417BF):
        _keybearer_hint(11),
        LogbookAsset(0x65206511):
        _keybearer_hint(15),
        LogbookAsset(0x28E8C41A):
        _keybearer_hint(19),
        # Agon
        LogbookAsset(0x150E8DB8):
        _keybearer_hint(45),
        LogbookAsset(0xDE525E1D):
        _keybearer_hint(53),
        # Torvus
        LogbookAsset(0x58C62CB3):
        _keybearer_hint(68),
        LogbookAsset(0x939AFF16):
        _keybearer_hint(91),
        # Sanctuary
        LogbookAsset(0x62CC4DC3):
        _keybearer_hint(117),
        LogbookAsset(0xA9909E66):
        _keybearer_hint(106),

        # Locations with hints
        LogbookAsset(1041207119):
        _light_suit_location_hint(24),
        LogbookAsset(4115881194):
        _guardian_hint(43),
        LogbookAsset(1948976790):
        _guardian_hint(79),
        LogbookAsset(3212301619):
        _guardian_hint(115),

        # Dark Temple hints
        LogbookAsset(67497535):
        Hint(HintType.RED_TEMPLE_KEY_SET,
             None,
             dark_temple=HintDarkTemple.AGON_WASTES),
        LogbookAsset(4072633400):
        Hint(HintType.RED_TEMPLE_KEY_SET,
             None,
             dark_temple=HintDarkTemple.TORVUS_BOG),
        LogbookAsset(0x82919C91):
        Hint(HintType.RED_TEMPLE_KEY_SET,
             None,
             dark_temple=HintDarkTemple.SANCTUARY_FORTRESS),

        # Jokes
        LogbookAsset(0x49CD4F34):
        Hint(HintType.JOKE, None),
        LogbookAsset(0x9F94AC29):
        Hint(HintType.JOKE, None),
    }

    # Run
    result = base_patches_factory.add_echoes_default_hints_to_patches(
        rng,
        empty_patches,
        echoes_game_description.world_list,
        num_joke=2,
        is_multiworld=is_multiworld)

    # Assert
    rng.shuffle.assert_has_calls([call(ANY), call(ANY)])
    assert result.hints == expected
コード例 #11
0
def decode_single(player_index: int, all_pools: dict[int, PoolResults],
                  game: GameDescription, game_modifications: dict,
                  configuration: BaseConfiguration) -> GamePatches:
    """
    Decodes a dict created by `serialize` back into a GamePatches.
    :param player_index:
    :param all_pools:
    :param game:
    :param game_modifications:
    :param configuration:
    :return:
    """
    world_list = game.world_list
    weakness_db = game.dock_weakness_database

    if game_modifications["game"] != game.game.value:
        raise ValueError(
            f"Expected '{game.game.value}', got '{game_modifications['game']}'"
        )

    initial_pickup_assignment = all_pools[player_index].assignment

    # Starting Location
    starting_location = AreaIdentifier.from_string(
        game_modifications["starting_location"])

    # Initial items
    starting_items = ResourceCollection.from_dict(
        game.resource_database, {
            find_resource_info_with_long_name(game.resource_database.item,
                                              resource_name): quantity
            for resource_name, quantity in
            game_modifications["starting_items"].items()
        })

    # Elevators
    elevator_connection = [(world_list.get_teleporter_node(
        NodeIdentifier.from_string(source_name)),
                            AreaIdentifier.from_string(target_name))
                           for source_name, target_name in
                           game_modifications["teleporters"].items()]

    # Dock Weakness
    def get_dock(ni: NodeIdentifier):
        result = game.world_list.node_by_identifier(ni)
        assert isinstance(result, DockNode)
        return result

    dock_weakness = [(get_dock(NodeIdentifier.from_string(source_name)),
                      weakness_db.get_by_weakness(
                          weakness_data["type"],
                          weakness_data["name"],
                      )) for source_name, weakness_data in
                     game_modifications["dock_weakness"].items()]

    # Configurable Nodes
    configurable_nodes = {
        NodeIdentifier.from_string(identifier):
        data_reader.read_requirement(requirement, game.resource_database)
        for identifier, requirement in
        game_modifications["configurable_nodes"].items()
    }

    # Pickups
    target_name_re = re.compile(r"(.*) for Player (\d+)")

    pickup_assignment: PickupAssignment = {}
    for world_name, world_data in game_modifications["locations"].items():
        for area_node_name, target_name in typing.cast(dict[str, str],
                                                       world_data).items():
            if target_name == _ETM_NAME:
                continue

            pickup_name_match = target_name_re.match(target_name)
            if pickup_name_match is not None:
                pickup_name = pickup_name_match.group(1)
                target_player = int(pickup_name_match.group(2)) - 1
            else:
                pickup_name = target_name
                target_player = 0

            node_identifier = NodeIdentifier.create(
                world_name, *area_node_name.split("/", 1))
            node = world_list.node_by_identifier(node_identifier)
            assert isinstance(node, PickupNode)
            if node.pickup_index in initial_pickup_assignment:
                pickup = initial_pickup_assignment[node.pickup_index]
                if (pickup_name, target_player) != (pickup.name, player_index):
                    raise ValueError(
                        f"{area_node_name} should be vanilla based on configuration"
                    )

            elif pickup_name != _ETM_NAME:
                configuration_item_pool = all_pools[target_player].pickups
                pickup = _find_pickup_with_name(configuration_item_pool,
                                                pickup_name)
                configuration_item_pool.remove(pickup)
            else:
                pickup = None

            if pickup is not None:
                pickup_assignment[node.pickup_index] = PickupTarget(
                    pickup, target_player)

    # Hints
    hints = {}
    for identifier_str, hint in game_modifications["hints"].items():
        hints[NodeIdentifier.from_string(identifier_str)] = Hint.from_json(
            hint)

    patches = GamePatches.create_from_game(game, player_index, configuration)
    patches = patches.assign_dock_weakness(dock_weakness)
    patches = patches.assign_elevators(elevator_connection)
    return dataclasses.replace(
        patches,
        pickup_assignment=pickup_assignment,  # PickupAssignment
        configurable_nodes=configurable_nodes,
        starting_items=starting_items,  # ResourceGainTuple
        starting_location=starting_location,  # AreaIdentifier
        hints=hints,
    )
コード例 #12
0
def _patches_with_data(request, echoes_game_description, echoes_game_patches,
                       echoes_item_database):
    game = echoes_game_description
    db = game.resource_database

    data = {
        "game": echoes_game_description.game.value,
        "starting_location": "Temple Grounds/Landing Site",
        "starting_items": {},
        "teleporters": {
            "Temple Grounds/Temple Transport C/Elevator to Great Temple - Temple Transport C":
            "Great Temple/Temple Transport C",
            "Temple Grounds/Transport to Agon Wastes/Elevator to Agon Wastes - Transport to Temple Grounds":
            "Agon Wastes/Transport to Temple Grounds",
            "Temple Grounds/Transport to Torvus Bog/Elevator to Torvus Bog - Transport to Temple Grounds":
            "Torvus Bog/Transport to Temple Grounds",
            "Temple Grounds/Temple Transport B/Elevator to Great Temple - Temple Transport B":
            "Great Temple/Temple Transport B",
            "Temple Grounds/Transport to Sanctuary Fortress/Elevator to Sanctuary Fortress - Transport to Temple Grounds":
            "Sanctuary Fortress/Transport to Temple Grounds",
            "Temple Grounds/Temple Transport A/Elevator to Great Temple - Temple Transport A":
            "Great Temple/Temple Transport A",
            "Great Temple/Temple Transport A/Elevator to Temple Grounds - Temple Transport A":
            "Temple Grounds/Temple Transport A",
            "Great Temple/Temple Transport C/Elevator to Temple Grounds - Temple Transport C":
            "Temple Grounds/Temple Transport C",
            "Great Temple/Temple Transport B/Elevator to Temple Grounds - Temple Transport B":
            "Temple Grounds/Temple Transport B",
            "Temple Grounds/Sky Temple Gateway/Teleport to Great Temple - Sky Temple Energy Controller":
            "Great Temple/Sky Temple Energy Controller",
            "Great Temple/Sky Temple Energy Controller/Teleport to Temple Grounds - Sky Temple Gateway":
            "Temple Grounds/Sky Temple Gateway",
            "Agon Wastes/Transport to Temple Grounds/Elevator to Temple Grounds - Transport to Agon Wastes":
            "Temple Grounds/Transport to Agon Wastes",
            "Agon Wastes/Transport to Torvus Bog/Elevator to Torvus Bog - Transport to Agon Wastes":
            "Torvus Bog/Transport to Agon Wastes",
            "Agon Wastes/Transport to Sanctuary Fortress/Elevator to Sanctuary Fortress - Transport to Agon Wastes":
            "Sanctuary Fortress/Transport to Agon Wastes",
            "Torvus Bog/Transport to Temple Grounds/Elevator to Temple Grounds - Transport to Torvus Bog":
            "Temple Grounds/Transport to Torvus Bog",
            "Torvus Bog/Transport to Agon Wastes/Elevator to Agon Wastes - Transport to Torvus Bog":
            "Agon Wastes/Transport to Torvus Bog",
            "Torvus Bog/Transport to Sanctuary Fortress/Elevator to Sanctuary Fortress - Transport to Torvus Bog":
            "Sanctuary Fortress/Transport to Torvus Bog",
            "Sanctuary Fortress/Transport to Temple Grounds/Elevator to Temple Grounds - Transport to Sanctuary Fortress":
            "Temple Grounds/Transport to Sanctuary Fortress",
            "Sanctuary Fortress/Transport to Agon Wastes/Elevator to Agon Wastes - Transport to Sanctuary Fortress":
            "Agon Wastes/Transport to Sanctuary Fortress",
            "Sanctuary Fortress/Transport to Torvus Bog/Elevator to Torvus Bog - Transport to Sanctuary Fortress":
            "Torvus Bog/Transport to Sanctuary Fortress",
            "Sanctuary Fortress/Aerie/Elevator to Sanctuary Fortress - Aerie Transport Station":
            "Sanctuary Fortress/Aerie Transport Station",
            "Sanctuary Fortress/Aerie Transport Station/Elevator to Sanctuary Fortress - Aerie":
            "Sanctuary Fortress/Aerie",
        },
        "dock_weakness": {},
        "configurable_nodes": {},
        "locations": {},
        "hints": {}
    }
    patches = dataclasses.replace(echoes_game_patches, player_index=0)

    locations = collections.defaultdict(dict)
    for world, area, node in game.world_list.all_worlds_areas_nodes:
        if node.is_resource_node and isinstance(node, PickupNode):
            world_name = world.dark_name if area.in_dark_aether else world.name
            locations[world_name][game.world_list.node_name(
                node)] = game_patches_serializer._ETM_NAME

    data["locations"] = {
        world: {area: item
                for area, item in sorted(locations[world].items())}
        for world in sorted(locations.keys())
    }

    if request.param.get("starting_item"):
        item_name = request.param.get("starting_item")
        patches = patches.assign_extra_initial_items({
            db.get_item_by_name(item_name):
            1,
        })
        data["starting_items"][item_name] = 1

    if request.param.get("elevator"):
        teleporter = request.param.get("elevator")
        elevator_connection = copy.copy(patches.elevator_connection)
        elevator_connection[teleporter] = game.starting_location

        patches = dataclasses.replace(patches,
                                      elevator_connection=elevator_connection)
        data["teleporters"][
            teleporter.as_string] = "Temple Grounds/Landing Site"

    if request.param.get("configurable_nodes"):
        gates = {}
        for identifier, translator in request.param.get("configurable_nodes"):
            requirement = ResourceRequirement(db.get_item(translator), 1,
                                              False)
            gates[NodeIdentifier.from_string(identifier)] = requirement
            data["configurable_nodes"][
                identifier] = data_writer.write_requirement(requirement)

        patches = patches.assign_node_configuration(gates)

    if request.param.get("pickup"):
        pickup_name = request.param.get("pickup")
        pickup = pickup_creator.create_major_item(
            echoes_item_database.major_items[pickup_name], MajorItemState(),
            True, game.resource_database, None, False)

        patches = patches.assign_new_pickups([(PickupIndex(5),
                                               PickupTarget(pickup, 0))])
        data["locations"]["Temple Grounds"][
            'Transport to Agon Wastes/Pickup (Missile)'] = pickup_name

    if request.param.get("hint"):
        identifier, hint = request.param.get("hint")
        patches = patches.assign_hint(NodeIdentifier.from_string(identifier),
                                      Hint.from_json(hint))
        data["hints"][identifier] = hint

    return data, patches