def test_replace_node(game_editor): # Setup world_list = game_editor.game.world_list loc = AreaIdentifier("Temple Grounds", "Landing Site") loc2 = AreaIdentifier("Temple Grounds", "Service Access") landing_site = world_list.area_by_area_location(loc) source = landing_site.node_with_name("Save Station") door = landing_site.node_with_name("Door to Service Access") assert isinstance(door, DockNode) req = landing_site.connections[source][door] new_node = dataclasses.replace( door, identifier=door.identifier.renamed("FooBar")) # Run game_editor.replace_node(landing_site, door, new_node) # Assert assert world_list.area_by_area_location( loc).connections[source][new_node] is req dock_to_landing = world_list.area_by_area_location(loc2).node_with_name( "Door to Landing Site") assert isinstance(dock_to_landing, DockNode) assert dock_to_landing.default_connection.node_name == "FooBar"
def _starting_location_on_select_classic(self): classics = [ AreaIdentifier("Mimiga Village", "Start Point"), AreaIdentifier("Mimiga Village", "Arthur's House"), AreaIdentifier("Labyrinth", "Camp") ] with self._editor as editor: editor.set_configuration_field( "starting_location", editor.configuration.starting_location.with_elements( classics, self.game_enum))
def test_add_elevator_connections_to_patches_vanilla(echoes_game_description, skip_final_bosses: bool, default_echoes_configuration, echoes_game_patches): # Setup patches_factory = echoes_game_description.game.generator.base_patches_factory expected = dataclasses.replace(echoes_game_patches, elevator_connection=echoes_game_description.get_default_elevator_connection()) if skip_final_bosses: node_ident = NodeIdentifier.create("Temple Grounds", "Sky Temple Gateway", "Teleport to Great Temple - Sky Temple Energy Controller") expected.elevator_connection[node_ident] = AreaIdentifier("Temple Grounds", "Credits") config = default_echoes_configuration config = dataclasses.replace(config, elevators=dataclasses.replace(config.elevators, skip_final_bosses=skip_final_bosses)) # Run result = patches_factory.add_elevator_connections_to_patches( config, Random(0), echoes_game_patches) # Assert assert result == expected
def test_create_spawn_point_field(echoes_game_description, empty_patches): # Setup resource_db = echoes_game_description.resource_database loc = AreaIdentifier("Temple Grounds", "Hive Chamber B") patches = empty_patches.assign_starting_location(loc).assign_extra_initial_items({ resource_db.get_by_type_and_index(ResourceType.ITEM, "MorphBall"): 3, }) capacities = [ {'amount': 3 if item.short_name == "MorphBall" else 0, 'index': item.extra["item_id"]} for item in resource_db.item ] # Run result = patch_data_factory._create_spawn_point_field(patches, echoes_game_description) # Assert assert result == { "location": { "world_asset_id": 1006255871, "area_asset_id": 494654382, }, "amount": capacities, "capacity": capacities, }
def test_rename_area(game_editor): # Setup world_list = game_editor.game.world_list loc_1 = AreaIdentifier("Temple Grounds", "Transport to Agon Wastes") loc_2 = AreaIdentifier("Agon Wastes", "Transport to Temple Grounds") final = AreaIdentifier("Temple Grounds", "Foo Bar Transportation") # Run game_editor.rename_area(world_list.area_by_area_location(loc_1), "Foo Bar Transportation") # Assert assert world_list.area_by_area_location(final) is not None area_2 = world_list.area_by_area_location(loc_2) assert area_2.node_with_name( "Elevator to Temple Grounds - Foo Bar Transportation") is not None
def test_create_message_for_hint_relative_area(echoes_game_patches, blank_pickup, players_config, echoes_hint_exporter, offset, distance_text): patches = echoes_game_patches.assign_new_pickups([ (PickupIndex(5), PickupTarget(blank_pickup, 0)), ]) hint = Hint( HintType.LOCATION, PrecisionPair(HintLocationPrecision.RELATIVE_TO_AREA, HintItemPrecision.DETAILED, include_owner=False, relative=RelativeDataArea( offset, AreaIdentifier("Torvus Bog", "Great Bridge"), HintRelativeAreaName.NAME)), PickupIndex(5)) namer = EchoesHintNamer({0: patches}, PlayersConfiguration(0, {0: "You"})) exporter = HintExporter(namer, random.Random(0), ["A Joke"]) # Run result = exporter.create_message_for_hint(hint, {0: patches}, players_config, True) # Assert assert result == ( f'The &push;&main-color=#FF6705B3;Blank Pickup&pop; can be found ' f'&push;&main-color=#FF3333;{distance_text} {10 + (offset or 0)} rooms&pop; away from ' f'Torvus Bog - Great Bridge.')
def test_create_new_dock(skip_qtbot, tmp_path, blank_game_data): db_path = Path(tmp_path.joinpath("test-game", "game")) game_data = default_data.read_json_then_binary(RandovaniaGame.BLANK)[1] window = DataEditorWindow(game_data, db_path, True, True) window.set_warning_dialogs_disabled(True) skip_qtbot.addWidget(window) window.focus_on_area_by_name("Back-Only Lock Room") current_area = window.current_area target_area = window.game_description.world_list.area_by_area_location( AreaIdentifier("Intro", "Explosive Depot")) assert current_area.node_with_name("Dock to Explosive Depot") is None assert target_area.node_with_name("Dock to Back-Only Lock Room") is None # Run window._create_new_dock(NodeLocation(0, 0, 0), target_area) # Assert new_node = current_area.node_with_name("Dock to Explosive Depot") assert new_node is not None assert window.current_node is new_node assert target_area.node_with_name( "Dock to Back-Only Lock Room") is not None
def _area_name_to_area_location(world_list: WorldList, area_name: str) -> AreaIdentifier: world_name, area_name = re.match("([^/]+)/([^/]+)", area_name).group(1, 2) # Filter out dark world names world_name = world_list.world_with_name(world_name).name return AreaIdentifier(world_name, area_name)
def test_one_way_elevator_connections(echoes_game_description, replacement, expected): # Setup rng = random.Random(5000) target_locations = [AreaIdentifier(f"w{i}", f"a{i}") for i in range(6)] elevators = [ ElevatorHelper(NodeIdentifier.create(f"w{i}", f"a{i}", f"n{i}"), AreaIdentifier(f"w{i}", f"a{i}")) for i in range(6) ] database = tuple(elevators) # Run result = elevator_distributor.one_way_elevator_connections( rng, database, target_locations, replacement) # Assert assert result == expected
def test_replace_node_unknown_node(game_editor): # Setup world_list = game_editor.game.world_list loc = AreaIdentifier("Temple Grounds", "Landing Site") loc2 = AreaIdentifier("Temple Grounds", "Service Access") landing_site = world_list.area_by_area_location(loc) dock = world_list.area_by_area_location(loc2).node_with_name( "Door to Landing Site") # Run with pytest.raises( ValueError, match= "Given Door to Landing Site does does not belong to Landing Site." ): game_editor.replace_node(landing_site, dock, dock)
def static_teleporters(self) -> Dict[NodeIdentifier, AreaIdentifier]: static = {} if self.skip_final_bosses: if self.game == RandovaniaGame.METROID_PRIME: crater = NodeIdentifier.create( "Tallon Overworld", "Artifact Temple", "Teleport to Impact Crater - Crater Impact Point") static[crater] = AreaIdentifier("End of Game", "Credits") elif self.game == RandovaniaGame.METROID_PRIME_ECHOES: gateway = NodeIdentifier.create( "Temple Grounds", "Sky Temple Gateway", "Teleport to Great Temple - Sky Temple Energy Controller") static[gateway] = AreaIdentifier("Temple Grounds", "Credits") else: raise ValueError( f"Unsupported skip_final_bosses and {self.game}") return static
def _location_with_data(request, mocker, echoes_game_description): world_list = echoes_game_description.world_list areas = list(itertools.islice( (AreaIdentifier(world.name, area.name) for world in world_list.worlds for area in world.areas if area.valid_starting_location), 15)) mocker.patch("randovania.layout.lib.location_list.LocationList.areas_list", return_value=list(sorted(areas))) return request.param["encoded"], LocationList.from_json(request.param["json"], RandovaniaGame.METROID_PRIME_ECHOES)
def area_locations_with_filter( game: RandovaniaGame, condition: Callable[[Area], bool]) -> list[AreaIdentifier]: world_list = default_database.game_description_for(game).world_list identifiers = [ AreaIdentifier( world_name=world.name, area_name=area.name, ) for world in world_list.worlds for area in world.areas if condition(area) ] return _sorted_area_identifiers(identifiers)
def test_add_relative_hint(echoes_game_description, echoes_game_patches, precise_distance, location_precision, echoes_item_database): # Setup rng = Random(5000) target_precision = MagicMock(spec=HintItemPrecision) precision = MagicMock(spec=HintItemPrecision) patches = echoes_game_patches.assign_new_pickups([ (PickupIndex(8), PickupTarget( _make_pickup(echoes_item_database.item_categories["movement"]), 0)), ]) hint_distributor = EchoesHintDistributor() if location_precision == HintLocationPrecision.RELATIVE_TO_AREA: max_distance = 8 data = RelativeDataArea( None if precise_distance else 3, # Was Industrial Site AreaIdentifier("Temple Grounds", "Hive Chamber A"), precision, ) else: max_distance = 20 data = RelativeDataItem( None if precise_distance else 11, PickupIndex(8), precision, ) # Run result = hint_distributor.add_relative_hint( echoes_game_description.world_list, patches, rng, PickupIndex(1), target_precision, location_precision, precise_distance, precision, max_distance=max_distance, ) # Assert pair = PrecisionPair(location_precision, target_precision, include_owner=False, relative=data) assert result == Hint(HintType.LOCATION, pair, PickupIndex(1))
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 test_quick_fill_cs_classic(skip_qtbot, preset_manager): # Setup base = preset_manager.default_preset_for_game( RandovaniaGame.CAVE_STORY).get_preset() preset = dataclasses.replace( base, uuid=uuid.UUID('b41fde84-1f57-4b79-8cd6-3e5a78077fa6'), base_preset_uuid=base.uuid) editor = PresetEditor(preset) window = PresetCSStartingArea( editor, default_database.game_description_for(preset.game)) skip_qtbot.addWidget(window) # Run skip_qtbot.mouseClick(window.starting_area_quick_fill_classic, QtCore.Qt.LeftButton) # Assert expected = { AreaIdentifier("Mimiga Village", "Start Point"), AreaIdentifier("Mimiga Village", "Arthur's House"), AreaIdentifier("Labyrinth", "Camp") } assert set(editor.configuration.starting_location.locations) == expected
def test_remove_node(game_editor): # Setup world_list = game_editor.game.world_list loc = AreaIdentifier("Temple Grounds", "Landing Site") landing_site = world_list.area_by_area_location(loc) node = landing_site.node_with_name("Door to Service Access") assert node is not None # Run game_editor.remove_node(landing_site, node) # Assert assert world_list.area_by_area_location(loc).node_with_name( "Door to Service Access") is None
def test_two_way_elevator_connections_unchecked(): # Setup rng = random.Random(5000) elevators = [ ElevatorHelper(NodeIdentifier.create(f"w{i}", f"a{i}", f"n{i}"), AreaIdentifier(f"w{i}", f"a{i}")) for i in range(6) ] database = tuple(elevators) # Run result = elevator_distributor.two_way_elevator_connections( rng, database, False) # Assert assert result == { NodeIdentifier.create("w0", "a0", "n0"): AreaIdentifier("w4", "a4"), NodeIdentifier.create("w1", "a1", "n1"): AreaIdentifier("w2", "a2"), NodeIdentifier.create("w2", "a2", "n2"): AreaIdentifier("w1", "a1"), NodeIdentifier.create("w3", "a3", "n3"): AreaIdentifier("w5", "a5"), NodeIdentifier.create("w4", "a4", "n4"): AreaIdentifier("w0", "a0"), NodeIdentifier.create("w5", "a5", "n5"): AreaIdentifier("w3", "a3"), }
def create_new_node(self) -> Node: node_type = self.node_type_combo.currentData() identifier = dataclasses.replace(self.node.identifier, node_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()) description = self.description_edit.toMarkdown() extra = json.loads(self.extra_edit.toPlainText()) layers = (self.layers_combo.currentText(),) if node_type == GenericNode: return GenericNode(identifier, heal, location, description, layers, extra) elif node_type == DockNode: connection_node: Node = self.dock_connection_node_combo.currentData() return DockNode( identifier, 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, 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, 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, heal, location, description, layers, extra, event, ) elif node_type == ConfigurableNode: return ConfigurableNode( identifier, 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, 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, heal, location, description, layers, extra, self._unlocked_by_requirement, self._get_command_visor() ) else: raise RuntimeError(f"Unknown node type: {node_type}")
def create(cls, world_name: str, area_name: str, node_name: str) -> "NodeIdentifier": return cls(AreaIdentifier(world_name, area_name), node_name)
default_echoes_configuration.major_items_configuration, minimum_random_starting_items=2, ), sky_temple_keys=LayoutSkyTempleKeyMode.ALL_BOSSES, ) # Run result = pool_creator.calculate_pool_item_count(layout_configuration) # Assert assert result == (118, 121) @pytest.mark.parametrize("puppies", [False, True]) @pytest.mark.parametrize("starting_area", [ AreaIdentifier("Mimiga Village", "Start Point"), AreaIdentifier("Labyrinth", "Camp") ]) def test_cs_item_pool_creator(default_cs_configuration: CSConfiguration, puppies, starting_area): game_description = default_database.game_description_for( default_cs_configuration.game) default_cs_configuration = dataclasses.replace(default_cs_configuration, puppies_anywhere=puppies) tricks = default_cs_configuration.trick_level.set_level_for_trick( game_description.resource_database.get_by_type_and_index( ResourceType.TRICK, "SNBubbler"), LayoutTrickLevel.HYPERMODE) tricks = tricks.set_level_for_trick( game_description.resource_database.get_by_type_and_index( ResourceType.TRICK, "SNMissiles"), LayoutTrickLevel.HYPERMODE) default_cs_configuration = dataclasses.replace(default_cs_configuration,
def add(world: str, area: str, node: str, target_world: str, target_area: str): elevator_connection.append(( wl.get_teleporter_node(NodeIdentifier.create(world, area, node)), AreaIdentifier(target_world, target_area), ))
rng, database, False) # Assert assert result == { NodeIdentifier.create("w0", "a0", "n0"): AreaIdentifier("w4", "a4"), NodeIdentifier.create("w1", "a1", "n1"): AreaIdentifier("w2", "a2"), NodeIdentifier.create("w2", "a2", "n2"): AreaIdentifier("w1", "a1"), NodeIdentifier.create("w3", "a3", "n3"): AreaIdentifier("w5", "a5"), NodeIdentifier.create("w4", "a4", "n4"): AreaIdentifier("w0", "a0"), NodeIdentifier.create("w5", "a5", "n5"): AreaIdentifier("w3", "a3"), } @pytest.mark.parametrize(["replacement", "expected"], [ (False, { NodeIdentifier.create("w0", "a0", "n0"): AreaIdentifier("w1", "a1"), NodeIdentifier.create("w1", "a1", "n1"): AreaIdentifier("w2", "a2"), NodeIdentifier.create("w2", "a2", "n2"): AreaIdentifier("w3", "a3"), NodeIdentifier.create("w3", "a3", "n3"): AreaIdentifier("w5", "a5"), NodeIdentifier.create("w4", "a4", "n4"): AreaIdentifier("w0", "a0"), NodeIdentifier.create("w5", "a5", "n5"): AreaIdentifier("w4", "a4"), }), (True, { NodeIdentifier.create("w0", "a0", "n0"): AreaIdentifier("w2", "a2"), NodeIdentifier.create("w1", "a1", "n1"): AreaIdentifier("w3", "a3"), NodeIdentifier.create("w2", "a2", "n2"): AreaIdentifier("w4", "a4"), NodeIdentifier.create("w3", "a3", "n3"): AreaIdentifier("w2", "a2"), NodeIdentifier.create("w4", "a4", "n4"): AreaIdentifier("w5", "a5"), NodeIdentifier.create("w5", "a5", "n5"): AreaIdentifier("w3", "a3"), }), ])
def add(world: str, area: str, node: str, target_world: str, target_area: str): elevator_connection[NodeIdentifier.create(world, area, node)] = AreaIdentifier(target_world, target_area)
def _a(world, area, instance_id=None): if instance_id is not None: return NodeIdentifier.create(world, area, instance_id).as_json return AreaIdentifier(world, area).as_json
def setup_elevators(self): self._elevator_id_to_combo = {} if not hasattr(self.game_configuration, "elevators"): return elevators_config: TeleporterConfiguration = getattr( self.game_configuration, "elevators") world_list = self.game_description.world_list nodes_by_world: Dict[ str, List[TeleporterNode]] = collections.defaultdict(list) areas_to_not_change = { "Sky Temple Gateway", "Sky Temple Energy Controller", "Aerie Transport Station", "Aerie", } targets = {} for world, area, node in world_list.all_worlds_areas_nodes: if isinstance( node, TeleporterNode ) and node.editable and area.name not in areas_to_not_change: name = world.correct_name(area.in_dark_aether) nodes_by_world[name].append(node) location = AreaIdentifier(world.name, area.name) targets[elevators.get_short_elevator_or_area_name( self.game_configuration.game, world_list, location, True)] = location if elevators_config.mode == TeleporterShuffleMode.ONE_WAY_ANYTHING: targets = {} for world in world_list.worlds: for area in world.areas: name = world.correct_name(area.in_dark_aether) targets[f"{name} - {area.name}"] = AreaIdentifier( world.name, area.name) combo_targets = sorted(targets.items(), key=lambda it: it[0]) for world_name in sorted(nodes_by_world.keys()): nodes = nodes_by_world[world_name] nodes_locations = [ world_list.identifier_for_node(node).area_location for node in nodes ] nodes_names = [ elevators.get_short_elevator_or_area_name( self.game_configuration.game, world_list, location, False) for location in nodes_locations ] group = QtWidgets.QGroupBox(self.elevators_scroll_contents) group.setTitle(world_name) self.elevators_scroll_layout.addWidget(group) layout = QtWidgets.QGridLayout(group) for i, (node, location, name) in enumerate( sorted(zip(nodes, nodes_locations, nodes_names), key=lambda it: it[2])): node_name = QtWidgets.QLabel(group) node_name.setText(name) node_name.setWordWrap(True) node_name.setMinimumWidth(75) layout.addWidget(node_name, i, 0) combo = QtWidgets.QComboBox(group) if elevators_config.is_vanilla: combo.addItem("Vanilla", node.default_connection) combo.setEnabled(False) else: combo.addItem("Undefined", None) for target_name, connection in combo_targets: combo.addItem(target_name, connection) combo.setMinimumContentsLength(11) combo.currentIndexChanged.connect( self.update_locations_tree_for_reachable_nodes) self._elevator_id_to_combo[world_list.identifier_for_node( node)] = combo layout.addWidget(combo, i, 1)
def node_to_area_location(self, node: Node) -> AreaIdentifier: return AreaIdentifier( world_name=self.nodes_to_world(node).name, area_name=self.nodes_to_area(node).name, )
def identifier_for_area(self, area: Area) -> AreaIdentifier: world = self.world_with_area(area) return AreaIdentifier(world_name=world.name, area_name=area.name)
def ni(w: str, a: str, n: str, tw: str, ta: str): elevator_connection[NodeIdentifier.create(w, a, n)] = AreaIdentifier(tw, ta)