Esempio n. 1
0
    def from_json(cls, value: dict) -> "TranslatorConfiguration":
        default = cls.default()

        params = copy.copy(value)

        translator_requirement = copy.copy(default.translator_requirement)
        for key, item in params.pop("translator_requirement").items():
            translator_requirement[NodeIdentifier.from_string(key)] = LayoutTranslatorRequirement(item)

        return cls(translator_requirement, **params)
async def test_add_default_hints_to_patches(echoes_game_description,
                                            empty_patches, is_multiworld):
    # Setup
    layout_configuration = MagicMock()
    layout_configuration.game = RandovaniaGame.METROID_PRIME_ECHOES
    rng = MagicMock()
    hint_distributor = EchoesHintDistributor()

    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
        'Temple Grounds/Landing Site/Keybearer Corpse (M-Dhe)':
        _keybearer_hint(11),
        'Temple Grounds/Industrial Site/Keybearer Corpse (J-Fme)':
        _keybearer_hint(15),
        'Temple Grounds/Storage Cavern A/Keybearer Corpse (D-Isl)':
        _keybearer_hint(19),
        # Agon
        'Agon Wastes/Central Mining Station/Keybearer Corpse (J-Stl)':
        _keybearer_hint(45),
        'Agon Wastes/Main Reactor/Keybearer Corpse (B-Stl)':
        _keybearer_hint(53),
        # Torvus
        'Torvus Bog/Torvus Lagoon/Keybearer Corpse (S-Dly)':
        _keybearer_hint(68),
        'Torvus Bog/Catacombs/Keybearer Corpse (G-Sch)':
        _keybearer_hint(91),
        # Sanctuary
        'Sanctuary Fortress/Sanctuary Entrance/Keybearer Corpse (S-Jrs)':
        _keybearer_hint(117),
        'Sanctuary Fortress/Dynamo Works/Keybearer Corpse (C-Rch)':
        _keybearer_hint(106),

        # Locations with hints
        'Sanctuary Fortress/Sanctuary Energy Controller/Lore Scan':
        _light_suit_location_hint(24),
        'Sanctuary Fortress/Main Gyro Chamber/Lore Scan':
        _guardian_hint(43),
        'Sanctuary Fortress/Watch Station/Lore Scan':
        _guardian_hint(79),
        'Sanctuary Fortress/Main Research/Lore Scan':
        _guardian_hint(115),

        # Dark Temple hints
        'Sanctuary Fortress/Hall of Combat Mastery/Lore Scan':
        Hint(HintType.RED_TEMPLE_KEY_SET,
             None,
             dark_temple=HintDarkTemple.AGON_WASTES),
        'Sanctuary Fortress/Sanctuary Entrance/Lore Scan':
        Hint(HintType.RED_TEMPLE_KEY_SET,
             None,
             dark_temple=HintDarkTemple.TORVUS_BOG),
        'Torvus Bog/Catacombs/Lore Scan':
        Hint(HintType.RED_TEMPLE_KEY_SET,
             None,
             dark_temple=HintDarkTemple.SANCTUARY_FORTRESS),

        # Jokes
        'Torvus Bog/Gathering Hall/Lore Scan':
        Hint(HintType.JOKE, None),
        'Torvus Bog/Training Chamber/Lore Scan':
        Hint(HintType.JOKE, None),
    }
    expected = {
        NodeIdentifier.from_string(ident_s): hint
        for ident_s, hint in expected.items()
    }

    # Run
    result = await hint_distributor.assign_pre_filler_hints(
        empty_patches,
        prefill=PreFillParams(
            rng=rng,
            configuration=layout_configuration,
            game=echoes_game_description,
            is_multiworld=is_multiworld,
        ),
    )

    # Assert
    rng.shuffle.assert_has_calls([call(ANY), call(ANY)])
    assert result.hints == expected
Esempio n. 3
0
    def apply_previous_state(self, previous_state: Optional[dict]) -> bool:
        if previous_state is None:
            return False

        starting_location = None
        needs_starting_location = len(
            self.game_configuration.starting_location.locations) > 1
        configurable_nodes = {}

        try:
            pickup_name_to_pickup = {
                pickup.name: pickup
                for pickup in self._collected_pickups.keys()
            }
            quantity_to_change = {
                pickup_name_to_pickup[pickup_name]: quantity
                for pickup_name, quantity in
                previous_state["collected_pickups"].items()
            }
            previous_actions = [
                self.game_description.world_list.node_by_identifier(
                    NodeIdentifier.from_string(identifier))
                for identifier in previous_state["actions"]
            ]
            if needs_starting_location:
                starting_location = AreaIdentifier.from_json(
                    previous_state["starting_location"])

            teleporters: Dict[NodeIdentifier, Optional[AreaIdentifier]] = {
                NodeIdentifier.from_json(item["teleporter"]):
                (AreaIdentifier.from_json(item["data"])
                 if item["data"] is not None else None)
                for item in previous_state["elevators"]
            }
            if self.game_configuration.game == RandovaniaGame.METROID_PRIME_ECHOES:
                configurable_nodes = {
                    NodeIdentifier.from_string(identifier):
                    (LayoutTranslatorRequirement(item)
                     if item is not None else None)
                    for identifier, item in
                    previous_state["configurable_nodes"].items()
                }
        except (KeyError, AttributeError):
            return False

        self.setup_starting_location(starting_location)

        for teleporter, area_location in teleporters.items():
            combo = self._elevator_id_to_combo[teleporter]
            if area_location is None:
                combo.setCurrentIndex(0)
                continue
            for i in range(combo.count()):
                if area_location == combo.itemData(i):
                    combo.setCurrentIndex(i)
                    break

        for identifier, requirement in configurable_nodes.items():
            combo = self._translator_gate_to_combo[identifier]
            for i in range(combo.count()):
                if requirement == combo.itemData(i):
                    combo.setCurrentIndex(i)
                    break

        self.bulk_change_quantity(quantity_to_change)
        self._add_new_actions(previous_actions)

        world_list = self.game_description.world_list
        state = self.state_for_current_configuration()
        self.focus_on_world(world_list.nodes_to_world(state.node))
        self.focus_on_area(world_list.nodes_to_area(state.node))

        return True
Esempio n. 4
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 = {
        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: ElevatorConnection = {
        NodeIdentifier.from_string(source_name):
        AreaIdentifier.from_string(target_name)
        for source_name, target_name in
        game_modifications["teleporters"].items()
    }

    # Dock Weakness
    dock_weakness: dict[NodeIdentifier, DockWeakness] = {
        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)

    return GamePatches(
        player_index=player_index,
        configuration=configuration,
        pickup_assignment=pickup_assignment,  # PickupAssignment
        elevator_connection=elevator_connection,  # ElevatorConnection
        dock_connection={},  # Dict[Tuple[int, int], DockConnection]
        dock_weakness=dock_weakness,
        configurable_nodes=configurable_nodes,
        starting_items=starting_items,  # ResourceGainTuple
        starting_location=starting_location,  # AreaIdentifier
        hints=hints,
    )
Esempio n. 5
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: NodeIdentifier = request.param.get("elevator")
        patches = patches.assign_elevators([
            (game.world_list.get_teleporter_node(teleporter),
             game.starting_location),
        ])
        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.simple(db.get_item(translator))
            gates.append((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