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()
def connections_from(self, node: Node, patches: GamePatches) -> Iterator[Tuple[Node, Requirement]]: """ Queries all nodes from other areas you can go from a given node. Aka, doors and teleporters :param patches: :param node: :return: Generator of pairs Node + Requirement for going to that node """ if isinstance(node, DockNode): # TODO: respect is_blast_shield: if already opened once, no requirement needed. # Includes opening form behind with different criteria try: target_node = self.resolve_dock_node(node, patches) original_area = self.nodes_to_area(node) dock_weakness = patches.dock_weakness.get((original_area.area_asset_id, node.dock_index), node.default_dock_weakness) yield target_node, dock_weakness.requirement except IndexError: # TODO: fix data to not having docks pointing to nothing yield None, Requirement.impossible() if isinstance(node, TeleporterNode): try: yield self.resolve_teleporter_node(node, patches), Requirement.trivial() except IndexError: # TODO: fix data to not have teleporters pointing to areas with invalid default_node_index print("Teleporter is broken!", node) yield None, Requirement.impossible() if isinstance(node, PlayerShipNode): for other_node in self.all_nodes: if isinstance(other_node, PlayerShipNode) and other_node != node: yield other_node, other_node.is_unlocked
def requirement_to_leave(self, context: NodeContext) -> Requirement: # FIXME: using non-resource as key in CurrentResources if context.current_resources.get( "add_self_as_requirement_to_resources") == 1: return ResourceRequirement(self.pickup_index, 1, False) else: return Requirement.trivial()
def requirement_to_leave( self, patches: GamePatches, current_resources: CurrentResources) -> Requirement: if current_resources.get("add_self_as_requirement_to_resources") == 1: return ResourceRequirement(self.event, 1, False) else: return Requirement.trivial()
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(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 lock_node = context.node_provider.node_by_identifier( self.get_lock_node_identifier(context)) return lock_node, requirement
def reach_from_state(cls, game: GameDescription, initial_state: State, ) -> "GeneratorReach": reach = cls(game, initial_state, networkx.DiGraph()) reach._expand_graph([GraphPath(None, initial_state.node, Requirement.trivial())]) return reach
def test_requirement_as_set_2(): req = RequirementAnd([ Requirement.trivial(), _req("A"), ]) assert req.as_set(None) == RequirementSet([ RequirementList([_req("A")]), ])
def requirement_to_leave( self, patches: GamePatches, current_resources: CurrentResources) -> Requirement: # FIXME: using non-resource as key in CurrentResources if current_resources.get("add_self_as_requirement_to_resources") == 1: return ResourceRequirement(self.pickup_index, 1, False) else: return Requirement.trivial()
def test_requirement_as_set_4(): req = RequirementOr([ Requirement.impossible(), _req("A"), Requirement.trivial(), ]) assert req.as_set(None) == RequirementSet([ RequirementList([]), ])
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)
def reach_from_state( cls, game: GameDescription, initial_state: State, ) -> "GeneratorReach": reach = cls(game, initial_state, graph_module.RandovaniaGraph.new()) reach._expand_graph( [GraphPath(None, initial_state.node, Requirement.trivial())]) return reach
def connections_from( self, context: NodeContext) -> typing.Iterator[tuple[Node, Requirement]]: target_area_identifier = context.patches.elevator_connection.get( context.node_provider.identifier_for_node(self), self.default_connection, ) if target_area_identifier is None: return yield context.node_provider.default_node_for_area( target_area_identifier), Requirement.trivial()
def read_dock_weakness_database( data: Dict, resource_database: ResourceDatabase, ) -> DockWeaknessDatabase: door_types = read_array( data["door"], lambda item: read_dock_weakness(item, resource_database, DockType.DOOR)) portal_types = read_array( data["portal"], lambda item: read_dock_weakness( item, resource_database, DockType.PORTAL)) return DockWeaknessDatabase(door=door_types, morph_ball=[ DockWeakness(0, "Morph Ball Door", False, Requirement.trivial(), DockType.MORPH_BALL_DOOR) ], other=[ DockWeakness(0, "Other Door", False, Requirement.trivial(), DockType.OTHER) ], portal=portal_types)
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), ]
def test_apply_edit_connections_change( echoes_game_data, skip_qtbot, ): # Setup window = DataEditorWindow(echoes_game_data, True) skip_qtbot.addWidget(window) game = window.game_description landing_site = game.world_list.area_by_asset_id(1655756413) source = landing_site.node_with_name("Save Station") target = landing_site.node_with_name("Door to Service Access") # Run window.world_selector_box.setCurrentIndex( window.world_selector_box.findText( "Temple Grounds (Sky Temple Grounds)")) window.area_selector_box.setCurrentIndex( window.area_selector_box.findText(landing_site.name)) window._apply_edit_connections(source, target, Requirement.trivial()) # Assert assert landing_site.connections[source][target] == Requirement.trivial()
def test_basic_search_with_translator_gate(has_translator: bool, echoes_resource_database): # Setup scan_visor = echoes_resource_database.get_item(10) node_a = GenericNode("Node A", True, None, 0) node_b = GenericNode("Node B", True, None, 1) node_c = GenericNode("Node C", True, None, 2) translator_node = TranslatorGateNode("Translator Gate", True, None, 3, TranslatorGate(1), scan_visor) world_list = WorldList([ World("Test World", "Test Dark World", 1, [ Area( "Test Area A", False, 10, 0, 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_specific = EchoesGameSpecific(energy_per_tank=100, safe_zone_heal_per_second=1, beam_configurations=(), dangerous_energy_tank=False) game = GameDescription(RandovaniaGame.PRIME2, DockWeaknessDatabase([], [], [], []), echoes_resource_database, game_specific, Requirement.impossible(), None, {}, world_list) patches = game.create_game_patches() patches = patches.assign_gate_assignment({TranslatorGate(1): scan_visor}) initial_state = State({scan_visor: 1 if has_translator else 0}, (), 99, node_a, patches, None, echoes_resource_database, game.world_list) # Run reach = 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}
def _potential_nodes_from(self, node: Node) -> Iterator[Tuple[Node, Requirement, bool]]: extra_requirement = _extra_requirement_for_node(self._game, node) requirement_to_leave = node.requirement_to_leave(self._state.patches, self._state.resources) for target_node, requirement in self._game.world_list.potential_nodes_from(node, self.state.patches): 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]) satisfied = requirement.satisfied(self._state.resources, self._state.energy) yield target_node, requirement, satisfied
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}
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, (RequirementAnd, RequirementOr)): 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}")
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)
def calculate_reach(cls, logic: Logic, initial_state: State) -> "ResolverReach": checked_nodes: Dict[Node, int] = {} # Keys: nodes to check # Value: how much energy was available when visiting that node nodes_to_check: Dict[Node, int] = { initial_state.node: initial_state.energy } reach_nodes: Dict[Node, int] = {} requirements_by_node: Dict[Node, Set[RequirementList]] = defaultdict(set) path_to_node: Dict[Node, Tuple[Node, ...]] = {} path_to_node[initial_state.node] = tuple() while nodes_to_check: node = next(iter(nodes_to_check)) energy = nodes_to_check.pop(node) if node.heal: energy = initial_state.maximum_energy checked_nodes[node] = energy if node != initial_state.node: reach_nodes[node] = energy requirement_to_leave = node.requirement_to_leave(initial_state.patches, initial_state.resources) for target_node, requirement in logic.game.world_list.potential_nodes_from(node, initial_state.patches): if target_node is None: continue if checked_nodes.get(target_node, math.inf) <= energy or nodes_to_check.get(target_node, 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) 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) if satisfied: nodes_to_check[target_node] = energy - requirement.damage(initial_state.resources) path_to_node[target_node] = path_to_node[node] + (node,) 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].update(requirement.as_set.alternatives) # Discard satisfiable requirements of nodes reachable by other means for node in set(reach_nodes.keys()).intersection(requirements_by_node.keys()): requirements_by_node.pop(node) if requirements_by_node: satisfiable_requirements = frozenset.union( *[RequirementSet(requirements).union(logic.get_additional_requirements(node)).alternatives for node, requirements in requirements_by_node.items()]) else: satisfiable_requirements = frozenset() return ResolverReach(reach_nodes, path_to_node, satisfiable_requirements, logic)
def requirement_to_leave(self, patches: GamePatches, current_resources: CurrentResources) -> Requirement: return Requirement.trivial()
]), _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([
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.world = game.world_list.nodes_to_world(node) world = self.world self._type_to_tab = { GenericNode: self.tab_generic, DockNode: self.tab_dock, PickupNode: self.tab_pickup, TeleporterNode: self.tab_teleporter, EventNode: self.tab_event, TranslatorGateNode: 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) for area in world.areas: self.dock_connection_area_combo.addItem(area.name, area) refresh_if_needed(self.dock_connection_area_combo, self.on_dock_connection_area_combo) for i, enum in enumerate(enum_lib.iterate_enum(DockType)): self.dock_type_combo.setItemData(i, enum) for world in sorted(game.world_list.worlds, key=lambda x: x.name): self.teleporter_destination_world_combo.addItem( "{0.name} ({0.dark_name})".format(world), userData=world) 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, enum in enumerate(enum_lib.iterate_enum(LoreType)): self.lore_type_combo.setItemData(i, enum) 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.node_type_combo.currentIndexChanged.connect( self.on_node_type_combo) self.dock_connection_area_combo.currentIndexChanged.connect( self.on_dock_connection_area_combo) self.dock_connection_node_combo.currentIndexChanged.connect( self.on_dock_connection_node_combo) self.dock_type_combo.currentIndexChanged.connect( self.on_dock_type_combo) 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) 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)
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.world = game.world_list.nodes_to_world(node) world = self.world self._type_to_tab = { GenericNode: self.tab_generic, DockNode: self.tab_dock, PickupNode: self.tab_pickup, TeleporterNode: self.tab_teleporter, EventNode: self.tab_event, TranslatorGateNode: 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) for area in world.areas: self.dock_connection_area_combo.addItem(area.name, area) refresh_if_needed(self.dock_connection_area_combo, self.on_dock_connection_area_combo) for i, enum in enumerate(enum_lib.iterate_enum(DockType)): self.dock_type_combo.setItemData(i, enum) for world in sorted(game.world_list.worlds, key=lambda x: x.name): self.teleporter_destination_world_combo.addItem( "{0.name} ({0.dark_name})".format(world), userData=world) 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, enum in enumerate(enum_lib.iterate_enum(LoreType)): self.lore_type_combo.setItemData(i, enum) 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.node_type_combo.currentIndexChanged.connect( self.on_node_type_combo) self.dock_connection_area_combo.currentIndexChanged.connect( self.on_dock_connection_area_combo) self.dock_connection_node_combo.currentIndexChanged.connect( self.on_dock_connection_node_combo) self.dock_type_combo.currentIndexChanged.connect( self.on_dock_type_combo) 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) 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) 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, TranslatorGateNode): 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): self.dock_index_spin.setValue(node.dock_index) # Connection other_area = self.game.world_list.area_by_area_location( AreaLocation(self.world.world_asset_id, node.default_connection.area_asset_id)) self.dock_connection_area_combo.setCurrentIndex( self.dock_connection_area_combo.findData(other_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( node.default_connection.dock_index)) self.dock_connection_index_raw_spin.setValue( node.default_connection.dock_index) # Dock Weakness self.dock_type_combo.setCurrentIndex( self.dock_type_combo.findData( node.default_dock_weakness.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_asset_id( node.default_connection.world_asset_id) area = self.game.world_list.area_by_area_location( node.default_connection) self.teleporter_instance_id_edit.setText( hex(node.teleporter_instance_id) if node. teleporter_instance_id is not None else "") 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_scan_asset_id_edit.setText( hex(node.scan_asset_id) if node.scan_asset_id is not None else "") 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: TranslatorGateNode): self.translator_gate_spin.setValue(node.gate.index) 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.LUMINOTH_LORE: self.logbook_extra_combo.setCurrentIndex( self.logbook_extra_combo.findData(node.required_translator)) elif node.lore_type == LoreType.LUMINOTH_WARRIOR: 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_node_type_combo(self, _): self.tab_widget.setCurrentWidget( self._type_to_tab[self.node_type_combo.currentData()]) def on_dock_connection_area_combo(self, _): area: Area = self.dock_connection_area_combo.currentData() self.dock_connection_node_combo.clear() for node in area.nodes: if isinstance(node, DockNode): self.dock_connection_node_combo.addItem( f"{node.name} - Dock {node.dock_index}", node.dock_index) self.dock_connection_node_combo.addItem("Other", None) def on_dock_connection_node_combo(self, _): self.dock_connection_index_raw_spin.setEnabled( self.dock_connection_node_combo.currentData() is 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_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.LUMINOTH_LORE: 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.LUMINOTH_WARRIOR: self.logbook_extra_label.setText("Pickup index hinted:") for node in self.game.world_list.all_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() name = self.name_edit.text() 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()) index = self.node.index if node_type == GenericNode: return GenericNode(name, heal, location, index) elif node_type == DockNode: return DockNode( name, heal, location, index, self.dock_index_spin.value(), DockConnection( self.dock_connection_area_combo.currentData( ).area_asset_id, self.dock_connection_node_combo.currentData()), self.dock_weakness_combo.currentData(), ) elif node_type == PickupNode: return PickupNode( name, heal, location, index, PickupIndex(self.pickup_index_spin.value()), self.major_location_check.isChecked(), ) elif node_type == TeleporterNode: instance_id = self.teleporter_instance_id_edit.text() scan_asset_id = self.teleporter_scan_asset_id_edit.text() return TeleporterNode( name, heal, location, index, int(instance_id, 0) if instance_id != "" else None, AreaLocation( self.teleporter_destination_world_combo.currentData( ).world_asset_id, self.teleporter_destination_area_combo.currentData(). area_asset_id), int(scan_asset_id, 0) if scan_asset_id != "" else None, 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( name, heal, location, index, event, ) elif node_type == TranslatorGateNode: return TranslatorGateNode( name, heal, location, index, TranslatorGate(self.translator_gate_spin.value()), self._get_scan_visor()) elif node_type == LogbookNode: lore_type: LoreType = self.lore_type_combo.currentData() if lore_type == LoreType.LUMINOTH_LORE: 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.LUMINOTH_WARRIOR: hint_index = self.logbook_extra_combo.currentData() else: hint_index = None return LogbookNode( name, heal, location, index, 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(name, heal, location, index, self._unlocked_by_requirement, self._get_command_visor()) else: raise RuntimeError(f"Unknown node type: {node_type}") def _get_scan_visor(self): return find_resource_info_with_long_name( self.game.resource_database.item, "Scan Visor") def _get_command_visor(self): return find_resource_info_with_long_name( self.game.resource_database.item, "Command Visor") def try_accept(self): try: self.create_new_node() self.accept() except Exception as e: QtWidgets.QMessageBox.warning(self, "Invalid configuration", f"Unable to save node: {e}")
def test_trivial_requirement_str(): assert str(Requirement.trivial()) == "Trivial"
def test_trivial_requirement_damage(): assert Requirement.trivial().damage({}, None) == 0
def test_trivial_requirement_satisfied(): assert Requirement.trivial().satisfied({}, 99, None)
def test_trivial_requirement_as_set(): assert Requirement.trivial().as_set(None) == RequirementSet.trivial()
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"),