def test_calculate_dangerous_resources(danger_a, danger_b, expected_result):
    set_a: Requirement = MagicMock()
    set_b: Requirement = MagicMock()

    set_a.as_set.return_value.dangerous_resources = danger_a
    set_b.as_set.return_value.dangerous_resources = danger_b

    n1 = MagicMock()
    n1.node_index = 0
    n2 = MagicMock()
    n2.node_index = 1
    n3 = MagicMock()
    n3.node_index = 2
    n4 = MagicMock()
    n4.node_index = 3

    area_a = Area("area_a", 0, True, [n1, n2], {n1: {n2: set_a}, n2: {}}, {})
    area_b = Area("area_b", 0, True, [n3, n4], {n3: {}, n4: {n3: set_b}}, {})
    world = World("W", [area_a, area_b], {})
    wl = WorldList([world])

    # Run
    result = game_description._calculate_dangerous_resources_in_areas(wl, None)

    # Assert
    assert set(result) == set(expected_result)
Beispiel #2
0
 def add_node(self, area: Area, node: Node):
     if area.node_with_name(node.name) is not None:
         raise ValueError(f"A node named {node.name} already exists.")
     area.nodes.append(node)
     area.connections[node] = {}
     area.clear_dock_cache()
     self.game.world_list.invalidate_node_cache()
Beispiel #3
0
    def remove_node(self, area: Area, node: Node):
        area.nodes.remove(node)
        area.connections.pop(node, None)
        for connection in area.connections.values():
            connection.pop(node, None)
        area.clear_dock_cache()

        self.game.world_list.invalidate_node_cache()
Beispiel #4
0
def test_connections_from_dock_blast_shield(empty_patches):
    # Setup
    trivial = Requirement.trivial()
    req_1 = ResourceRequirement.simple(
        SimpleResourceInfo(0, "Ev1", "Ev1", ResourceType.EVENT))
    req_2 = ResourceRequirement.simple(
        SimpleResourceInfo(1, "Ev2", "Ev2", ResourceType.EVENT))
    dock_type = DockType("Type", "Type", frozendict())
    weak_1 = DockWeakness(0, "Weak 1", frozendict(), req_1, None)
    weak_2 = DockWeakness(1, "Weak 2", frozendict(), trivial,
                          DockLock(DockLockType.FRONT_BLAST_BACK_BLAST, req_2))

    node_1_identifier = NodeIdentifier.create("W", "Area 1", "Node 1")
    node_2_identifier = NodeIdentifier.create("W", "Area 2", "Node 2")

    node_1 = DockNode(node_1_identifier, 0, False, None, "", ("default", ), {},
                      dock_type, node_2_identifier, weak_1, None, None)
    node_1_lock = DockLockNode.create_from_dock(node_1, 1)
    node_2 = DockNode(node_2_identifier, 2, False, None, "", ("default", ), {},
                      dock_type, node_1_identifier, weak_2, None, None)
    node_2_lock = DockLockNode.create_from_dock(node_2, 3)

    area_1 = Area("Area 1", None, True, [node_1, node_1_lock], {}, {})
    area_2 = Area("Area 2", None, True, [node_2, node_2_lock], {}, {})

    world = World("W", [area_1, area_2], {})
    world_list = WorldList([world])
    world_list.ensure_has_node_cache()

    game_mock = MagicMock()
    game_mock.world_list = world_list
    patches = dataclasses.replace(empty_patches, game=game_mock)

    context = NodeContext(
        patches=patches,
        current_resources=ResourceCollection(),
        database=patches.game.resource_database,
        node_provider=world_list,
    )

    # Run
    result_1 = list(node_1.connections_from(context))
    result_2 = list(node_2.connections_from(context))

    # Assert
    simple = ResourceRequirement.simple

    assert result_1 == [
        (node_2,
         RequirementAnd(
             [req_1,
              simple(NodeResourceInfo.from_node(node_2, context))])),
        (node_1_lock, RequirementAnd([trivial, req_2])),
    ]
    assert result_2 == [
        (node_1, simple(NodeResourceInfo.from_node(node_2, context))),
        (node_2_lock, req_2),
    ]
Beispiel #5
0
    def edit_connections(self, area: Area, from_node: Node, target_node: Node, requirement: Optional[Requirement]):
        current_connections = area.connections[from_node]
        area.connections[from_node][target_node] = requirement
        if area.connections[from_node][target_node] is None:
            del area.connections[from_node][target_node]

        area.connections[from_node] = {
            node: current_connections[node]
            for node in area.nodes
            if node in current_connections
        }
Beispiel #6
0
def test_connections_from_dock_blast_shield(empty_patches):
    # Setup
    trivial = Requirement.trivial()
    req_1 = ResourceRequirement(
        SimpleResourceInfo("Ev1", "Ev1", ResourceType.EVENT), 1, False)
    req_2 = ResourceRequirement(
        SimpleResourceInfo("Ev2", "Ev2", ResourceType.EVENT), 1, False)
    dock_type = DockType("Type", "Type", frozendict())
    weak_1 = DockWeakness("Weak 1", frozendict(), req_1, None)
    weak_2 = DockWeakness("Weak 2", frozendict(), trivial,
                          DockLock(DockLockType.FRONT_BLAST_BACK_BLAST, req_2))

    node_1_identifier = NodeIdentifier.create("W", "Area 1", "Node 1")
    node_2_identifier = NodeIdentifier.create("W", "Area 2", "Node 2")

    node_1 = DockNode(node_1_identifier, False, None, "", ("default", ), {},
                      dock_type, node_2_identifier, weak_1, None, None)
    node_1_lock = DockLockNode.create_from_dock(node_1)
    node_2 = DockNode(node_2_identifier, False, None, "", ("default", ), {},
                      dock_type, node_1_identifier, weak_2, None, None)
    node_2_lock = DockLockNode.create_from_dock(node_2)

    area_1 = Area("Area 1", None, True, [node_1, node_1_lock], {}, {})
    area_2 = Area("Area 2", None, True, [node_2, node_2_lock], {}, {})

    world = World("W", [area_1, area_2], {})
    world_list = WorldList([world])

    context = NodeContext(
        patches=empty_patches,
        current_resources={},
        database=None,
        node_provider=world_list,
    )

    # Run
    result_1 = list(node_1.connections_from(context))
    result_2 = list(node_2.connections_from(context))

    # Assert
    assert result_1 == [
        (node_2,
         RequirementAnd([req_1,
                         ResourceRequirement.simple(node_2_identifier)])),
        (node_1_lock, RequirementAnd([trivial, req_2])),
    ]
    assert result_2 == [
        (node_1, ResourceRequirement.simple(node_2_identifier)),
        (node_2_lock, req_2),
    ]
Beispiel #7
0
    def read_area(self, area_name: str, data: dict) -> Area:
        self.current_area_name = area_name
        nodes = [
            self.read_node(node_name, item)
            for node_name, item in data["nodes"].items()
        ]
        nodes_by_name = {node.name: node for node in nodes}

        connections = {}
        for origin in nodes:
            origin_data = data["nodes"][origin.name]
            try:
                connections[origin] = {}
            except TypeError as e:
                print(origin.extra)
                raise KeyError(f"Area {area_name}, node {origin}: {e}")

            for target_name, target_requirement in origin_data[
                    "connections"].items():
                try:
                    the_set = read_requirement(target_requirement,
                                               self.resource_database)
                    connections[origin][nodes_by_name[target_name]] = the_set
                except (MissingResource, KeyError) as e:
                    raise type(
                        e
                    )(f"In area {area_name}, connection from {origin.name} to {target_name} got error: {e}"
                      )
        try:
            return Area(area_name, data["default_node"],
                        data["valid_starting_location"], nodes, connections,
                        data["extra"])
        except KeyError as e:
            raise KeyError(f"Missing key `{e}` for area `{area_name}`")
Beispiel #8
0
def find_area_errors(game: GameDescription, area: Area) -> Iterator[str]:
    nodes_with_paths_in = set()
    for node in area.nodes:
        nodes_with_paths_in.update(area.connections[node].keys())

        for error in find_node_errors(game, node):
            yield f"{area.name} - {error}"

        if node in area.connections.get(node, {}):
            yield f"{area.name} - Node '{node.name}' has a connection to itself"

    if area.default_node is not None and area.node_with_name(area.default_node) is None:
        yield f"{area.name} has default node {area.default_node}, but no node with that name exists"

    # elif area.default_node is not None:
    #     nodes_with_paths_in.add(area.node_with_name(area.default_node))

    for node in area.nodes:
        if isinstance(node, (TeleporterNode, DockNode)) or area.connections[node]:
            continue

        # FIXME: cannot implement this for PickupNodes because their resource gain depends on GamePatches
        if isinstance(node, EventNode):
            # if this node would satisfy the victory condition, it does not need outgoing connections
            current = ResourceCollection.from_resource_gain(game.resource_database, node.resource_gain_on_collect(None))
            if game.victory_condition.satisfied(current, 0, game.resource_database):
                continue

        if node in nodes_with_paths_in:
            yield f"{area.name} - '{node.name}': Node has paths in, but no connections out."
Beispiel #9
0
    def replace_node(self, area: Area, old_node: Node, new_node: Node):
        def sub(n: Node):
            return new_node if n == old_node else n

        if old_node not in area.nodes:
            raise ValueError("Given {} does does not belong to {}{}".format(
                old_node.name, area.name,
                ", but the area contains a node with that name."
                if area.node_with_name(old_node.name) is not None else "."
            ))

        if old_node.name != new_node.name and area.node_with_name(new_node.name) is not None:
            raise ValueError(f"A node named {new_node.name} already exists.")

        if isinstance(old_node, DockNode):
            self.remove_node(area, old_node.lock_node)

        old_identifier = old_node.identifier
        self.replace_references_to_node_identifier(
            old_identifier,
            old_identifier.renamed(new_node.name),
        )

        area.nodes[area.nodes.index(old_node)] = new_node

        new_connections = {
            sub(source_node): {
                sub(target_node): requirements
                for target_node, requirements in connection.items()
            }
            for source_node, connection in area.connections.items()
        }
        area.connections.clear()
        area.connections.update(new_connections)
        if area.default_node == old_node.name:
            object.__setattr__(area, "default_node", new_node.name)
        area.clear_dock_cache()

        if isinstance(new_node, DockNode):
            self.add_node(area, DockLockNode.create_from_dock(new_node, self.new_node_index()))

        self.game.world_list.invalidate_node_cache()
Beispiel #10
0
    def read_area(self, area_name: str, data: dict) -> Area:
        self.current_area_name = area_name
        nodes = [
            self.read_node(node_name, item)
            for node_name, item in data["nodes"].items()
        ]
        nodes_by_name = {node.name: node for node in nodes}

        connections = {}
        for origin in nodes:
            origin_data = data["nodes"][origin.name]
            try:
                connections[origin] = {}
            except TypeError as e:
                print(origin.extra)
                raise KeyError(f"Area {area_name}, node {origin}: {e}")

            for target_name, target_requirement in origin_data[
                    "connections"].items():
                try:
                    the_set = read_requirement(target_requirement,
                                               self.resource_database)
                    connections[origin][nodes_by_name[target_name]] = the_set
                except (MissingResource, KeyError) as e:
                    raise type(
                        e
                    )(f"In area {area_name}, connection from {origin.name} to {target_name} got error: {e}"
                      )

        for node in list(nodes):
            if isinstance(node, DockNode):
                lock_node = DockLockNode.create_from_dock(
                    node, self.next_node_index)
                nodes.append(lock_node)
                connections[lock_node] = {}
                self.next_node_index += 1

        for combo in event_pickup.find_nodes_to_combine(nodes, connections):
            combo_node = event_pickup.EventPickupNode.create_from(
                self.next_node_index, *combo)
            nodes.append(combo_node)
            for existing_connections in connections.values():
                if combo[0] in existing_connections:
                    existing_connections[combo_node] = copy.copy(
                        existing_connections[combo[0]])
            connections[combo_node] = copy.copy(connections[combo[1]])
            self.next_node_index += 1

        try:
            return Area(area_name, data["default_node"],
                        data["valid_starting_location"], nodes, connections,
                        data["extra"])
        except KeyError as e:
            raise KeyError(f"Missing key `{e}` for area `{area_name}`")
Beispiel #11
0
def _create_world_list(asset_id: int, pickup_index: PickupIndex):
    nc = NodeIdentifier.create

    logbook_node = LogbookNode(nc("World", "Area",
                                  "Logbook A"), True, None, "", ("default", ),
                               {}, asset_id, None, None, None, None)
    pickup_node = PickupNode(nc("World", "Area", "Pickup Node"), True, None,
                             "", ("default", ), {}, pickup_index, True)

    world_list = WorldList([
        World("World", [
            Area("Area", 0, True, [logbook_node, pickup_node], {}, {}),
            Area("Other Area", 0, True, [
                PickupNode(nc("World", "Other Area", f"Pickup {i}"), True,
                           None, "", ("default", ), {}, PickupIndex(i), True)
                for i in range(pickup_index.index)
            ], {}, {}),
        ], {}),
    ])

    return logbook_node, pickup_node, world_list
Beispiel #12
0
def test_basic_search_with_translator_gate(has_translator: bool, echoes_resource_database, echoes_game_patches):
    # Setup
    scan_visor = echoes_resource_database.get_item("DarkVisor")
    nc = functools.partial(NodeIdentifier.create, "Test World", "Test Area A")

    node_a = GenericNode(nc("Node A"), True, None, "", ("default",), {})
    node_b = GenericNode(nc("Node B"), True, None, "", ("default",), {})
    node_c = GenericNode(nc("Node C"), True, None, "", ("default",), {})
    translator_node = ConfigurableNode(translator_identif := nc("Translator Gate"),
                                       True, None, "", ("default",), {})

    world_list = WorldList([
        World("Test World", [
            Area("Test Area A", None, True, [node_a, node_b, node_c, translator_node],
                 {
                     node_a: {
                         node_b: Requirement.trivial(),
                         translator_node: Requirement.trivial(),
                     },
                     node_b: {
                         node_a: Requirement.trivial(),
                     },
                     node_c: {
                         translator_node: Requirement.trivial(),
                     },
                     translator_node: {
                         node_a: Requirement.trivial(),
                         node_c: Requirement.trivial(),
                     },
                 },
                 {}
                 )
        ], {})
    ])
    game = GameDescription(RandovaniaGame.METROID_PRIME_ECHOES, DockWeaknessDatabase([], {}, (None, None)),
                           echoes_resource_database, ("default",), Requirement.impossible(),
                           None, {}, None, world_list)

    patches = echoes_game_patches.assign_node_configuration({
        translator_identif: ResourceRequirement(scan_visor, 1, False)
    })
    initial_state = State({scan_visor: 1 if has_translator else 0}, (), 99,
                          node_a, patches, None, StateGameData(echoes_resource_database, game.world_list, 100, 99))

    # Run
    reach = reach_lib.reach_with_all_safe_resources(game, initial_state)

    # Assert
    if has_translator:
        assert set(reach.safe_nodes) == {node_a, node_b, translator_node, node_c}
    else:
        assert set(reach.safe_nodes) == {node_a, node_b}
Beispiel #13
0
    def move_node_from_area_to_area(self, old_area: Area, new_area: Area, node: Node):
        assert node in old_area.nodes

        if new_area.node_with_name(node.name) is not None:
            raise ValueError(f"New area {new_area.name} already contains a node named {node.name}")

        old_world = self.game.world_list.world_with_area(old_area)
        new_world = self.game.world_list.world_with_area(new_area)

        self.remove_node(old_area, node)
        self.add_node(new_area, node)
        self.replace_references_to_node_identifier(
            NodeIdentifier.create(old_world.name, old_area.name, node.name),
            NodeIdentifier.create(new_world.name, new_area.name, node.name),
        )
Beispiel #14
0
def test_calculate_dangerous_resources(danger_a, danger_b, expected_result):
    set_a: Requirement = MagicMock()
    set_b: Requirement = MagicMock()

    set_a.as_set.return_value.dangerous_resources = danger_a
    set_b.as_set.return_value.dangerous_resources = danger_b

    n1: Node = "n1"
    n2: Node = "n2"

    area_a = Area(
        "area_a", 0, True, [n1, n2],
        {
            n1: {
                n2: set_a
            },
            n2: {}
        },
        {}
    )
    area_b = Area(
        "area_b", 0, True, [n1, n2],
        {
            n1: {},
            n2: {
                n1: set_b
            }
        },
        {}
    )

    # Run
    result = game_description._calculate_dangerous_resources_in_areas([area_a, area_b], None)

    # Assert
    assert set(result) == set(expected_result)
Beispiel #15
0
    def _create_new_dock(self, location: NodeLocation, target_area: Area):
        current_area = self.current_area
        target_identifier = self.world_list.identifier_for_area(target_area)
        source_identifier = self.world_list.identifier_for_area(current_area)

        dock_weakness = self.game_description.dock_weakness_database.default_weakness
        source_name_base = integrity_check.base_dock_name_raw(
            dock_weakness[0], dock_weakness[1], target_identifier)
        target_name_base = integrity_check.base_dock_name_raw(
            dock_weakness[0], dock_weakness[1], source_identifier)

        source_count = len(
            integrity_check.docks_with_same_base_name(current_area,
                                                      source_name_base))
        if source_count != len(
                integrity_check.docks_with_same_base_name(
                    target_area, target_name_base)):
            raise ValueError(
                f"Expected {target_area.name} to also have {source_count} "
                f"docks with name {target_name_base}")

        if source_count > 0:
            source_name = f"{source_name_base} ({source_count + 1})"
            target_name = f"{target_name_base} ({source_count + 1})"
        else:
            source_name = source_name_base
            target_name = target_name_base

        new_node_this_area_identifier = NodeIdentifier(
            self.world_list.identifier_for_area(current_area), source_name)
        new_node_other_area_identifier = NodeIdentifier(
            self.world_list.identifier_for_area(target_area), target_name)

        new_node_this_area = DockNode(
            identifier=new_node_this_area_identifier,
            node_index=self.editor.new_node_index(),
            heal=False,
            location=location,
            description="",
            layers=("default", ),
            extra={},
            dock_type=dock_weakness[0],
            default_connection=new_node_other_area_identifier,
            default_dock_weakness=dock_weakness[1],
            override_default_open_requirement=None,
            override_default_lock_requirement=None,
        )

        new_node_other_area = DockNode(
            identifier=new_node_other_area_identifier,
            node_index=self.editor.new_node_index(),
            heal=False,
            location=location,
            description="",
            layers=("default", ),
            extra={},
            dock_type=dock_weakness[0],
            default_connection=new_node_this_area_identifier,
            default_dock_weakness=dock_weakness[1],
            override_default_open_requirement=None,
            override_default_lock_requirement=None,
        )

        self.editor.add_node(current_area, new_node_this_area)
        self.editor.add_node(target_area, new_node_other_area)
        if source_count == 1:
            self.editor.rename_node(
                current_area,
                current_area.node_with_name(source_name_base),
                f"{source_name_base} (1)",
            )
            self.editor.rename_node(
                target_area,
                target_area.node_with_name(target_name_base),
                f"{target_name_base} (1)",
            )
        self.on_select_area(new_node_this_area)
Beispiel #16
0
            for node in area.nodes:
                if set(node.layers).isdisjoint(active_layers):
                    nodes.remove(node)
                    connections.pop(node, None)
                    for connection in connections.values():
                        connection.pop(node, None)

                    if area.default_node == node.name:
                        has_default_node = False

            areas.append(
                Area(
                    name=area.name,
                    default_node=area.default_node
                    if has_default_node else None,
                    valid_starting_location=area.valid_starting_location,
                    nodes=nodes,
                    connections=connections,
                    extra=area.extra,
                ))

        worlds.append(dataclasses.replace(world, areas=areas))

    return GameDescription(
        game=game.game,
        resource_database=game.resource_database,
        layers=game.layers,
        dock_weakness_database=game.dock_weakness_database,
        world_list=WorldList(worlds),
        victory_condition=game.victory_condition,
        starting_location=game.starting_location,