def pretty_description(self) -> str: if self.minimal_logic: return "Minimal Logic" count_at_difficulties = collections.defaultdict(list) for trick in _all_tricks( default_database.resource_database_for(self.game)): count_at_difficulties[self.level_for_trick(trick)].append( trick.long_name) if len(count_at_difficulties) == 1: for level in count_at_difficulties.keys(): if level == LayoutTrickLevel.DISABLED: return "All tricks disabled" return f"All tricks enabled at {level.long_name}" def tricks_at_level(tricks: List[str]) -> str: if len(tricks) != 1: return f"{len(tricks)}" else: return tricks[0] descriptions = [ f"{tricks_at_level(count_at_difficulties[level])} at {level.long_name}" for level in iterate_enum(LayoutTrickLevel) if count_at_difficulties[level] ] return "Enabled tricks: {}".format(", ".join(descriptions))
def create_tracker(self): self.delete_tracker() game_enum = RandovaniaGame.PRIME2 resource_database = default_database.resource_database_for(game_enum) with get_data_path().joinpath(f"gui_assets/tracker/{game_enum.value}.json").open("r") as tracker_details_file: tracker_details = json.load(tracker_details_file) for element in tracker_details["elements"]: text_template = "" if "image_path" in element: image_path = get_data_path().joinpath(element["image_path"]) pixmap = QPixmap(str(image_path)) label = ClickableLabel(self.inventory_group, paint_with_opacity(pixmap, 0.3), paint_with_opacity(pixmap, 1.0)) label.set_checked(False) label.set_ignore_mouse_events(True) elif "label" in element: label = QLabel(self.inventory_group) label.setAlignment(Qt.AlignCenter) text_template = element["label"] else: raise ValueError(f"Invalid element: {element}") resources = [ find_resource_info_with_long_name(resource_database.item, resource_name) for resource_name in element["resources"] ] self._tracker_elements.append(Element(label, resources, text_template)) self.inventory_layout.addWidget(label, element["row"], element["column"])
def bit_pack_unpack(cls, decoder: BitPackDecoder, metadata) -> "AmmoState": ammo: Ammo = metadata["ammo"] db = default_database.resource_database_for(ammo.game) # Ammo Count ammo_count = [] for ammo_index in ammo.items: ammo_item = db.get_item(ammo_index) ammo_count.append( bitpacking.decode_int_with_limits( decoder, (ammo_item.max_capacity // 2, ammo_item.max_capacity + 1), )) # Pickup Count pickup_count = bitpacking.decode_big_int(decoder) # Require Major Item requires_major_item = True if ammo.unlocked_by is not None: requires_major_item = bitpacking.decode_bool(decoder) return cls( ammo_count=tuple(ammo_count), pickup_count=pickup_count, requires_major_item=requires_major_item, )
def bit_pack_unpack(cls, decoder: BitPackDecoder, item: MajorItem, reference: "MajorItemState") -> "MajorItemState": db = default_database.resource_database_for(item.game) if item.progression: main_index = item.progression[0] else: main_index = item.ammo_index[0] main_item = db.get_item(main_index) # original location original = False if item.original_index is not None: original = bitpacking.decode_bool(decoder) # num shuffled shuffled = bitpacking.decode_int_with_limits(decoder, DEFAULT_MAXIMUM_SHUFFLED) # starting item if main_item.max_capacity > 1: starting = bitpacking.decode_int_with_limits( decoder, (2, main_item.max_capacity + 1)) else: starting = decoder.decode_single(main_item.max_capacity + 1) # priority priority = bitpacking.BitPackFloat.bit_pack_unpack( decoder, PRIORITY_LIMITS) # ammo index if item.ammo_index: custom_ammo = bitpacking.decode_bool(decoder) if custom_ammo: all_equal = len( item.ammo_index) <= 1 or bitpacking.decode_bool(decoder) if all_equal: ammo = decoder.decode_single( db.get_item(item.ammo_index[0]).max_capacity + 1) included_ammo = [ammo] * len(item.ammo_index) else: included_ammo = [ decoder.decode_single( db.get_item(item).max_capacity + 1) for item in item.ammo_index ] else: included_ammo = reference.included_ammo else: included_ammo = [] return cls( include_copy_in_original_location=original, num_shuffled_pickups=shuffled, num_included_in_starting_items=starting, priority=priority, included_ammo=tuple(included_ammo), )
def admin_session(user, session_id): session: GameSession = GameSession.get_by_id(session_id) rows = [] presets = session.all_presets for player in session.players: player = typing.cast(GameSessionMembership, player) if player.is_observer: rows.append([ player.effective_name, "Observer", "", ]) else: preset = presets[player.row] db = default_database.resource_database_for(preset.game) inventory = [] if player.inventory is not None: try: parsed_inventory: list[dict] = BinaryInventory.parse( player.inventory) except construct.ConstructError: # Handle old format in an adhoc way # TODO 4.3: remove this code and purge all old inventory from the server db items_by_id: dict[int, ItemResourceInfo] = { item.extra["item_id"]: item for item in db.item } parsed_inventory = [{ "name": items_by_id[item["index"]].short_name, **item, } for item in OldBinaryInventory.parse( player.inventory)] for item in parsed_inventory: if item["amount"] + item["capacity"] > 0: inventory.append("{} x{}/{}".format( db.get_item(item["name"]).long_name, item["amount"], item["capacity"])) rows.append([ player.effective_name, preset.name, ", ".join(inventory), ]) header = ["Name", "Preset", "Inventory"] return "<table border='1'><tr>{}</tr>{}</table>".format( "".join(f"<th>{h}</th>" for h in header), "".join("<tr>{}</tr>".format("".join(f"<td>{h}</td>" for h in r)) for r in rows), )
def prime1_hint_text(): db = default_database.resource_database_for(RandovaniaGame.METROID_PRIME) artifact = pickup_creator.create_artifact(0, 0, db) result = [( "Artifact", artifact.item_category, artifact.broad_category, )] return result
def prime3_hint_text(): db = default_database.resource_database_for(RandovaniaGame.METROID_PRIME_CORRUPTION) cell = pickup_creator.create_energy_cell(0, db) result = [( "Energy Cell", cell.item_category, cell.broad_category, )] return result
def create_tracker(self, game_enum: RandovaniaGame): effective_theme = self.theme if not path_for(game_enum, effective_theme).is_file(): effective_theme = TrackerTheme.CLASSIC if (game_enum, effective_theme) == self._last_tracker_config: return self.delete_tracker() resource_database = default_database.resource_database_for(game_enum) with path_for(game_enum, effective_theme).open("r") as tracker_details_file: tracker_details = json.load(tracker_details_file) for element in tracker_details["elements"]: text_template = "" if "image_path" in element: image_path = get_data_path().joinpath(element["image_path"]) pixmap = QPixmap(str(image_path)) label = ClickableLabel(self.inventory_group, paint_with_opacity(pixmap, 0.3), paint_with_opacity(pixmap, 1.0)) label.set_checked(False) label.set_ignore_mouse_events(True) elif "label" in element: label = QLabel(self.inventory_group) label.setAlignment(Qt.AlignCenter) text_template = element["label"] else: raise ValueError(f"Invalid element: {element}") resources = [ find_resource_info_with_long_name(resource_database.item, resource_name) for resource_name in element["resources"] ] self._tracker_elements.append( Element(label, resources, text_template)) self.inventory_layout.addWidget(label, element["row"], element["column"]) self.inventory_spacer = QSpacerItem(5, 5, QSizePolicy.Expanding, QSizePolicy.Expanding) self.inventory_layout.addItem(self.inventory_spacer, self.inventory_layout.rowCount(), self.inventory_layout.columnCount()) self._update_tracker_from_hook({}) self._last_tracker_config = (game_enum, effective_theme)
async def refresh_received_pickups(self): self.logger.debug(f"start") async with self._pickups_lock: game, result = await self.network_client.game_session_request_pickups( ) resource_database = default_database.resource_database_for(game) self.logger.info(f"received {len(result)} items") self._received_pickups = [(provider_name, _decode_pickup(data, resource_database)) for provider_name, data in result]
async def _on_game_session_pickups_update_raw(self, data): game = RandovaniaGame(data["game"]) resource_database = default_database.resource_database_for(game) await self.on_game_session_pickups_update( GameSessionPickups( game=game, pickups=tuple( (item["provider_name"], _decode_pickup(item["pickup"], resource_database)) for item in data["pickups"]), ))
def bit_pack_encode(self, metadata) -> Iterator[Tuple[int, int]]: ammo: Ammo = metadata["ammo"] db = default_database.resource_database_for(ammo.game) for count, ammo_index in zip(self.ammo_count, ammo.items): ammo_item = db.get_item(ammo_index) yield from bitpacking.encode_int_with_limits( count, (ammo_item.max_capacity // 2, ammo_item.max_capacity + 1), ) yield from bitpacking.encode_big_int(self.pickup_count) if ammo.unlocked_by is not None: yield from bitpacking.encode_bool(self.requires_major_item)
def check_consistency(self, item: MajorItem): db = default_database.resource_database_for(item.game) if self.num_shuffled_pickups < 0 or self.num_shuffled_pickups > DEFAULT_MAXIMUM_SHUFFLED[ -1]: raise ValueError( f"Can only shuffle between 0 and {DEFAULT_MAXIMUM_SHUFFLED[-1]} copies," f" got {self.num_shuffled_pickups}. ({item.name})") if item.must_be_starting: if not self.num_included_in_starting_items: raise ValueError( f"Required items must be included in starting items. ({item.name})" ) if self.num_included_in_starting_items > 0: if len(item.progression) > 1: raise ValueError( f"Progressive items cannot be starting item. ({item.name})" ) for progression in item.progression: if self.num_included_in_starting_items > db.get_item( progression).max_capacity: raise ValueError( f"More starting copies than the item's max copy. ({item.name})" ) if self.include_copy_in_original_location and item.original_index is None: raise ValueError(f"Custom item cannot be vanilla. ({item.name})") if not (PRIORITY_LIMITS["min"] <= self.priority <= PRIORITY_LIMITS["max"]): raise ValueError( "Priority must be between {min} and {max}, got {priority}". format( priority=self.priority, **PRIORITY_LIMITS, )) if len(self.included_ammo) != len(item.ammo_index): raise ValueError( f"Mismatched included_ammo array size. ({item.name})") for ammo_index, ammo in zip(item.ammo_index, self.included_ammo): if ammo > db.get_item(ammo_index).max_capacity: raise ValueError( f"Including more than maximum capacity for ammo {ammo_index}. Included: {ammo}; Max: {db.get_item(ammo_index).max_capacity}" )
def bit_pack_encode(self, metadata) -> Iterator[Tuple[int, int]]: resource_database = default_database.resource_database_for(self.game) yield from bitpacking.encode_bool(self.minimal_logic) if self.minimal_logic: return encodable_levels = list(LayoutTrickLevel) encodable_levels.remove(LayoutTrickLevel.DISABLED) for trick in sorted(_all_tricks(resource_database)): has_trick = self.has_specific_level_for_trick(trick) yield from bitpacking.encode_bool(has_trick) if has_trick: yield from bitpacking.pack_array_element( self.level_for_trick(trick), encodable_levels)
def bit_pack_encode( self, item: MajorItem, reference: "MajorItemState") -> Iterator[Tuple[int, int]]: db = default_database.resource_database_for(item.game) if item.progression: main_index = item.progression[0] else: main_index = item.ammo_index[0] main_item = db.get_item(main_index) # original location if item.original_index is not None: yield from bitpacking.encode_bool( self.include_copy_in_original_location) # num shuffled yield from bitpacking.encode_int_with_limits(self.num_shuffled_pickups, DEFAULT_MAXIMUM_SHUFFLED) # starting item if main_item.max_capacity > 1: yield from bitpacking.encode_int_with_limits( self.num_included_in_starting_items, (2, main_item.max_capacity + 1)) else: yield self.num_included_in_starting_items, main_item.max_capacity + 1 # priority yield from bitpacking.BitPackFloat( self.priority).bit_pack_encode(PRIORITY_LIMITS) # ammo index assert len(self.included_ammo) == len(item.ammo_index) if self.included_ammo: custom_ammo = self.included_ammo != reference.included_ammo yield from bitpacking.encode_bool(custom_ammo) if custom_ammo: all_equal = len(set(self.included_ammo)) == 1 if len(item.ammo_index) > 1: yield from bitpacking.encode_bool(all_equal) for ammo_index, ammo in zip(item.ammo_index, self.included_ammo): yield ammo, db.get_item(ammo_index).max_capacity + 1 if all_equal: break
def bit_pack_unpack(cls, decoder: BitPackDecoder, metadata): game = metadata["reference"].game resource_database = default_database.resource_database_for(game) minimal_logic = bitpacking.decode_bool(decoder) specific_levels = {} if not minimal_logic: encodable_levels = list(LayoutTrickLevel) encodable_levels.remove(LayoutTrickLevel.DISABLED) for trick in sorted(_all_tricks(resource_database)): if bitpacking.decode_bool(decoder): specific_levels[trick.short_name] = decoder.decode_element( encodable_levels) return cls(minimal_logic, specific_levels, game)
def show_item_popup(self, item: MajorItem): """ Shows the ItemConfigurationPopup for the given MajorItem :param item: :return: """ major_items_configuration = self._editor.major_items_configuration popup = ItemConfigurationPopup( self, item, major_items_configuration.items_state[item], default_database.resource_database_for(self.game)) result = popup.exec_() if result == QDialog.Accepted: with self._editor: self._editor.major_items_configuration = major_items_configuration.replace_state_for_item( item, popup.state)
def check_consistency(self, ammo: Ammo): db = default_database.resource_database_for(ammo.game) if len(self.ammo_count) != len(ammo.items): raise ValueError( f"Ammo state has {len(self.ammo_count)} ammo counts, expected {len(ammo.items)}" ) for count, ammo_index in zip(self.ammo_count, ammo.items): ammo_item = db.get_item(ammo_index) if not (0 <= count <= ammo_item.max_capacity): raise ValueError( f"Ammo count for item {ammo_index} of value {count} is not " f"in range [0, {ammo_item.max_capacity}].") if self.pickup_count < 0: raise ValueError( f"Pickup count must be at least 0, got {self.pickup_count}")
def pretty_description(self) -> str: if self.minimal_logic: return "Minimal Logic" difficulties = collections.defaultdict(int) for trick in _all_tricks( default_database.resource_database_for(self.game)): difficulties[self.level_for_trick(trick)] += 1 if len(difficulties) == 1: for level in difficulties.keys(): return f"All at {level.long_name}" descriptions = [ f"{difficulties[level]} at {level.long_name}" for level in iterate_enum(LayoutTrickLevel) if difficulties[level] > 0 ] return ", ".join(descriptions)
def create_temple_key_hint(all_patches: Dict[int, GamePatches], player_index: int, temple: HintDarkTemple, namer: HintNamer, with_color: bool, ) -> str: """ Creates the text for . :param all_patches: :param player_index: :param temple: :param namer: :param with_color: :return: """ all_world_names = {} _TEMPLE_NAMES = ["Dark Agon Temple", "Dark Torvus Temple", "Hive Temple"] temple_index = [HintDarkTemple.AGON_WASTES, HintDarkTemple.TORVUS_BOG, HintDarkTemple.SANCTUARY_FORTRESS].index(temple) db = default_database.resource_database_for(RandovaniaGame.METROID_PRIME_ECHOES) items = [db.get_item(index) for index in echoes_items.DARK_TEMPLE_KEY_ITEMS[temple_index]] locations_for_items = guaranteed_item_hint.find_locations_that_gives_items(items, all_patches, player_index) for options in locations_for_items.values(): for player, location in options: all_world_names[namer.format_world(location, with_color=False)] = (player, location) break temple_name = namer.format_temple_name(_TEMPLE_NAMES[temple_index], with_color=with_color) names_sorted = [namer.format_world(location, with_color=with_color) for name, (_, location) in sorted(all_world_names.items(), key=lambda it: it[0])] if len(names_sorted) == 0: return f"The keys to {temple_name} are nowhere to be found." elif len(names_sorted) == 1: return f"The keys to {temple_name} can all be found in {names_sorted[0]}." else: last = names_sorted.pop() front = ", ".join(names_sorted) return f"The keys to {temple_name} can be found in {front} and {last}."
def prime2_hint_text(): db = default_database.resource_database_for(RandovaniaGame.METROID_PRIME_ECHOES) result = [] for temple in range(3): key = pickup_creator.create_dark_temple_key(0, temple, db) result.append(( key.name.replace(" 1", "").strip(), key.item_category, key.broad_category, )) key = pickup_creator.create_sky_temple_key(0, db) result.append(( "Sky Temple Key", key.item_category, key.broad_category, )) return result
def apply_fixes(version: EchoesDolVersion, dol_file: DolFile): resource_database = default_database.resource_database_for( RandovaniaGame.METROID_PRIME_ECHOES) dol_file.symbols[ "CMapWorldInfo::IsAnythingSet"] = version.anything_set_address dol_file.write_instructions("CMapWorldInfo::IsAnythingSet", [ li(r3, 1), blr(), ]) dol_file.write_instructions(version.rs_debugger_printf_loop_address, [ nop(), ]) from randovania.games.prime2.exporter.patch_data_factory import item_id_for_item_resource for item in ["Double Damage", "Unlimited Missiles", "Unlimited Beam Ammo"]: index = item_id_for_item_resource( resource_database.get_item_by_name(item)) dol_file.write(version.powerup_should_persist + index, b"\x01")
def admin_session(user, session_id): session: GameSession = GameSession.get_by_id(session_id) rows = [] presets = session.all_presets for player in session.players: player = typing.cast(GameSessionMembership, player) if player.is_observer: rows.append([ player.effective_name, "Observer", "", ]) else: preset = presets[player.row] db = default_database.resource_database_for(preset.game) inventory = [] for item in json.loads(player.inventory): if item["amount"] + item["capacity"] > 0: inventory.append("{} x{}/{}".format( db.get_item(item["index"]).long_name, item["amount"], item["capacity"])) rows.append([ player.effective_name, preset.name, ", ".join(inventory), ]) header = ["Name", "Preset", "Inventory"] return "<table border='1'><tr>{}</tr>{}</table>".format( "".join(f"<th>{h}</th>" for h in header), "".join("<tr>{}</tr>".format("".join(f"<td>{h}</td>" for h in r)) for r in rows), )
def echoes_resource_database() -> ResourceDatabase: return default_database.resource_database_for(RandovaniaGame.PRIME2)
def create_tracker(self): tracker_name = self.selected_tracker if tracker_name == self._current_tracker_name or tracker_name is None: return self.delete_tracker() with path_for( self.trackers[tracker_name]).open("r") as tracker_details_file: tracker_details = json.load(tracker_details_file) game_enum = RandovaniaGame(tracker_details["game"]) resource_database = default_database.resource_database_for(game_enum) for element in tracker_details["elements"]: text_template = "" minimum_to_check = element.get("minimum_to_check", 1) field_to_check = FieldToCheck( element.get("field_to_check", FieldToCheck.CAPACITY.value)) labels = [] if "image_path" in element: paths = element["image_path"] if not isinstance(paths, list): paths = [paths] visible = True for path in paths: image_path = get_data_path().joinpath(path) if not image_path.exists(): logging.error("Tracker asset not found: %s", image_path) pixmap = QtGui.QPixmap(str(image_path)) label = ClickableLabel(self.inventory_group, paint_with_opacity(pixmap, 0.3), paint_with_opacity(pixmap, 1.0)) label.set_checked(False) label.set_ignore_mouse_events(True) label.setVisible(visible) visible = False labels.append(label) elif "label" in element: label = QtWidgets.QLabel(self.inventory_group) label.setAlignment(QtCore.Qt.AlignCenter) text_template = element["label"] labels.append(label) else: raise ValueError(f"Invalid element: {element}") resources = [ find_resource_info_with_long_name(resource_database.item, resource_name) for resource_name in element["resources"] ] for resource, label in zip(resources, labels): label.setToolTip(resource.long_name) self._tracker_elements.append( Element(labels, resources, text_template, minimum_to_check, field_to_check)) for label in labels: self.inventory_layout.addWidget(label, element["row"], element["column"]) self.inventory_spacer = QtWidgets.QSpacerItem( 5, 5, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) self.inventory_layout.addItem(self.inventory_spacer, self.inventory_layout.rowCount(), self.inventory_layout.columnCount()) self._current_tracker_game = game_enum self._update_tracker_from_hook({}) self._current_tracker_name = tracker_name
def _create_ammo_pickup_boxes(self, size_policy, item_database: ItemDatabase): """ Creates the GroupBox with SpinBoxes for selecting the pickup count of all the ammo :param item_database: :return: """ self._ammo_item_count_spinboxes = collections.defaultdict(list) self._ammo_pickup_widgets = {} resource_database = default_database.resource_database_for(self.game) broad_to_category = { "beam_related": "beam", "morph_ball_related": "morph_ball", "missile_related": "missile", } layouts_with_lines: set[tuple[Foldable, QtWidgets.QGridLayout]] = { self._boxes_for_category[broad_to_category.get(ammo.broad_category.name, ammo.broad_category.name)][:2] for ammo in item_database.ammo.values() } for box, layout in layouts_with_lines: layout.addWidget(_create_separator(box), layout.rowCount(), 0, 1, -1) for ammo in item_database.ammo.values(): category_box, category_layout, _ = self._boxes_for_category[broad_to_category.get(ammo.broad_category.name, ammo.broad_category.name)] pickup_box = QtWidgets.QGroupBox(category_box) pickup_box.setSizePolicy(size_policy) pickup_box.setTitle(ammo.name + "s") layout = QtWidgets.QGridLayout(pickup_box) layout.setObjectName(f"{ammo.name} Box Layout") current_row = 0 def add_row(widget_a: QtWidgets.QWidget, widget_b: QtWidgets.QWidget): nonlocal current_row layout.addWidget(widget_a, current_row, 0) layout.addWidget(widget_b, current_row, 1) current_row += 1 for ammo_index, ammo_item in enumerate(ammo.items): item = resource_database.get_by_type_and_index(ResourceType.ITEM, ammo_item) item_count_label = QtWidgets.QLabel(pickup_box) item_count_label.setText(item.long_name if len(ammo.items) > 1 else "Contains") item_count_spinbox = ScrollProtectedSpinBox(pickup_box) item_count_spinbox.setMaximum(item.max_capacity) item_count_spinbox.valueChanged.connect(partial(self._on_update_ammo_pickup_item_count_spinbox, ammo, ammo_index)) self._ammo_item_count_spinboxes[ammo.name].append(item_count_spinbox) add_row(item_count_label, item_count_spinbox) # Pickup Count count_label = QtWidgets.QLabel(pickup_box) count_label.setText("Pickup Count") count_label.setToolTip("How many instances of this expansion should be placed.") pickup_spinbox = ScrollProtectedSpinBox(pickup_box) pickup_spinbox.setMaximum(999) pickup_spinbox.valueChanged.connect(partial(self._on_update_ammo_pickup_num_count_spinbox, ammo)) add_row(count_label, pickup_spinbox) # FIXME: hardcoded check to hide required mains for Prime 1 if ammo.temporary: require_major_item_check = QtWidgets.QCheckBox(pickup_box) require_major_item_check.setText("Requires the major item to work?") require_major_item_check.stateChanged.connect(partial(self._on_update_ammo_require_major_item, ammo)) if self.game == RandovaniaGame.METROID_PRIME: require_major_item_check.setVisible(False) layout.addWidget(require_major_item_check, current_row, 0, 1, 2) current_row += 1 else: require_major_item_check = None expected_count = QtWidgets.QLabel(pickup_box) expected_count.setWordWrap(True) expected_count.setText("<TODO>") layout.addWidget(expected_count, current_row, 0, 1, 2) current_row += 1 self._ammo_pickup_widgets[ammo] = AmmoPickupWidgets(pickup_spinbox, expected_count, pickup_box, require_major_item_check) category_layout.addWidget(pickup_box)
def prime1_resource_database() -> ResourceDatabase: return default_database.resource_database_for(RandovaniaGame.METROID_PRIME)
def _get_resource_database(description: LayoutDescription, player: int) -> ResourceDatabase: return default_database.resource_database_for( description.get_preset(player).game)
def _create_ammo_pickup_boxes(self, size_policy, item_database: ItemDatabase): """ Creates the GroupBox with SpinBoxes for selecting the pickup count of all the ammo :param item_database: :return: """ self._ammo_maximum_spinboxes = collections.defaultdict(list) self._ammo_pickup_widgets = {} resource_database = default_database.resource_database_for(self.game) broad_to_category = { ItemCategory.BEAM_RELATED: ItemCategory.BEAM, ItemCategory.MORPH_BALL_RELATED: ItemCategory.MORPH_BALL, ItemCategory.MISSILE_RELATED: ItemCategory.MISSILE, } for ammo in item_database.ammo.values(): category_box, category_layout, _ = self._boxes_for_category[ broad_to_category[ammo.broad_category]] pickup_box = QGroupBox(category_box) pickup_box.setSizePolicy(size_policy) pickup_box.setTitle(ammo.name + "s") layout = QGridLayout(pickup_box) layout.setObjectName(f"{ammo.name} Box Layout") current_row = 0 for ammo_item in ammo.items: item = resource_database.get_by_type_and_index( ResourceType.ITEM, ammo_item) target_count_label = QLabel(pickup_box) target_count_label.setText(f"{item.long_name} Target" if len( ammo.items) > 1 else "Target count") maximum_spinbox = QSpinBox(pickup_box) maximum_spinbox.setMaximum(ammo.maximum) maximum_spinbox.valueChanged.connect( partial(self._on_update_ammo_maximum_spinbox, ammo_item)) self._ammo_maximum_spinboxes[ammo_item].append(maximum_spinbox) layout.addWidget(target_count_label, current_row, 0) layout.addWidget(maximum_spinbox, current_row, 1) current_row += 1 count_label = QLabel(pickup_box) count_label.setText("Pickup Count") count_label.setToolTip( "How many instances of this expansion should be placed.") pickup_spinbox = QSpinBox(pickup_box) pickup_spinbox.setMaximum(AmmoState.maximum_pickup_count()) pickup_spinbox.valueChanged.connect( partial(self._on_update_ammo_pickup_spinbox, ammo)) layout.addWidget(count_label, current_row, 0) layout.addWidget(pickup_spinbox, current_row, 1) current_row += 1 if ammo.temporaries: require_major_item_check = QCheckBox(pickup_box) require_major_item_check.setText( "Requires the major item to work?") require_major_item_check.stateChanged.connect( partial(self._on_update_ammo_require_major_item, ammo)) layout.addWidget(require_major_item_check, current_row, 0, 1, 2) current_row += 1 else: require_major_item_check = None expected_count = QLabel(pickup_box) expected_count.setWordWrap(True) expected_count.setText(_EXPECTED_COUNT_TEXT_TEMPLATE) expected_count.setToolTip( "Some expansions may provide 1 extra, even with no variance, if the total count " "is not divisible by the pickup count.") layout.addWidget(expected_count, current_row, 0, 1, 2) current_row += 1 self._ammo_pickup_widgets[ammo] = AmmoPickupWidgets( pickup_spinbox, expected_count, pickup_box, require_major_item_check) category_layout.addWidget(pickup_box)
def echoes_resource_database() -> ResourceDatabase: return default_database.resource_database_for(RandovaniaGame.METROID_PRIME_ECHOES)
def _create_ammo_pickup_boxes(self, size_policy, item_database: ItemDatabase): """ Creates the GroupBox with SpinBoxes for selecting the pickup count of all the ammo :param item_database: :return: """ self._ammo_maximum_spinboxes = collections.defaultdict(list) self._ammo_pickup_widgets = {} resource_database = default_database.resource_database_for(self.game) for ammo in item_database.ammo.values(): title_layout = QHBoxLayout() title_layout.setObjectName(f"{ammo.name} Title Horizontal Layout") expand_ammo_button = QToolButton(self.ammo_box) expand_ammo_button.setGeometry(QRect(20, 30, 24, 21)) expand_ammo_button.setText("+") title_layout.addWidget(expand_ammo_button) category_label = QLabel(self.ammo_box) category_label.setSizePolicy(size_policy) category_label.setText(ammo.name + "s") title_layout.addWidget(category_label) pickup_box = QGroupBox(self.ammo_box) pickup_box.setSizePolicy(size_policy) layout = QGridLayout(pickup_box) layout.setObjectName(f"{ammo.name} Box Layout") current_row = 0 for ammo_item in ammo.items: item = resource_database.get_by_type_and_index( ResourceType.ITEM, ammo_item) target_count_label = QLabel(pickup_box) target_count_label.setText(f"{item.long_name} Target" if len( ammo.items) > 1 else "Target count") maximum_spinbox = QSpinBox(pickup_box) maximum_spinbox.setMaximum(ammo.maximum) maximum_spinbox.valueChanged.connect( partial(self._on_update_ammo_maximum_spinbox, ammo_item)) self._ammo_maximum_spinboxes[ammo_item].append(maximum_spinbox) layout.addWidget(target_count_label, current_row, 0) layout.addWidget(maximum_spinbox, current_row, 1) current_row += 1 count_label = QLabel(pickup_box) count_label.setText("Pickup Count") count_label.setToolTip( "How many instances of this expansion should be placed.") pickup_spinbox = QSpinBox(pickup_box) pickup_spinbox.setMaximum(AmmoState.maximum_pickup_count()) pickup_spinbox.valueChanged.connect( partial(self._on_update_ammo_pickup_spinbox, ammo)) layout.addWidget(count_label, current_row, 0) layout.addWidget(pickup_spinbox, current_row, 1) current_row += 1 if ammo.temporaries: require_major_item_check = QCheckBox(pickup_box) require_major_item_check.setText( "Requires the major item to work?") require_major_item_check.stateChanged.connect( partial(self._on_update_ammo_require_major_item, ammo)) layout.addWidget(require_major_item_check, current_row, 0, 1, 2) current_row += 1 else: require_major_item_check = None expected_count = QLabel(pickup_box) expected_count.setWordWrap(True) expected_count.setText(_EXPECTED_COUNT_TEXT_TEMPLATE) expected_count.setToolTip( "Some expansions may provide 1 extra, even with no variance, if the total count " "is not divisible by the pickup count.") layout.addWidget(expected_count, current_row, 0, 1, 2) current_row += 1 self._ammo_pickup_widgets[ammo] = (pickup_spinbox, expected_count, expand_ammo_button, category_label, pickup_box, require_major_item_check) expand_ammo_button.clicked.connect( partial(_toggle_box_visibility, expand_ammo_button, pickup_box)) pickup_box.setVisible(False) self.ammo_layout.addLayout(title_layout) self.ammo_layout.addWidget(pickup_box)