def patch_requirements(self, static_resources: ResourceCollection,
                        damage_multiplier: float,
                        database: ResourceDatabase) -> Requirement:
     if static_resources.is_resource_set(self.resource):
         if self.satisfied(static_resources, 0, database):
             return Requirement.trivial()
         else:
             return Requirement.impossible()
     else:
         return self.multiply_amount(damage_multiplier)
Beispiel #2
0
    def simplify(self, keep_comments: bool = False) -> Requirement:
        new_items = expand_items(self.items, RequirementAnd,
                                 Requirement.trivial(), keep_comments)
        if Requirement.impossible() in new_items and mergeable_array(
                self, keep_comments):
            return Requirement.impossible()

        if len(new_items) == 1 and mergeable_array(self, keep_comments):
            return new_items[0]

        return RequirementAnd(new_items, comment=self.comment)
    def simplify(self, keep_comments: bool = False) -> Requirement:
        new_items = expand_items(self.items, RequirementOr,
                                 Requirement.impossible(), keep_comments)
        if Requirement.trivial() in new_items and mergeable_array(
                self, keep_comments):
            return Requirement.trivial()

        num_and_requirements = 0
        common_requirements = None
        for item in new_items:
            if isinstance(item, RequirementAnd) and mergeable_array(
                    item, keep_comments):
                num_and_requirements += 1
                if common_requirements is None:
                    common_requirements = item.items
                else:
                    common_requirements = [
                        common for common in common_requirements
                        if common in item.items
                    ]

        # Only extract the common requirements if there's more than 1 requirement
        if num_and_requirements >= 2 and common_requirements:
            simplified_items = []
            common_new_or = []

            for item in new_items:
                if isinstance(item, RequirementAnd) and mergeable_array(
                        item, keep_comments):
                    assert set(common_requirements) <= set(item.items)
                    simplified_condition = [
                        it for it in item.items
                        if it not in common_requirements
                    ]
                    if simplified_condition:
                        common_new_or.append(
                            RequirementAnd(simplified_condition
                                           ) if len(simplified_condition) > 1
                            else simplified_condition[0])
                else:
                    simplified_items.append(item)

            common_requirements.append(RequirementOr(common_new_or))
            simplified_items.append(RequirementAnd(common_requirements))
            final_items = simplified_items

        else:
            final_items = new_items

        if len(final_items) == 1 and mergeable_array(self, keep_comments):
            return final_items[0]

        return RequirementOr(final_items, comment=self.comment)
def test_requirement_as_set_4(database):
    def _req(name: str):
        id_req = ResourceRequirement.simple(database.get_item(name))
        return id_req

    req = RequirementOr([
        Requirement.impossible(),
        _req("A"),
        Requirement.trivial(),
    ])
    assert req.as_set(database) == RequirementSet([
        RequirementList([]),
    ])
Beispiel #5
0
def test_edit_connections(game_editor):
    # Setup
    landing_site = game_editor.game.world_list.area_by_area_location(
        AreaIdentifier("Temple Grounds", "Landing Site"))
    source = landing_site.node_with_name("Save Station")
    target = landing_site.node_with_name("Door to Service Access")
    assert landing_site.connections[source][target] != Requirement.trivial()

    # Run
    game_editor.edit_connections(landing_site, source, target,
                                 Requirement.trivial())

    # Assert
    assert landing_site.connections[source][target] == Requirement.trivial()
Beispiel #6
0
    def update_connections(self):
        current_node = self.current_node
        current_connection_node = self.current_connection_node

        assert current_node != current_connection_node or current_node is None

        if self._connections_visualizer is not None:
            self._connections_visualizer.deleteLater()
            self._connections_visualizer = None

        if current_connection_node is None or current_node is None:
            assert len(self.current_area.nodes) <= 1 or not self.edit_mode
            return

        requirement = self.current_area.connections[current_node].get(
            self.current_connection_node, Requirement.impossible())
        if self._collection_for_filtering is not None:
            requirement = requirement.patch_requirements(
                self._collection_for_filtering,
                1.0,
                self.game_description.resource_database,
            )

        self._connections_visualizer = ConnectionsVisualizer(
            self.other_node_alternatives_contents,
            self.alternatives_grid_layout, self.resource_database, requirement,
            False)
Beispiel #7
0
    def configurable_node_assignment(
            self, configuration: DreadConfiguration, game: GameDescription,
            rng: Random) -> Iterator[NodeConfigurationAssociation]:
        result = []

        rsb = game.resource_database

        requirement_for_type = {
            "POWERBEAM": rsb.requirement_template["Shoot Beam"],
            "BOMB": rsb.requirement_template["Lay Bomb"],
            "MISSILE": rsb.requirement_template["Shoot Missile"],
            "SUPERMISSILE": rsb.requirement_template["Shoot Super Missile"],
            "POWERBOMB": rsb.requirement_template["Lay Power Bomb"],
            "SCREWATTACK": ResourceRequirement.simple(rsb.get_item("Screw")),
            "WEIGHT": Requirement.impossible(),
            "SPEEDBOOST": ResourceRequirement.simple(rsb.get_item("Speed")),
        }

        for node in game.world_list.iterate_nodes():
            if not isinstance(node, ConfigurableNode):
                continue

            result.append((game.world_list.identifier_for_node(node),
                           RequirementAnd([
                               requirement_for_type[block_type]
                               for block_type in node.extra["tile_types"]
                           ]).simplify()))

        return result
Beispiel #8
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 #9
0
def find_invalid_strongly_connected_components(game: GameDescription) -> Iterator[str]:
    import networkx
    graph = networkx.DiGraph()

    for node in game.world_list.iterate_nodes():
        if isinstance(node, DockLockNode):
            continue
        graph.add_node(node)

    context = NodeContext(
        patches=GamePatches.create_from_game(game, 0, None),
        current_resources=ResourceCollection.with_database(game.resource_database),
        database=game.resource_database,
        node_provider=game.world_list,
    )

    for node in game.world_list.iterate_nodes():
        if node not in graph:
            continue

        for other, req in game.world_list.potential_nodes_from(node, context):
            if other not in graph:
                continue

            if req != Requirement.impossible():
                graph.add_edge(node, other)

    starting_node = game.world_list.resolve_teleporter_connection(game.starting_location)

    for strong_comp in networkx.strongly_connected_components(graph):
        components: set[Node] = strong_comp

        # The starting location determines the default component
        if starting_node in components:
            continue

        if any(node.extra.get("different_strongly_connected_component", False) for node in components):
            continue

        if len(components) == 1:
            node = next(iter(components))

            # If the component is a single node which is the default node of it's area, allow it
            area = game.world_list.nodes_to_area(node)
            if area.default_node == node.name:
                continue

            # We accept nodes that have no paths out or in.
            if not graph.in_edges(node) and not graph.edges(node):
                continue

        names = sorted(
            game.world_list.node_name(node, with_world=True)
            for node in strong_comp
        )
        yield "Unknown strongly connected component detected containing {} nodes:\n{}".format(len(names), names)
Beispiel #10
0
def pretty_print_requirement(requirement: Requirement,
                             level: int = 0) -> Iterator[Tuple[int, str]]:
    if requirement == Requirement.impossible():
        yield level, "Impossible"

    elif requirement == Requirement.trivial():
        yield level, "Trivial"

    elif isinstance(requirement, RequirementArrayBase):
        yield from pretty_print_requirement_array(requirement, level)

    elif isinstance(requirement, ResourceRequirement):
        yield level, pretty_print_resource_requirement(requirement)

    elif isinstance(requirement, RequirementTemplate):
        yield level, requirement.template_name
    else:
        raise RuntimeError(
            f"Unknown requirement type: {type(requirement)} - {requirement}")
Beispiel #11
0
    def connections_from(
            self,
            context: NodeContext) -> typing.Iterator[tuple[Node, Requirement]]:
        target_area_identifier = context.patches.get_elevator_connection_for(
            self)
        if target_area_identifier is None:
            return

        yield context.node_provider.default_node_for_area(
            target_area_identifier), Requirement.trivial()
Beispiel #12
0
    def create_new_template(self):
        template_name, did_confirm = QtWidgets.QInputDialog.getText(
            self, "New Template", "Insert template name:")
        if not did_confirm or template_name == "":
            return

        self.db.requirement_template[template_name] = Requirement.trivial()
        self.create_template_editor(template_name)
        self.tab_template_layout.removeWidget(self.create_new_template_button)
        self.tab_template_layout.addWidget(self.create_new_template_button)
Beispiel #13
0
    def replace_connection_with(self, target_node: Node,
                                requirement: Requirement):
        current_node = self.current_node

        if requirement == Requirement.impossible():
            requirement = None

        self.editor.edit_connections(self.current_area, current_node,
                                     target_node, requirement)
        self.update_connections()
        self.area_view_canvas.update()
Beispiel #14
0
    async def _open_edit_connection(self):
        if self._check_for_edit_dialog():
            return

        from_node = self.current_node
        target_node = self.current_connection_node
        assert from_node is not None
        assert target_node is not None

        requirement = self.current_area.connections[from_node].get(
            target_node, Requirement.impossible())
        editor = ConnectionsEditor(self, self.resource_database, requirement)
        if await self._execute_edit_dialog(editor):
            self.editor.edit_connections(self.current_area, from_node,
                                         target_node, editor.final_requirement)
            self.update_connections()
            self.area_view_canvas.update()
Beispiel #15
0
    def satisfiable_actions(self,
                            state: State,
                            victory_condition: Requirement,
                            ) -> Iterator[Tuple[ResourceNode, int]]:

        interesting_resources = calculate_interesting_resources(
            self._satisfiable_requirements.union(victory_condition.as_set(state.resource_database).alternatives),
            state.resources,
            state.energy,
            state.resource_database)

        # print(" > satisfiable actions, with {} interesting resources".format(len(interesting_resources)))
        for action, energy in self.possible_actions(state):
            for resource, amount in action.resource_gain_on_collect(state.node_context()):
                if resource in interesting_resources:
                    yield action, energy
                    break
Beispiel #16
0
    def _lock_connection(self, context: NodeContext) -> typing.Optional[tuple[Node, Requirement]]:
        requirement = Requirement.trivial()

        forward_weakness = self.get_front_weakness(context)
        forward_lock = forward_weakness.lock

        if forward_lock is not None:
            requirement = self._get_lock_requirement(context, forward_weakness)

        back_weakness = self.get_back_weakness(context)
        back_lock = None
        if back_weakness is not None:
            back_lock = back_weakness.lock

            if back_lock is not None and back_lock.lock_type == DockLockType.FRONT_BLAST_BACK_BLAST:
                requirement = RequirementAnd([requirement, back_lock.requirement])

        if forward_lock is None and back_lock is None:
            return None

        return self.lock_node, requirement
Beispiel #17
0
    def _potential_nodes_from(
            self, node: Node) -> Iterator[Tuple[Node, RequirementSet]]:
        extra_requirement = _extra_requirement_for_node(
            self._game, self.node_context(), node)
        requirement_to_leave = node.requirement_to_leave(
            self._state.node_context())

        for target_node, requirement in self._game.world_list.potential_nodes_from(
                node, self.node_context()):
            if target_node is None:
                continue

            if requirement_to_leave != Requirement.trivial():
                requirement = RequirementAnd(
                    [requirement, requirement_to_leave])

            if extra_requirement is not None:
                requirement = RequirementAnd([requirement, extra_requirement])

            yield target_node, requirement.as_set(
                self._state.resource_database)
Beispiel #18
0
    def state_for_current_configuration(self) -> Optional[State]:
        state = self._initial_state.copy()
        if self._actions:
            state.node = self._actions[-1]

        state.patches = state.patches.assign_elevators(
            (state.world_list.get_teleporter_node(teleporter),
             combo.currentData())
            for teleporter, combo in self._elevator_id_to_combo.items())

        for gate, item in self._translator_gate_to_combo.items():
            scan_visor = self.game_description.resource_database.get_item(
                "Scan")

            requirement: Optional[
                LayoutTranslatorRequirement] = item.currentData()
            if requirement is None:
                translator_req = Requirement.impossible()
            else:
                translator = self.game_description.resource_database.get_item(
                    requirement.item_name)
                translator_req = ResourceRequirement.simple(translator)

            state.patches.configurable_nodes[gate] = RequirementAnd([
                ResourceRequirement.simple(scan_visor),
                translator_req,
            ])

        for pickup, quantity in self._collected_pickups.items():
            for _ in range(quantity):
                add_pickup_to_state(state, pickup)

        for node in self._collected_nodes:
            state.resources.add_resource_gain(
                node.resource_gain_on_collect(state.node_context()))

        return state
Beispiel #19
0
def base_dock_name_raw(dock_type: DockType, weakness: DockWeakness, connection: AreaIdentifier) -> str:
    expected_connector = "to"
    if weakness.requirement == Requirement.impossible() and weakness.name != "Not Determined":
        expected_connector = "from"
    return f"{dock_type.long_name} {expected_connector} {connection.area_name}"
def test_simplified_requirement(database):
    def _req(name: str):
        id_req = ResourceRequirement.simple(database.get_item(name))
        return id_req

    test_parameters = [
        (
            RequirementOr([
                RequirementAnd([
                    _req("A"),
                ]),
            ]),
            _req("A"),
        ), (
            RequirementAnd([
                RequirementOr([
                    _req("A"),
                ]),
            ]),
            _req("A"),
        ),
        (
            RequirementAnd([
                RequirementOr([_req("B"), Requirement.trivial()]),
                RequirementAnd([
                    Requirement.trivial(),
                    _req("A"),
                ]),
            ]),
            _req("A"),
        ),
        (
            RequirementOr([
                _req("A"),
                RequirementAnd([_req("B"), _req("C")]),
                RequirementAnd([_req("B"), _req("D")]),
            ]),
            RequirementOr([
                _req("A"),
                RequirementAnd([
                    _req("B"),
                    RequirementOr([
                        _req("C"),
                        _req("D"),
                    ]),
                ]),
            ]),
        ),
        (
            RequirementOr([
                RequirementAnd([_req("B"), _req("C")]),
                RequirementAnd([_req("B"), _req("D")]),
            ]),
            RequirementAnd([
                _req("B"),
                RequirementOr([
                    _req("C"),
                    _req("D"),
                ]),
            ]),
        ), (
            RequirementOr([
                _req("A"),
                _req("A"),
            ]),
            _req("A"),
        ), (
            RequirementAnd([
                _req("A"),
                _req("A"),
            ]),
            _req("A"),
        ),
        (
            RequirementOr([
                RequirementAnd([
                    _req("A"),
                    RequirementOr([_req("A"),
                                   RequirementOr([_req("A")])])
                ]),
                RequirementAnd([
                    _req("A"),
                    RequirementOr([
                        _req("A"),
                        RequirementOr([]),
                    ]),
                ]),
            ]),
            _req("A"),
        ),
        (
            RequirementOr([
                RequirementAnd([
                    _req("A"),
                    RequirementOr([_req("A"),
                                   RequirementOr([_req("A")])])
                ]),
                RequirementAnd([
                    _req("A"),
                    RequirementOr([
                        _req("A"),
                        RequirementOr([_req("A")]),
                    ])
                ])
            ]),
            _req("A"),
        )
    ]

    for original, expected in test_parameters:
        simplified = original.simplify()
        assert simplified == expected
        assert simplified.as_set(database) == expected.as_set(database)
Beispiel #21
0
class NodeDetailsPopup(QtWidgets.QDialog, Ui_NodeDetailsPopup):
    _unlocked_by_requirement = Requirement.trivial()
    _connections_visualizer = None

    def __init__(self, game: GameDescription, node: Node):
        super().__init__()
        self.setupUi(self)
        common_qt_lib.set_default_window_icon(self)

        self.game = game
        self.node = node

        self._type_to_tab = {
            GenericNode: self.tab_generic,
            DockNode: self.tab_dock,
            PickupNode: self.tab_pickup,
            TeleporterNode: self.tab_teleporter,
            EventNode: self.tab_event,
            ConfigurableNode: self.tab_translator_gate,
            LogbookNode: self.tab_logbook,
            PlayerShipNode: self.tab_player_ship,
        }
        tab_to_type = {tab: node_type for node_type, tab in self._type_to_tab.items()}

        # Dynamic Stuff
        for i, node_type in enumerate(self._type_to_tab.keys()):
            self.node_type_combo.setItemData(i, node_type)

        self.layers_combo.clear()
        for layer in game.layers:
            self.layers_combo.addItem(layer)

        self.dock_type_combo.clear()
        for i, dock_type in enumerate(game.dock_weakness_database.dock_types):
            self.dock_type_combo.addItem(dock_type.long_name, userData=dock_type)
        refresh_if_needed(self.dock_type_combo, self.on_dock_type_combo)

        for world in sorted(game.world_list.worlds, key=lambda x: x.name):
            self.dock_connection_world_combo.addItem(world.name, userData=world)
            self.teleporter_destination_world_combo.addItem(world.name, userData=world)
        refresh_if_needed(self.teleporter_destination_world_combo, self.on_dock_connection_world_combo)
        refresh_if_needed(self.teleporter_destination_world_combo, self.on_teleporter_destination_world_combo)

        for event in sorted(game.resource_database.event, key=lambda it: it.long_name):
            self.event_resource_combo.addItem(event.long_name, event)
        if self.event_resource_combo.count() == 0:
            self.event_resource_combo.addItem("No events in database", None)
            self.event_resource_combo.setEnabled(False)

        for i, dock_type in enumerate(enum_lib.iterate_enum(LoreType)):
            self.lore_type_combo.setItemData(i, dock_type)
        refresh_if_needed(self.lore_type_combo, self.on_lore_type_combo)

        self.set_unlocked_by(Requirement.trivial())

        # Signals
        self.button_box.accepted.connect(self.try_accept)
        self.button_box.rejected.connect(self.reject)
        self.name_edit.textEdited.connect(self.on_name_edit)
        self.node_type_combo.currentIndexChanged.connect(self.on_node_type_combo)
        self.dock_connection_world_combo.currentIndexChanged.connect(self.on_dock_connection_world_combo)
        self.dock_connection_area_combo.currentIndexChanged.connect(self.on_dock_connection_area_combo)
        self.dock_type_combo.currentIndexChanged.connect(self.on_dock_type_combo)
        self.dock_update_name_button.clicked.connect(self.on_dock_update_name_button)
        self.teleporter_destination_world_combo.currentIndexChanged.connect(self.on_teleporter_destination_world_combo)
        self.lore_type_combo.currentIndexChanged.connect(self.on_lore_type_combo)
        self.player_ship_unlocked_button.clicked.connect(self.on_player_ship_unlocked_button)

        # Hide the tab bar
        tab_bar: QtWidgets.QTabBar = self.tab_widget.findChild(QtWidgets.QTabBar)
        tab_bar.hide()

        # Values
        self.name_edit.setText(node.name)
        self.heals_check.setChecked(node.heal)
        self.location_group.setChecked(node.location is not None)
        if node.location is not None:
            self.location_x_spin.setValue(node.location.x)
            self.location_y_spin.setValue(node.location.y)
            self.location_z_spin.setValue(node.location.z)
        self.description_edit.setMarkdown(node.description)
        self.extra_edit.setPlainText(json.dumps(frozen_lib.unwrap(node.extra), indent=4))

        try:
            visible_tab = self._fill_for_type(node)
            self.node_type_combo.setCurrentIndex(self.node_type_combo.findData(tab_to_type[visible_tab]))
            refresh_if_needed(self.node_type_combo, self.on_node_type_combo)
        except Exception:
            pass

        self.on_name_edit(self.name_edit.text())

    def _fill_for_type(self, node: Node) -> QtWidgets.QWidget:
        if isinstance(node, GenericNode):
            return self.tab_generic

        elif isinstance(node, DockNode):
            self.fill_for_dock(node)
            return self.tab_dock

        elif isinstance(node, PickupNode):
            self.fill_for_pickup(node)
            return self.tab_pickup

        elif isinstance(node, TeleporterNode):
            self.fill_for_teleporter(node)
            return self.tab_teleporter

        elif isinstance(node, EventNode):
            self.fill_for_event(node)
            return self.tab_event

        elif isinstance(node, ConfigurableNode):
            self.fill_for_translator_gate(node)
            return self.tab_translator_gate

        elif isinstance(node, LogbookNode):
            self.fill_for_logbook_node(node)
            return self.tab_logbook

        elif isinstance(node, PlayerShipNode):
            self.fill_for_player_ship_node(node)
            return self.tab_player_ship

        else:
            raise ValueError(f"Unknown node type: {node}")

    def fill_for_dock(self, node: DockNode):
        # Connection
        other_node = self.game.world_list.node_by_identifier(node.default_connection)
        area = self.game.world_list.nodes_to_area(other_node)
        world = self.game.world_list.nodes_to_world(other_node)

        self.dock_connection_world_combo.setCurrentIndex(self.dock_connection_world_combo.findData(world))
        refresh_if_needed(self.dock_connection_world_combo, self.on_dock_connection_world_combo)
        self.dock_connection_area_combo.setCurrentIndex(self.dock_connection_area_combo.findData(area))
        refresh_if_needed(self.dock_connection_area_combo, self.on_dock_connection_area_combo)
        self.dock_connection_node_combo.setCurrentIndex(self.dock_connection_node_combo.findData(other_node))

        # Dock Weakness
        self.dock_type_combo.setCurrentIndex(self.dock_type_combo.findData(node.dock_type))
        refresh_if_needed(self.dock_type_combo, self.on_dock_type_combo)
        self.dock_weakness_combo.setCurrentIndex(self.dock_weakness_combo.findData(node.default_dock_weakness))

    def fill_for_pickup(self, node: PickupNode):
        self.pickup_index_spin.setValue(node.pickup_index.index)
        self.major_location_check.setChecked(node.major_location)

    def fill_for_teleporter(self, node: TeleporterNode):
        world = self.game.world_list.world_by_area_location(node.default_connection)
        try:
            area = self.game.world_list.area_by_area_location(node.default_connection)
        except KeyError:
            area = None

        self.teleporter_destination_world_combo.setCurrentIndex(self.teleporter_destination_world_combo.findData(world))
        refresh_if_needed(self.teleporter_destination_world_combo, self.on_teleporter_destination_world_combo)
        self.teleporter_destination_area_combo.setCurrentIndex(self.teleporter_destination_area_combo.findData(area))
        self.teleporter_editable_check.setChecked(node.editable)
        self.teleporter_vanilla_name_edit.setChecked(node.keep_name_when_vanilla)

    def fill_for_event(self, node: EventNode):
        self.event_resource_combo.setCurrentIndex(self.event_resource_combo.findData(node.event))

    def fill_for_translator_gate(self, node: ConfigurableNode):
        pass

    def fill_for_logbook_node(self, node: LogbookNode):
        self.logbook_string_asset_id_edit.setText(hex(node.string_asset_id).upper())
        self.lore_type_combo.setCurrentIndex(self.lore_type_combo.findData(node.lore_type))
        refresh_if_needed(self.lore_type_combo, self.on_lore_type_combo)

        if node.lore_type == LoreType.REQUIRES_ITEM:
            self.logbook_extra_combo.setCurrentIndex(self.logbook_extra_combo.findData(node.required_translator))

        elif node.lore_type == LoreType.SPECIFIC_PICKUP:
            self.logbook_extra_combo.setCurrentIndex(self.logbook_extra_combo.findData(node.hint_index))

    def fill_for_player_ship_node(self, node: PlayerShipNode):
        self.set_unlocked_by(node.is_unlocked)

    def set_unlocked_by(self, requirement: Requirement):
        if self._connections_visualizer is not None:
            self._connections_visualizer.deleteLater()
            self._connections_visualizer = None

        self._unlocked_by_requirement = requirement
        self._connections_visualizer = ConnectionsVisualizer(
            self.player_ship_unlocked_group,
            self.player_ship_unlocked_layout,
            self.game.resource_database,
            requirement,
            False
        )

    # Signals
    def on_name_edit(self, value: str):
        has_error = False

        try:
            new_node = self.create_new_node()
        except ValueError:
            new_node = None

        if isinstance(new_node, DockNode):
            area = self.game.world_list.nodes_to_area(self.node)
            has_error = not integrity_check.dock_has_correct_name(area, new_node)[0]

        common_qt_lib.set_error_border_stylesheet(self.name_edit, has_error)

    def on_node_type_combo(self, _):
        self.tab_widget.setCurrentWidget(self._type_to_tab[self.node_type_combo.currentData()])

    def on_dock_connection_world_combo(self, _):
        world: World = self.dock_connection_world_combo.currentData()

        self.dock_connection_area_combo.clear()
        for area in sorted(world.areas, key=lambda x: x.name):
            self.dock_connection_area_combo.addItem(area.name, userData=area)

    def on_dock_connection_area_combo(self, _):
        area: Optional[Area] = self.dock_connection_area_combo.currentData()

        self.dock_connection_node_combo.clear()
        empty = True
        if area is not None:
            for node in area.nodes:
                if isinstance(node, DockNode):
                    self.dock_connection_node_combo.addItem(node.name, userData=node)
                    empty = False
        if empty:
            self.dock_connection_node_combo.addItem("Other", None)

    def on_dock_type_combo(self, _):
        self.dock_weakness_combo.clear()

        for weakness in self.game.dock_weakness_database.get_by_type(self.dock_type_combo.currentData()):
            self.dock_weakness_combo.addItem(weakness.name, weakness)

    def on_dock_update_name_button(self):
        new_node = self.create_new_node()
        assert isinstance(new_node, DockNode)
        expected_name = integrity_check.base_dock_name(new_node)
        self.name_edit.setText(expected_name)
        self.on_name_edit(self.name_edit.text())

    def on_teleporter_destination_world_combo(self, _):
        world: World = self.teleporter_destination_world_combo.currentData()

        self.teleporter_destination_area_combo.clear()
        for area in sorted(world.areas, key=lambda x: x.name):
            self.teleporter_destination_area_combo.addItem(area.name, userData=area)

    def on_lore_type_combo(self, _):
        lore_type: LoreType = self.lore_type_combo.currentData()

        self.logbook_extra_combo.clear()

        if lore_type == LoreType.REQUIRES_ITEM:
            self.logbook_extra_label.setText("Translator needed:")
            for item in self.game.resource_database.item:
                self.logbook_extra_combo.addItem(item.long_name, item)

        elif lore_type == LoreType.SPECIFIC_PICKUP:
            self.logbook_extra_label.setText("Pickup index hinted:")
            for node in self.game.world_list.iterate_nodes():
                if isinstance(node, PickupNode):
                    self.logbook_extra_combo.addItem("{} - {}".format(
                        self.game.world_list.node_name(node, True, True),
                        node.pickup_index.index,
                    ), node.pickup_index.index)

        else:
            self.logbook_extra_label.setText("Nothing:")
            self.logbook_extra_combo.addItem("Nothing", None)

    @asyncSlot()
    async def on_player_ship_unlocked_button(self):
        self._edit_popup = ConnectionsEditor(self, self.game.resource_database, self._unlocked_by_requirement)
        self._edit_popup.setModal(True)
        try:
            result = await async_dialog.execute_dialog(self._edit_popup)
            if result == QtWidgets.QDialog.Accepted:
                self.set_unlocked_by(self._edit_popup.final_requirement)
        finally:
            self._edit_popup = None

    # Final
    def create_new_node(self) -> Node:
        node_type = self.node_type_combo.currentData()
        identifier = self.node.identifier.renamed(self.name_edit.text())
        node_index = self.node.node_index
        heal = self.heals_check.isChecked()
        location = None
        if self.location_group.isChecked():
            location = NodeLocation(self.location_x_spin.value(),
                                    self.location_y_spin.value(),
                                    self.location_z_spin.value())
        description = self.description_edit.toMarkdown()
        extra = json.loads(self.extra_edit.toPlainText())
        layers = (self.layers_combo.currentText(),)

        if node_type == GenericNode:
            return GenericNode(identifier, node_index, heal, location, description, layers, extra)

        elif node_type == DockNode:
            connection_node: Node = self.dock_connection_node_combo.currentData()

            return DockNode(
                identifier, node_index, heal, location, description, layers, extra,
                self.dock_type_combo.currentData(),
                self.game.world_list.identifier_for_node(connection_node),
                self.dock_weakness_combo.currentData(),
                None, None,
            )

        elif node_type == PickupNode:
            return PickupNode(
                identifier, node_index, heal, location, description, layers, extra,
                PickupIndex(self.pickup_index_spin.value()),
                self.major_location_check.isChecked(),
            )

        elif node_type == TeleporterNode:
            dest_world: World = self.teleporter_destination_world_combo.currentData()
            dest_area: Area = self.teleporter_destination_area_combo.currentData()

            return TeleporterNode(
                identifier, node_index, heal, location, description, layers, extra,
                AreaIdentifier(
                    world_name=dest_world.name,
                    area_name=dest_area.name,
                ),
                self.teleporter_vanilla_name_edit.isChecked(),
                self.teleporter_editable_check.isChecked(),
            )

        elif node_type == EventNode:
            event = self.event_resource_combo.currentData()
            if event is None:
                raise ValueError("There are no events in the database, unable to create EventNode.")
            return EventNode(
                identifier, node_index, heal, location, description, layers, extra,
                event,
            )

        elif node_type == ConfigurableNode:
            return ConfigurableNode(
                identifier, node_index, heal, location, description, layers, extra,
            )

        elif node_type == LogbookNode:
            lore_type: LoreType = self.lore_type_combo.currentData()
            if lore_type == LoreType.REQUIRES_ITEM:
                required_translator = self.logbook_extra_combo.currentData()
                if required_translator is None:
                    raise ValueError("Missing required translator.")
            else:
                required_translator = None

            if lore_type == LoreType.SPECIFIC_PICKUP:
                hint_index = self.logbook_extra_combo.currentData()
            else:
                hint_index = None

            return LogbookNode(
                identifier, node_index, heal, location, description, layers, extra,
                int(self.logbook_string_asset_id_edit.text(), 0),
                self._get_scan_visor(),
                lore_type,
                required_translator,
                hint_index
            )

        elif node_type == PlayerShipNode:
            return PlayerShipNode(
                identifier, node_index, heal, location, description, layers, extra,
                self._unlocked_by_requirement,
                self._get_command_visor()
            )

        else:
            raise RuntimeError(f"Unknown node type: {node_type}")

    def _get_scan_visor(self):
        try:
            return find_resource_info_with_long_name(
                self.game.resource_database.item,
                "Scan Visor"
            )
        except MissingResource:
            return None

    def _get_command_visor(self):
        try:
            return find_resource_info_with_long_name(
                self.game.resource_database.item,
                "Command Visor"
            )
        except MissingResource:
            return None

    def try_accept(self):
        try:
            self.create_new_node()
            self.accept()
        except Exception as e:
            logging.exception(f"Unable to save node: {e}")

            box = QtWidgets.QMessageBox(
                QtWidgets.QMessageBox.Warning,
                "Invalid configuration",
                f"Unable to save node: {e}",
                QtWidgets.QMessageBox.Ok,
                None,
            )
            box.setDefaultButton(QtWidgets.QMessageBox.Ok)
            box.setDetailedText("".join(traceback.format_tb(e.__traceback__)))
            common_qt_lib.set_default_window_icon(box)
            box.exec_()
Beispiel #22
0
    def __init__(self, game: GameDescription, node: Node):
        super().__init__()
        self.setupUi(self)
        common_qt_lib.set_default_window_icon(self)

        self.game = game
        self.node = node

        self._type_to_tab = {
            GenericNode: self.tab_generic,
            DockNode: self.tab_dock,
            PickupNode: self.tab_pickup,
            TeleporterNode: self.tab_teleporter,
            EventNode: self.tab_event,
            ConfigurableNode: self.tab_translator_gate,
            LogbookNode: self.tab_logbook,
            PlayerShipNode: self.tab_player_ship,
        }
        tab_to_type = {tab: node_type for node_type, tab in self._type_to_tab.items()}

        # Dynamic Stuff
        for i, node_type in enumerate(self._type_to_tab.keys()):
            self.node_type_combo.setItemData(i, node_type)

        self.layers_combo.clear()
        for layer in game.layers:
            self.layers_combo.addItem(layer)

        self.dock_type_combo.clear()
        for i, dock_type in enumerate(game.dock_weakness_database.dock_types):
            self.dock_type_combo.addItem(dock_type.long_name, userData=dock_type)
        refresh_if_needed(self.dock_type_combo, self.on_dock_type_combo)

        for world in sorted(game.world_list.worlds, key=lambda x: x.name):
            self.dock_connection_world_combo.addItem(world.name, userData=world)
            self.teleporter_destination_world_combo.addItem(world.name, userData=world)
        refresh_if_needed(self.teleporter_destination_world_combo, self.on_dock_connection_world_combo)
        refresh_if_needed(self.teleporter_destination_world_combo, self.on_teleporter_destination_world_combo)

        for event in sorted(game.resource_database.event, key=lambda it: it.long_name):
            self.event_resource_combo.addItem(event.long_name, event)
        if self.event_resource_combo.count() == 0:
            self.event_resource_combo.addItem("No events in database", None)
            self.event_resource_combo.setEnabled(False)

        for i, dock_type in enumerate(enum_lib.iterate_enum(LoreType)):
            self.lore_type_combo.setItemData(i, dock_type)
        refresh_if_needed(self.lore_type_combo, self.on_lore_type_combo)

        self.set_unlocked_by(Requirement.trivial())

        # Signals
        self.button_box.accepted.connect(self.try_accept)
        self.button_box.rejected.connect(self.reject)
        self.name_edit.textEdited.connect(self.on_name_edit)
        self.node_type_combo.currentIndexChanged.connect(self.on_node_type_combo)
        self.dock_connection_world_combo.currentIndexChanged.connect(self.on_dock_connection_world_combo)
        self.dock_connection_area_combo.currentIndexChanged.connect(self.on_dock_connection_area_combo)
        self.dock_type_combo.currentIndexChanged.connect(self.on_dock_type_combo)
        self.dock_update_name_button.clicked.connect(self.on_dock_update_name_button)
        self.teleporter_destination_world_combo.currentIndexChanged.connect(self.on_teleporter_destination_world_combo)
        self.lore_type_combo.currentIndexChanged.connect(self.on_lore_type_combo)
        self.player_ship_unlocked_button.clicked.connect(self.on_player_ship_unlocked_button)

        # Hide the tab bar
        tab_bar: QtWidgets.QTabBar = self.tab_widget.findChild(QtWidgets.QTabBar)
        tab_bar.hide()

        # Values
        self.name_edit.setText(node.name)
        self.heals_check.setChecked(node.heal)
        self.location_group.setChecked(node.location is not None)
        if node.location is not None:
            self.location_x_spin.setValue(node.location.x)
            self.location_y_spin.setValue(node.location.y)
            self.location_z_spin.setValue(node.location.z)
        self.description_edit.setMarkdown(node.description)
        self.extra_edit.setPlainText(json.dumps(frozen_lib.unwrap(node.extra), indent=4))

        try:
            visible_tab = self._fill_for_type(node)
            self.node_type_combo.setCurrentIndex(self.node_type_combo.findData(tab_to_type[visible_tab]))
            refresh_if_needed(self.node_type_combo, self.on_node_type_combo)
        except Exception:
            pass

        self.on_name_edit(self.name_edit.text())
Beispiel #23
0
 def connections_from(
         self, context: NodeContext) -> Iterator[tuple[Node, Requirement]]:
     dock = self.dock
     if dock._lock_connection(context) is not None:
         yield dock, Requirement.trivial()
Beispiel #24
0
    def calculate_reach(cls,
                        logic: Logic,
                        initial_state: State) -> "ResolverReach":

        all_nodes = logic.game.world_list.all_nodes
        checked_nodes: Dict[int, int] = {}
        database = initial_state.resource_database
        context = initial_state.node_context()

        # Keys: nodes to check
        # Value: how much energy was available when visiting that node
        nodes_to_check: Dict[int, int] = {
            initial_state.node.node_index: initial_state.energy
        }

        reach_nodes: Dict[int, int] = {}
        requirements_by_node: Dict[int, Set[RequirementList]] = defaultdict(set)

        path_to_node: dict[int, list[int]] = {
            initial_state.node.node_index: [],
        }

        while nodes_to_check:
            node_index = next(iter(nodes_to_check))
            node = all_nodes[node_index]
            energy = nodes_to_check.pop(node_index)

            if node.heal:
                energy = initial_state.maximum_energy

            checked_nodes[node_index] = energy
            if node_index != initial_state.node.node_index:
                reach_nodes[node_index] = energy

            requirement_to_leave = node.requirement_to_leave(context)

            for target_node, requirement in logic.game.world_list.potential_nodes_from(node, context):
                target_node_index = target_node.node_index

                if checked_nodes.get(target_node_index, math.inf) <= energy or nodes_to_check.get(target_node_index,
                                                                                            math.inf) <= energy:
                    continue

                if requirement_to_leave != Requirement.trivial():
                    requirement = RequirementAnd([requirement, requirement_to_leave])

                # Check if the normal requirements to reach that node is satisfied
                satisfied = requirement.satisfied(initial_state.resources, energy, database)
                if satisfied:
                    # If it is, check if we additional requirements figured out by backtracking is satisfied
                    satisfied = logic.get_additional_requirements(node).satisfied(initial_state.resources,
                                                                                  energy,
                                                                                  initial_state.resource_database)

                if satisfied:
                    nodes_to_check[target_node_index] = energy - requirement.damage(initial_state.resources, database)
                    path_to_node[target_node_index] = list(path_to_node[node_index])
                    path_to_node[target_node_index].append(node_index)

                elif target_node:
                    # If we can't go to this node, store the reason in order to build the satisfiable requirements.
                    # Note we ignore the 'additional requirements' here because it'll be added on the end.
                    requirements_by_node[target_node_index].update(
                        requirement.as_set(initial_state.resource_database).alternatives)

        # Discard satisfiable requirements of nodes reachable by other means
        for node_index in set(reach_nodes.keys()).intersection(requirements_by_node.keys()):
            requirements_by_node.pop(node_index)

        if requirements_by_node:
            satisfiable_requirements = frozenset.union(
                *[RequirementSet(requirements).union(logic.get_additional_requirements(all_nodes[node_index])).alternatives
                  for node_index, requirements in requirements_by_node.items()])
        else:
            satisfiable_requirements = frozenset()

        return ResolverReach(reach_nodes, path_to_node,
                             satisfiable_requirements,
                             logic)
Beispiel #25
0
 def requirement_to_leave(self, context: NodeContext) -> Requirement:
     return Requirement.trivial()
Beispiel #26
0
 def final_requirement(self) -> Optional[Requirement]:
     result = self.build_requirement()
     if result == Requirement.impossible():
         return None
     return result
 def _uses_trick(requirements: Requirement) -> bool:
     return any(
         criteria(individual)
         for individual in requirements.as_set(database).all_individual)
Beispiel #28
0
 def is_connection_visible(self, requirement: Requirement) -> bool:
     return self.state is None or requirement.satisfied(
         self.state.resources, self.state.energy,
         self.state.resource_database)
Beispiel #29
0
    def contextMenuEvent(self, event: QtGui.QContextMenuEvent) -> None:
        local_pos = QPointF(self.mapFromGlobal(event.globalPos()))
        local_pos -= self.get_area_canvas_offset()
        self._next_node_location = self.qt_local_to_game_loc(local_pos)

        menu = QtWidgets.QMenu(self)
        if self.state is None:
            menu.addAction(self._show_all_connections_action)
        if self.edit_mode:
            menu.addAction(self._create_node_action)
            menu.addAction(self._move_node_action)
            self._move_node_action.setEnabled(
                self.highlighted_node is not None)
            if self.highlighted_node is not None:
                self._move_node_action.setText(
                    f"Move {self.highlighted_node.name} here")

        # Areas Menu
        menu.addSeparator()
        areas_at_mouse = self._other_areas_at_position(local_pos)

        for area in areas_at_mouse:
            sub_menu = QtWidgets.QMenu(f"Area: {area.name}", self)
            sub_menu.addAction("View area").triggered.connect(
                functools.partial(self.SelectAreaRequest.emit, area))
            if self.edit_mode:
                sub_menu.addAction(
                    "Create dock here to this area").triggered.connect(
                        functools.partial(self.CreateDockRequest.emit,
                                          self._next_node_location, area))
            menu.addMenu(sub_menu)

        if not areas_at_mouse:
            sub_menu = QtGui.QAction("No areas here", self)
            sub_menu.setEnabled(False)
            menu.addAction(sub_menu)

        # Nodes Menu
        menu.addSeparator()
        nodes_at_mouse = self._nodes_at_position(local_pos)
        if self.highlighted_node in nodes_at_mouse:
            nodes_at_mouse.remove(self.highlighted_node)

        for node in nodes_at_mouse:
            if len(nodes_at_mouse) == 1:
                menu.addAction(node.name).setEnabled(False)
                sub_menu = menu
            else:
                sub_menu = QtWidgets.QMenu(node.name, self)

            sub_menu.addAction("Highlight this").triggered.connect(
                functools.partial(self.SelectNodeRequest.emit, node))
            view_connections = sub_menu.addAction("View connections to this")
            view_connections.setEnabled(
                (self.edit_mode and self.highlighted_node != node) or
                (node in self.area.connections.get(self.highlighted_node, {})))
            view_connections.triggered.connect(
                functools.partial(self.SelectConnectionsRequest.emit, node))

            if self.edit_mode:
                sub_menu.addSeparator()
                sub_menu.addAction(
                    "Replace connection with Trivial").triggered.connect(
                        functools.partial(
                            self.ReplaceConnectionsRequest.emit,
                            node,
                            Requirement.trivial(),
                        ))
                sub_menu.addAction("Remove connection").triggered.connect(
                    functools.partial(
                        self.ReplaceConnectionsRequest.emit,
                        node,
                        Requirement.impossible(),
                    ))
                if areas_at_mouse:
                    move_menu = QtWidgets.QMenu("Move to...", self)
                    for area in areas_at_mouse:
                        move_menu.addAction(area.name).triggered.connect(
                            functools.partial(
                                self.MoveNodeToAreaRequest.emit,
                                node,
                                area,
                            ))
                    sub_menu.addMenu(move_menu)

            if sub_menu != menu:
                menu.addMenu(sub_menu)

        if not nodes_at_mouse:
            sub_menu = QtGui.QAction("No other nodes here", self)
            sub_menu.setEnabled(False)
            menu.addAction(sub_menu)

        # Done

        menu.exec_(event.globalPos())
Beispiel #30
0
 def requirement_to_leave(self, context: NodeContext) -> Requirement:
     if context.current_resources.add_self_as_requirement_to_resources:
         return ResourceRequirement.simple(self.event)
     else:
         return Requirement.trivial()