async def on_database_area_selected(self, ctx: ComponentContext, game: RandovaniaGame, split_world: SplitWorld, world_id: int): option_selected = ctx.selected_options[0] valid_items = [ area for i, area in enumerate(split_world.areas) if f"area_{i}" == option_selected ] if not valid_items: return await ctx.edit_origin( components=[], embeds=[], content= f"Invalid selected option, unable to find given area '{option_selected}'." ) area = valid_items[0] db = default_database.game_description_for(game) title = "{}: {}".format(game.long_name, db.world_list.area_name(area)) valid_nodes = [ node for node in sorted(area.nodes, key=lambda it: it.name) if not node.is_derived_node ] select = manage_components.create_select( options=[ manage_components.create_select_option(node.name, value=node.name) for node in valid_nodes ], max_values=min(10, len(valid_nodes)), custom_id=f"{game.value}_world_{world_id}_{option_selected}", placeholder="Choose the room", ) action_row = manage_components.create_actionrow(select) files = [] area_image = render_area_with_pillow(area, game.data_path) if area_image is not None: files.append( discord.File(area_image, filename=f"{area.name}_image.png")) area_graph = render_area_with_graphviz(area) if area_graph is not None: files.append( discord.File(area_graph, filename=f"{area.name}_graph.png")) logging.info("Responding to area for %s with %d attachments.", area.name, len(files)) await ctx.send( content=f"**{title}**\nRequested by {ctx.author.display_name}.", files=files, components=[ action_row, ], )
def valid_targets(self) -> List[AreaIdentifier]: if self.mode == TeleporterShuffleMode.ONE_WAY_ANYTHING: return [ location for location in self.excluded_targets.areas_list(self.game) if location not in self.excluded_targets.locations ] elif self.mode in { TeleporterShuffleMode.ONE_WAY_ELEVATOR, TeleporterShuffleMode.ONE_WAY_ELEVATOR_REPLACEMENT }: world_list = default_database.game_description_for( self.game).world_list result = [] for identifier in self.editable_teleporters: node = world_list.node_by_identifier(identifier) if isinstance(node, TeleporterNode) and node.editable: # Valid destinations must be valid starting areas area = world_list.nodes_to_area(node) if area.valid_starting_location: result.append(identifier.area_identifier) # Hack for Metroid Prime 1, where the scripting for Metroid Prime Lair is dependent on the previous room elif area.name == "Metroid Prime Lair": result.append( AreaIdentifier.from_string( "Impact Crater/Subchamber Five")) return result else: return []
def update_content(self, configuration: BaseConfiguration, all_patches: dict[int, GamePatches], players: PlayersConfiguration): self.tree_widget.clear() self.tree_widget.setColumnCount(2) self.tree_widget.setHeaderLabels(["Source", "Destination"]) world_list = default_database.game_description_for( self.game_enum).world_list patches = all_patches[players.player_index] per_world: dict[str, dict[str, str]] = collections.defaultdict(dict) for source_loc, destination_loc in patches.elevator_connection.items(): source_world = world_list.world_by_area_location( source_loc.area_identifier) source_name = elevators.get_elevator_or_area_name( self.game_enum, world_list, source_loc.area_identifier, True) per_world[source_world. name][source_name] = elevators.get_elevator_or_area_name( self.game_enum, world_list, destination_loc, True) for world_name, world_contents in iterate_key_sorted(per_world): world_item = QtWidgets.QTreeWidgetItem(self.tree_widget) world_item.setText(0, world_name) world_item.setExpanded(True) for source_name, destination in iterate_key_sorted(world_contents): area_item = QtWidgets.QTreeWidgetItem(world_item) area_item.setText(0, source_name) area_item.setText(1, destination) self.tree_widget.resizeColumnToContents(0) self.tree_widget.resizeColumnToContents(1)
def prime2_preset_tabs(editor: PresetEditor, window_manager: WindowManager): game_enum = editor.game game_description = default_database.game_description_for(game_enum) from randovania.gui.preset_settings.trick_level_tab import PresetTrickLevel from randovania.gui.preset_settings.patcher_energy_tab import PresetPatcherEnergy from randovania.gui.preset_settings.elevators_tab import PresetElevators from randovania.gui.preset_settings.starting_area_tab import PresetMetroidStartingArea from randovania.gui.preset_settings.generation_tab import PresetGeneration from randovania.games.prime2.gui.preset_settings.echoes_goal_tab import PresetEchoesGoal from randovania.games.prime2.gui.preset_settings.echoes_hints_tab import PresetEchoesHints from randovania.games.prime2.gui.preset_settings.echoes_translators_tab import PresetEchoesTranslators from randovania.games.prime2.gui.preset_settings.echoes_beam_configuration_tab import PresetEchoesBeamConfiguration from randovania.games.prime2.gui.preset_settings.echoes_patches_tab import PresetEchoesPatches from randovania.gui.preset_settings.location_pool_tab import PresetLocationPool from randovania.games.prime2.gui.preset_settings.echoes_item_pool_tab import EchoesPresetItemPool return [ PresetTrickLevel(editor, game_description, window_manager), PresetPatcherEnergy(editor, game_enum), PresetElevators(editor, game_description), PresetMetroidStartingArea(editor, game_description), PresetGeneration(editor, game_description), PresetEchoesGoal(editor), PresetEchoesHints(editor), PresetEchoesTranslators(editor), PresetEchoesBeamConfiguration(editor), PresetEchoesPatches(editor), PresetLocationPool(editor, game_description), EchoesPresetItemPool(editor), ]
def prime1_preset_tabs(editor: PresetEditor, window_manager: WindowManager): game_enum = editor.game game_description = default_database.game_description_for(game_enum) from randovania.gui.preset_settings.trick_level_tab import PresetTrickLevel from randovania.gui.preset_settings.patcher_energy_tab import PresetPatcherEnergy from randovania.gui.preset_settings.elevators_tab import PresetElevators from randovania.gui.preset_settings.starting_area_tab import PresetMetroidStartingArea from randovania.games.prime1.gui.preset_settings.prime_goal_tab import PresetPrimeGoal from randovania.games.prime1.gui.preset_settings.prime_hints_tab import PresetPrimeHints from randovania.games.prime1.gui.preset_settings.prime_patches_tab import PresetPrimePatches from randovania.gui.preset_settings.location_pool_tab import PresetLocationPool from randovania.gui.preset_settings.metroid_item_pool_tab import MetroidPresetItemPool from randovania.games.prime1.gui.preset_settings.prime_generation_tab import PresetPrimeGeneration return [ PresetTrickLevel(editor, game_description, window_manager), PresetPatcherEnergy(editor, game_enum), PresetElevators(editor, game_description), PresetMetroidStartingArea(editor, game_description), PresetPrimeGeneration(editor, game_description), PresetPrimeGoal(editor), PresetPrimeHints(editor), PresetPrimePatches(editor), PresetLocationPool(editor, game_description), MetroidPresetItemPool(editor), ]
async def _show_dialog_for_prime3_layout(self): from randovania.games.patchers import gollop_corruption_patcher patches = self.layout_description.all_patches[ self.current_player_index] game = default_database.game_description_for(RandovaniaGame.PRIME3) item_names = [] for index in range(game.world_list.num_pickup_nodes): p_index = PickupIndex(index) if p_index in patches.pickup_assignment: name = patches.pickup_assignment[p_index].pickup.name else: name = "Missile Expansion" item_names.append(name) layout_string = gollop_corruption_patcher.layout_string_for_items( item_names) starting_location = patches.starting_location commands = "\n".join([ f'set seed="{layout_string}"', f'set "starting_items={gollop_corruption_patcher.starting_items_for(patches.starting_items)}"', f'set "starting_location={gollop_corruption_patcher.starting_location_for(starting_location)}"', ]) message_box = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Information, "Commands for patcher", commands) common_qt_lib.set_default_window_icon(message_box) message_box.setTextInteractionFlags(Qt.TextSelectableByMouse) QApplication.clipboard().setText(commands) await async_dialog.execute_dialog(message_box)
def __init__(self, editor: PresetEditor): super().__init__() self.setupUi(self) self._editor = editor size_policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.item_pool_layout.setAlignment(Qt.AlignTop) # Relevant Items self.game = editor.game self.game_description = default_database.game_description_for( self.game) item_database = default_database.item_database_for_game(self.game) self._energy_tank_item = item_database.major_items["Energy Tank"] self._register_random_starting_events() self._create_categories_boxes(item_database, size_policy) self._create_customizable_default_items(item_database) self._create_progressive_widgets(item_database) self._create_major_item_boxes(item_database, self.game_description.resource_database) self._create_energy_tank_box() self._create_split_ammo_widgets(item_database) self._create_ammo_pickup_boxes(size_policy, item_database)
def format_area(self, location: PickupLocation, with_world: bool, with_color: bool) -> str: world_list = default_database.game_description_for( location.game).world_list area = world_list.nodes_to_area( world_list.node_from_pickup_index(location.location)) return area.name
def _setup_difficulties_menu(self, game: RandovaniaGame, menu: QtWidgets.QMenu): from randovania.game_description import default_database game = default_database.game_description_for(game) tricks_in_use = used_tricks(game) menu.clear() for trick in sorted(game.resource_database.trick, key=lambda _trick: _trick.long_name): if trick not in tricks_in_use: continue trick_menu = QtWidgets.QMenu(self) trick_menu.setTitle(trick.long_name) menu.addAction(trick_menu.menuAction()) used_difficulties = difficulties_for_trick(game, trick) for i, trick_level in enumerate(iterate_enum(LayoutTrickLevel)): if trick_level in used_difficulties: difficulty_action = QtWidgets.QAction(self) difficulty_action.setText(trick_level.long_name) trick_menu.addAction(difficulty_action) difficulty_action.triggered.connect( functools.partial(self._open_trick_details_popup, game, trick, trick_level))
def __init__(self): super().__init__() self.logger.setLevel(logging.DEBUG) self.window = QMainWindow() self.setupUi(self.window) common_qt_lib.set_default_window_icon(self.window) self.pickups = [] self.collect_location_combo.setVisible(False) self.setup_collect_location_combo_button = QtWidgets.QPushButton( self.window) self.setup_collect_location_combo_button.setText( "Load list of locations") self.setup_collect_location_combo_button.clicked.connect( self._setup_locations_combo) self.gridLayout.addWidget(self.setup_collect_location_combo_button, 0, 0, 1, 2) self.collect_location_button.clicked.connect(self._emit_collection) self.collect_location_button.setEnabled(False) self.collect_randomly_check.stateChanged.connect( self._on_collect_randomly_toggle) self._timer = QtCore.QTimer(self.window) self._timer.timeout.connect(self._collect_randomly) self._timer.setInterval(10000) self._used_version = echoes_dol_versions.ALL_VERSIONS[0] self._connector = EchoesRemoteConnector(self._used_version) self.game = default_database.game_description_for( RandovaniaGame.METROID_PRIME_ECHOES) self._game_memory = bytearray(24 * (2**20)) self._game_memory_initialized = False
def dread_preset_tabs(editor: PresetEditor, window_manager: WindowManager): game_enum = editor.game game_description = default_database.game_description_for(game_enum) from randovania.gui.preset_settings.trick_level_tab import PresetTrickLevel from randovania.gui.preset_settings.elevators_tab import PresetElevators from randovania.gui.preset_settings.starting_area_tab import PresetMetroidStartingArea from randovania.gui.preset_settings.generation_tab import PresetGeneration from randovania.gui.preset_settings.location_pool_tab import PresetLocationPool from randovania.gui.preset_settings.metroid_item_pool_tab import MetroidPresetItemPool from randovania.gui.preset_settings.patcher_energy_tab import PresetPatcherEnergy from randovania.games.dread.gui.preset_settings.dread_patches_tab import PresetDreadPatches return [ PresetTrickLevel(editor, game_description, window_manager), *([ PresetElevators(editor, game_description), PresetMetroidStartingArea(editor, game_description), ] if window_manager.is_preview_mode else []), PresetGeneration(editor, game_description), PresetLocationPool(editor, game_description), MetroidPresetItemPool(editor), PresetPatcherEnergy(editor, RandovaniaGame.METROID_DREAD), PresetDreadPatches(editor), ]
def _create_energy_box(self): category_box, category_layout, _ = self._boxes_for_category["energy_tank"] game_description = default_database.game_description_for(self.game) row = 0 for item in [self._energy_tank_item, self._energy_part_item]: resource = game_description.resource_database.get_item(item.progression[0]) starting_label = QtWidgets.QLabel(category_box) starting_label.setText(f"Starting {item.name}") category_layout.addWidget(starting_label, row, 0) spinbox = self._energy_item_to_starting_spinbox[item] = ScrollProtectedSpinBox(category_box) spinbox.setMaximum(resource.max_capacity) spinbox.valueChanged.connect(functools.partial(self._on_update_starting, item=item)) category_layout.addWidget(spinbox, row, 1) row += 1 shuffled_label = QtWidgets.QLabel(category_box) shuffled_label.setText(f"Shuffled {item.name}") category_layout.addWidget(shuffled_label, row, 0) spinbox = self._energy_item_to_shuffled_spinbox[item] = ScrollProtectedSpinBox(category_box) spinbox.setMaximum(DEFAULT_MAXIMUM_SHUFFLED[-1]) spinbox.valueChanged.connect(functools.partial(self._on_update_shuffled, item=item)) category_layout.addWidget(spinbox, row, 1) row += 1
def as_json(self) -> list: world_list = default_database.game_description_for(self.game).world_list return list(sorted( world_list.area_name(world_list.area_by_area_location(location), separator="/", distinguish_dark_aether=False) for location in self.locations ))
def test_starting_location_world_select(skip_qtbot, preset_manager): # Setup base = preset_manager.default_preset_for_game( RandovaniaGame.METROID_PRIME_ECHOES).get_preset() preset = dataclasses.replace( base, uuid=uuid.UUID('b41fde84-1f57-4b79-8cd6-3e5a78077fa6'), base_preset_uuid=base.uuid) editor = PresetEditor(preset) window = PresetMetroidStartingArea( editor, default_database.game_description_for(preset.game)) skip_qtbot.addWidget(window) # Run checkbox_list = window._starting_location_for_world window.on_preset_changed(editor.create_custom_preset_with()) assert len(checkbox_list) == 10 temple_grounds_checkbox = checkbox_list["Temple Grounds"] assert temple_grounds_checkbox.checkState() == Qt.PartiallyChecked skip_qtbot.mouseClick(temple_grounds_checkbox, Qt.LeftButton) assert temple_grounds_checkbox.checkState() == Qt.Checked assert len(editor.configuration.starting_location.locations) == 39 skip_qtbot.mouseClick(temple_grounds_checkbox, Qt.LeftButton) assert temple_grounds_checkbox.checkState() == Qt.Unchecked assert len(editor.configuration.starting_location.locations) == 0 skip_qtbot.mouseClick(temple_grounds_checkbox, Qt.LeftButton) window.on_preset_changed(editor.create_custom_preset_with()) assert temple_grounds_checkbox.checkState() == Qt.Checked assert len(editor.configuration.starting_location.locations) == 39
def game_description_for_layout( configuration: BaseConfiguration) -> GameDescription: game = derived_nodes.remove_inactive_layers( default_database.game_description_for(configuration.game), configuration.active_layers(), ) return game
def format_area(self, location: PickupLocation, with_world: bool, with_color: bool) -> str: world_list = default_database.game_description_for( location.game).world_list result = _area_name( world_list, world_list.node_from_pickup_index(location.location), not with_world) return colorize_text(self.color_location, result, with_color)
def areas_list(cls, game: RandovaniaGame): world_list = default_database.game_description_for(game).world_list areas = [ AreaLocation(world.world_asset_id, area.area_asset_id) for world in world_list.worlds for area in world.areas if area.valid_starting_location ] return list(sorted(areas))
def test_find_database_errors(game_enum: RandovaniaGame): # Setup game = default_database.game_description_for(game_enum) # Run errors = integrity_check.find_database_errors(game) # Assert assert errors == []
def test_logic_bootstrap(preset_manager, game_enum, empty_patches): configuration = preset_manager.default_preset_for_game(game_enum).get_preset().configuration game = default_database.game_description_for(configuration.game) new_game, state = game_enum.generator.bootstrap.logic_bootstrap( configuration, game.get_mutable(), dataclasses.replace(empty_patches, configuration=configuration, starting_location=game.starting_location), )
def areas_list(cls, game: RandovaniaGame) -> List[Teleporter]: world_list = default_database.game_description_for(game).world_list areas = [ node.teleporter for world in world_list.worlds for area in world.areas for node in area.nodes if isinstance(node, TeleporterNode) and node.editable ] areas.sort() return areas
def bit_pack_encode(self, metadata) -> Iterator[Tuple[int, int]]: db = default_database.game_description_for(self.game) yield from self.randomization_mode.bit_pack_encode(metadata) if self.excluded_indices: yield from bitpacking.encode_bool(True) yield from bitpacking.pack_sorted_array_elements(self._sorted_indices, _all_indices(db)) else: yield from bitpacking.encode_bool(False)
async def _show_dialog_for_prime3_layout(self): from randovania.games.prime3.patcher import gollop_corruption_patcher from randovania.games.prime3.layout.corruption_cosmetic_patches import CorruptionCosmeticPatches from randovania.games.prime3.layout.corruption_configuration import CorruptionConfiguration from randovania.game_description import default_database cosmetic = typing.cast( CorruptionCosmeticPatches, self._options.options_for_game(RandovaniaGame.METROID_PRIME_CORRUPTION).cosmetic_patches, ) configuration = typing.cast( CorruptionConfiguration, self.layout_description.get_preset(self.current_player_index).configuration, ) patches = self.layout_description.all_patches[self.current_player_index] game = default_database.game_description_for(RandovaniaGame.METROID_PRIME_CORRUPTION) pickup_names = [] for index in range(game.world_list.num_pickup_nodes): p_index = PickupIndex(index) if p_index in patches.pickup_assignment: name = patches.pickup_assignment[p_index].pickup.name else: name = "Missile Expansion" pickup_names.append(name) layout_string = gollop_corruption_patcher.layout_string_for_items(pickup_names) starting_location = patches.starting_location starting_items = patches.starting_items.duplicate() starting_items.add_resource_gain([ (game.resource_database.get_item_by_name("Suit Type"), cosmetic.player_suit.value), ]) if configuration.start_with_corrupted_hypermode: hypermode_original = 0 else: hypermode_original = 1 commands = "\n".join([ f'set seed="{layout_string}"', f'set "starting_items={gollop_corruption_patcher.starting_items_for(starting_items, hypermode_original)}"', f'set "starting_location={gollop_corruption_patcher.starting_location_for(game, starting_location)}"', f'set "random_door_colors={str(cosmetic.random_door_colors).lower()}"', f'set "random_welding_colors={str(cosmetic.random_welding_colors).lower()}"', ]) dialog_text = ( "There is no integrated patcher for Metroid Prime 3: Corruption games.\n" "Download the randomizer for it from #corruption-general in the Metroid Prime Randomizer Discord, " "and use the following commands as a seed.\n\n" "\n{}").format(commands) message_box = ScrollLabelDialog(dialog_text, "Commands for patcher", self) message_box.resize(750, 200) message_box.label.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) common_qt_lib.set_clipboard(commands) await async_dialog.execute_dialog(message_box)
def _get_vanilla_translator_configuration(extra_field: str) -> dict[NodeIdentifier, LayoutTranslatorRequirement]: from randovania.game_description import default_database game = default_database.game_description_for(RandovaniaGame.METROID_PRIME_ECHOES) return { game.world_list.identifier_for_node(node): LayoutTranslatorRequirement.from_item_short_name( node.extra[extra_field] ) for node in game.world_list.iterate_nodes() if isinstance(node, ConfigurableNode) }
def area_locations_with_filter( game: RandovaniaGame, condition: Callable[[Area], bool]) -> List[AreaLocation]: world_list = default_database.game_description_for(game).world_list areas = [ AreaLocation(world.world_asset_id, area.area_asset_id) for world in world_list.worlds for area in world.areas if condition(area) ] return list(sorted(areas))
async def add_commands(self, slash: SlashCommand): slash.add_slash_command( self.database_command, name=self.configuration.get("command_prefix", "") + "database-inspect", description= "Consult the Randovania's logic database for one specific room.", guild_ids=None, options=[ manage_commands.create_option( "game", "The game's database to check.", option_type=SlashCommandOptionType.STRING, required=True, choices=[ manage_commands.create_choice(game.value, game.long_name) for game in enum_lib.iterate_enum(RandovaniaGame) ]) ], ) def add_id(custom_id: str, call, **kwargs): self._on_database_component_listener[ custom_id] = functools.partial(call, **kwargs) for game in enum_lib.iterate_enum(RandovaniaGame): db = default_database.game_description_for(game) world_options = await create_split_worlds(db) self._split_worlds[game] = world_options add_id(f"{game.value}_world", self.on_database_world_selected, game=game) add_id(f"back_to_{game.value}", self.on_database_back_to_game, game=game) for i, split_world in enumerate(world_options): add_id(f"{game.value}_world_{i}", self.on_database_area_selected, game=game, split_world=split_world, world_id=i) for j, area in enumerate(split_world.areas): add_id(f"{game.value}_world_{i}_area_{j}", self.on_area_node_selection, game=game, area=area) slash.add_component_callback( self.on_database_component, components=list(self._on_database_component_listener.keys()), use_callback_name=False, )
def test_committed_human_readable_description(game: RandovaniaGame, tmp_path): pretty_print.write_human_readable_game( default_database.game_description_for(game), tmp_path) new_files = {f.name: f.read_text("utf-8") for f in tmp_path.glob("*.txt")} existing_files = { f.name: f.read_text("utf-8") for f in game.data_path.joinpath("json_data").glob("*.txt") } assert new_files == existing_files
def __init__(self, editor: PresetEditor): super().__init__(editor) item_database = default_database.item_database_for_game(self.game) game_description = default_database.game_description_for(self.game) size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) self._energy_tank_item = item_database.major_items["Energy Tank"] self._create_energy_tank_box( game_description.resource_database.energy_tank) self._create_pickup_style_box(size_policy)
def test_area_maps(skip_qtbot, canvas: DataEditorCanvas, mocker): canvas.select_game(RandovaniaGame.CAVE_STORY) for world in default_database.game_description_for( RandovaniaGame.CAVE_STORY).world_list.worlds: canvas.select_world(world) for area in world.areas: canvas.select_area(area) break break assert canvas._background_image is not None
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_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, trick_level=tricks) base_patches = GamePatches(0, default_cs_configuration, {}, {}, {}, {}, {}, {}, starting_area, {}) rng = Random() result = pool_creator.calculate_pool_results( default_cs_configuration, game_description.resource_database, base_patches, rng) # Puppies expected_puppies = {"Hajime", "Nene", "Mick", "Shinobu", "Kakeru"} names = {pickup.name for pickup in result.assignment.values()} assert puppies != names.issuperset(expected_puppies) # First Cave Weapon first_cave_assignment = [ pickup for index, pickup in result.assignment.items() if index in FIRST_CAVE_INDICES ] expected_first_cave_len = 1 if starting_area.area_name == "Start Point" else 0 assert len(first_cave_assignment) == expected_first_cave_len assert starting_area.area_name != "Start Point" or first_cave_assignment[ 0].broad_category.name in {"weapon", "missile_related"} # Camp weapon/life capsule camp_assignment = [ pickup for index, pickup in result.assignment.items() if index in CAMP_INDICES ] if starting_area.area_name != "Camp": assert len(camp_assignment) == 0 else: expected_names = set(STRONG_WEAPONS) expected_names.add("5HP Life Capsule") for pickup in camp_assignment: assert pickup.name in expected_names