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 _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(_t(trick.long_name)) menu.addAction(trick_menu.menuAction()) used_difficulties = difficulties_for_trick(game, trick) for trick_level in enum_lib.iterate_enum(LayoutTrickLevel): if trick_level in used_difficulties: difficulty_action = QtGui.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 create_subparsers(sub_parsers): parser: ArgumentParser = sub_parsers.add_parser( "database", help="Actions for database manipulation") group = parser.add_mutually_exclusive_group() group.add_argument( "--game", type=str, choices=[game.value for game in iterate_enum(RandovaniaGame)], default=RandovaniaGame.PRIME2.value, help="Use the included database for the given game.", ) group.add_argument( "--json-database", type=Path, help="Path to the JSON encoded database.", ) sub_parsers = parser.add_subparsers(dest="database_command") create_convert_database_command(sub_parsers) view_area_command(sub_parsers) export_areas_command(sub_parsers) list_paths_with_dangerous_command(sub_parsers) list_paths_with_resource_command(sub_parsers) pickups_per_area_command(sub_parsers) def check_command(args): if args.database_command is None: parser.print_help() raise SystemExit(1) parser.set_defaults(func=check_command)
def write_human_readable_world_list(game: GameDescription, output: TextIO) -> None: def print_to_file(*args): output.write("\t".join(str(arg) for arg in args) + "\n") output.write("====================\nTemplates\n") for template_name, template in game.resource_database.requirement_template.items( ): output.write(f"\n* {template_name}:\n") for level, text in pretty_print_requirement(template): output.write(" {}{}\n".format(" " * level, text)) output.write("\n====================\nDock Weaknesses\n") for dock_type in iterate_enum(DockType): output.write(f"\n> {dock_type}") for weakness in game.dock_weakness_database.get_by_type(dock_type): output.write( f"\n * ({weakness.index}) {weakness.name}; Blast Shield? {weakness.is_blast_shield}\n" ) for level, text in pretty_print_requirement(weakness.requirement): output.write(" {}{}\n".format(" " * level, text)) output.write("\n") for world in game.world_list.worlds: output.write("====================\n{}\n".format(world.name)) for area in world.areas: output.write("----------------\n") pretty_print_area(game, area, print_function=print_to_file)
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 __init__(self, editor: PresetEditor): super().__init__(editor) self.setupUi(self) self.translators_layout.setAlignment(QtCore.Qt.AlignTop) self.translator_randomize_all_button.clicked.connect( self._on_randomize_all_gates_pressed) self.translator_randomize_all_with_unlocked_button.clicked.connect( self._on_randomize_all_gates_with_unlocked_pressed) self.translator_vanilla_actual_button.clicked.connect( self._on_vanilla_actual_gates_pressed) self.translator_vanilla_colors_button.clicked.connect( self._on_vanilla_colors_gates_pressed) self._combo_for_gate = {} gate_index_to_name, identifier_to_gate = gate_data() for i, (identifier, gate_index) in enumerate( sorted(identifier_to_gate.items(), key=lambda it: it[1])): label = QtWidgets.QLabel(self.translators_scroll_contents) label.setText(gate_index_to_name[gate_index]) self.translators_layout.addWidget(label, 3 + i, 0, 1, 1) combo = QComboBox(self.translators_scroll_contents) combo.identifier = identifier for item in iterate_enum(LayoutTranslatorRequirement): combo.addItem(item.long_name, item) combo.currentIndexChanged.connect( functools.partial(self._on_gate_combo_box_changed, combo)) self.translators_layout.addWidget(combo, 3 + i, 1, 1, 2) self._combo_for_gate[combo.identifier] = combo
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) for status in enum_lib.iterate_enum(GameConnectionStatus): self.current_status_combo.addItem(status.pretty_text, status) self.permanent_pickups = [] 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, 1, 0, 1, 1) self.collect_location_button.clicked.connect(self._emit_collection) self.collect_location_button.setEnabled(False) self._expected_patches = dol_patcher.ALL_VERSIONS_PATCHES[0] # FIXME: use PAL again self.patches = self._expected_patches self._game_memory = bytearray(24 * (2**20)) self._game_memory_initialized = False self.patches = None
def _create_resource_type_combo( current_resource_type: ResourceType, parent: QWidget, resource_database: ResourceDatabase) -> QComboBox: """ :param current_resource_type: :param parent: :return: """ resource_type_combo = QComboBox(parent) for resource_type in iterate_enum(ResourceType): try: count_elements = len(resource_database.get_by_type(resource_type)) except ValueError: count_elements = 0 if count_elements == 0: continue resource_type_combo.addItem(resource_type.name, resource_type) if resource_type is current_resource_type: resource_type_combo.setCurrentIndex(resource_type_combo.count() - 1) return resource_type_combo
async def add_commands(self, slash: SlashCommand): base_command = self.configuration.get("command_prefix", "") + "randovania-faq" for game in enum_lib.iterate_enum(RandovaniaGame): faq_entries = list(game.data.faq) if not faq_entries: continue def _shorten(n: str) -> str: if len(n) > 100: return n[:97] + "..." return n slash.add_subcommand( functools.partial(self.faq_game_command, game=game), base_command, name=game.value, description=f"Prints the answer to a FAQ for {game.long_name}.", options=[ manage_commands.create_option( "question", "Which question to answer?", option_type=SlashCommandOptionType.STRING, required=True, choices=[ manage_commands.create_choice( f"question_{question_id}", _shorten(question)) for question_id, (question, answer) in enumerate(faq_entries) ]) ], )
def __init__(self, editor: PresetEditor): super().__init__() self.setupUi(self) self._editor = editor randomizer_data = randovania.games.patchers.claris_patcher.decode_randomizer_data( ) self.translators_layout.setAlignment(QtCore.Qt.AlignTop) self.translator_randomize_all_button.clicked.connect( self._on_randomize_all_gates_pressed) self.translator_vanilla_actual_button.clicked.connect( self._on_vanilla_actual_gates_pressed) self.translator_vanilla_colors_button.clicked.connect( self._on_vanilla_colors_gates_pressed) self._combo_for_gate = {} for i, gate in enumerate(randomizer_data["TranslatorLocationData"]): label = QtWidgets.QLabel(self.translators_scroll_contents) label.setText(gate["Name"]) self.translators_layout.addWidget(label, 3 + i, 0, 1, 1) combo = QComboBox(self.translators_scroll_contents) combo.gate = TranslatorGate(gate["Index"]) for item in iterate_enum(LayoutTranslatorRequirement): combo.addItem(item.long_name, item) combo.currentIndexChanged.connect( functools.partial(self._on_gate_combo_box_changed, combo)) self.translators_layout.addWidget(combo, 3 + i, 1, 1, 2) self._combo_for_gate[combo.gate] = combo
def read_preset_list() -> List[Path]: preset_list = [] for game in enum_lib.iterate_enum(RandovaniaGame): base_path = game.data_path.joinpath("presets") preset_list.extend([ base_path.joinpath(preset["path"]) for preset in game.data.presets ]) return preset_list
def refresh_presets_command_logic(args): for game in enum_lib.iterate_enum(RandovaniaGame): logging.info(f"Refreshing presets for {game.long_name}") base_path = game.data_path.joinpath("presets") for preset_relative_path in game.data.presets: preset_path = base_path.joinpath(preset_relative_path["path"]) preset = VersionedPreset.from_file_sync(preset_path) preset.ensure_converted() preset.save_to_file(preset_path)
async def main(): package_folder = Path("dist", "randovania") if package_folder.exists(): shutil.rmtree(package_folder, ignore_errors=False) app_folder = Path("dist", "Randovania.app") if app_folder.exists(): shutil.rmtree(app_folder, ignore_errors=False) for game in iterate_enum(RandovaniaGame): database.export_as_binary( default_data.read_json_then_binary(game)[1], _ROOT_FOLDER.joinpath("randovania", "data", "binary_data", f"{game.value}.bin")) if is_production(): server_suffix = "randovania" client_id = 618134325921316864 else: server_suffix = "randovania-staging" client_id = 887825192208969828 configuration = { "discord_client_id": client_id, "server_address": f"https://randovania.metroidprime.run/{server_suffix}", "socketio_path": f"/{server_suffix}/socket.io", } with _ROOT_FOLDER.joinpath( "randovania", "data", "configuration.json").open("w") as config_release: json.dump(configuration, config_release) await download_nintendont() # HACK: pyintaller calls lipo/codesign on macOS and frequently timeout in github actions # There's also timeouts on Windows so we're expanding this to everyone print("Will patch timeout in PyInstaller compat") import PyInstaller.compat compat_path = Path(PyInstaller.compat.__file__) compat_text = compat_path.read_text().replace("timeout=60", "timeout=180") compat_path.write_text(compat_text) subprocess.run([sys.executable, "-m", "PyInstaller", "randovania.spec"], check=True) if platform.system() == "Windows": create_windows_zip(package_folder) elif platform.system() == "Darwin": create_macos_zip(app_folder) elif platform.system() == "Linux": create_linux_zip(package_folder) else: raise ValueError(f"Unknown system: {platform.system()}")
def __init__(self, editor: PresetEditor, game: GameDescription): super().__init__(editor) self.setupUi(self) self.game_description = game self.elevator_layout.setAlignment(QtCore.Qt.AlignTop) for value in enum_lib.iterate_enum(TeleporterShuffleMode): self.elevators_combo.addItem(value.long_name, value) self.elevators_combo.currentIndexChanged.connect( self._update_elevator_mode) signal_handling.on_checked(self.skip_final_bosses_check, self._update_require_final_bosses) signal_handling.on_checked(self.elevators_allow_unvisited_names_check, self._update_allow_unvisited_names) # Elevator Source self._create_source_elevators() # Elevator Target self._elevator_target_for_world, self._elevator_target_for_area = self.create_area_list_selection( self.elevators_target_group, self.elevators_target_layout, TeleporterTargetList.areas_list(self.game_enum), self._on_elevator_target_check_changed, ) if self.game_enum != RandovaniaGame.METROID_PRIME_ECHOES: self.elevators_help_sound_bug_label.setVisible(False) self.elevators_allow_unvisited_names_check.setVisible(False) self.elevators_line_3.setVisible(False) self.elevators_help_list_label.setVisible(False) if self.game_enum == RandovaniaGame.METROID_PRIME: self.skip_final_bosses_check.setText( "Go directly to credits from Artifact Temple") self.skip_final_bosses_label.setText("""<html><head/><body> <p>Change the teleport in Artifact Temple to go directly to the credits, skipping the final bosses.</p> <p>This changes the requirements to <span style=" font-weight:600;">not need the final bosses</span>, turning certain items optional such as Plasma Beam.</p></body></html> """) elif self.game_enum != RandovaniaGame.METROID_PRIME_ECHOES: self.skip_final_bosses_check.setVisible(False) self.skip_final_bosses_label.setVisible(False) elif self.game_enum == RandovaniaGame.METROID_PRIME_CORRUPTION: self.elevators_description_label.setText( self.elevators_description_label.text().replace( "elevator", "teleporter"))
def __init__( self, parent: QWidget, layout: QHBoxLayout, resource_database: ResourceDatabase, item: ResourceRequirement, ): self.parent = parent self.layout = layout self.resource_database = resource_database self.resource_type_combo = _create_resource_type_combo( item.resource.resource_type, parent, resource_database) self.resource_type_combo.setMinimumWidth(75) self.resource_type_combo.setMaximumWidth(75) self.resource_name_combo = _create_resource_name_combo( self.resource_database, item.resource.resource_type, item.resource, self.parent) self.negate_combo = ScrollProtectedComboBox(parent) self.negate_combo.addItem("≥", False) self.negate_combo.addItem("<", True) self.negate_combo.setCurrentIndex(int(item.negate)) self.negate_combo.setMinimumWidth(40) self.negate_combo.setMaximumWidth(40) self.negate_check = QtWidgets.QCheckBox(parent) self.negate_check.setChecked(item.negate) self.amount_edit = QLineEdit(parent) self.amount_edit.setValidator(QIntValidator(1, 10000)) self.amount_edit.setText(str(item.amount)) self.amount_edit.setMinimumWidth(45) self.amount_edit.setMaximumWidth(45) self.amount_combo = ScrollProtectedComboBox(parent) for trick_level in iterate_enum(LayoutTrickLevel): self.amount_combo.addItem(trick_level.long_name, userData=trick_level.as_number) self.amount_combo.setCurrentIndex( self.amount_combo.findData(item.amount)) for widget in self._all_widgets: self.layout.addWidget(widget) self.resource_type_combo.currentIndexChanged.connect(self._update_type) self._update_visible_elements_by_type()
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_categories_boxes(self, item_database: ItemDatabase, size_policy): self._boxes_for_category = {} categories = set() for major_item in item_database.major_items.values(): if not major_item.required: categories.add(major_item.item_category) all_categories = list(iterate_enum(ItemCategory)) for major_item_category in sorted(categories, key=lambda it: all_categories.index(it)): category_box = QGroupBox(self.scroll_area_contents) category_box.setTitle(major_item_category.long_name) category_box.setSizePolicy(size_policy) category_box.setObjectName(f"category_box {major_item_category}") category_layout = QGridLayout(category_box) category_layout.setObjectName(f"category_layout {major_item_category}") self.item_pool_layout.addWidget(category_box) self._boxes_for_category[major_item_category] = category_box, category_layout, {}
def refresh_all_logic(args): from randovania.game_description import pretty_print from randovania.game_description import data_reader, data_writer from randovania.game_description import integrity_check gd_per_game = {} path_per_game = {} idb_per_game = {} for game in iterate_enum(RandovaniaGame): logging.info("Reading %s", game.long_name) path, data = default_data.read_json_then_binary(game) path_per_game[game] = path gd = data_reader.decode_data(data) gd_per_game[game] = gd idb = default_database.item_database_for_game(game) idb_per_game[game] = idb should_stop = False if args.integrity_check: for game, gd in gd_per_game.items(): errors = integrity_check.find_database_errors(gd) if errors: logging.warning("Integrity errors for %s:\n%s", game.long_name, "\n".join(errors)) if game.data.development_state.is_stable: should_stop = True if should_stop: return for game, gd in gd_per_game.items(): path = path_per_game[game] logging.info("Writing %s", game.long_name) new_data = data_writer.write_game_description(gd) data_writer.write_as_split_files(new_data, path) path.with_suffix("").mkdir(parents=True, exist_ok=True) pretty_print.write_human_readable_game(gd, path.with_suffix("")) default_database.write_item_database_for_game(idb_per_game[game], game)
def __init__(self, parent: QWidget, item: MajorItem, starting_state: MajorItemState, resources_database: ResourceDatabase): super().__init__(parent) self.setupUi(self) self._item = item self.item_name_label.setText(item.name) # Apply transparency on the separator line transparent = QGraphicsOpacityEffect(self.separator_line) transparent.setOpacity(0.33) self.separator_line.setGraphicsEffect(transparent) self.separator_line.hide() for case in enum_lib.iterate_enum(MajorItemStateCase): if case == MajorItemStateCase.VANILLA and item.original_index is None: continue if case == MajorItemStateCase.STARTING_ITEM and len( item.progression) > 1: continue
async def assign_other_hints(self, patches: GamePatches, identifiers: list[NodeIdentifier], prefill: PreFillParams) -> GamePatches: all_hint_identifiers = [ identifier for identifier in identifiers if identifier not in patches.hints ] prefill.rng.shuffle(all_hint_identifiers) # Dark Temple hints temple_hints = list(enum_lib.iterate_enum(HintDarkTemple)) while all_hint_identifiers and temple_hints: identifier = all_hint_identifiers.pop() patches = patches.assign_hint( identifier, Hint(HintType.RED_TEMPLE_KEY_SET, None, dark_temple=temple_hints.pop(0))) identifiers.remove(identifier) return patches
async def main(): package_folder = Path("dist", "randovania") if package_folder.exists(): shutil.rmtree(package_folder, ignore_errors=False) app_folder = Path("dist", "Randovania.app") if app_folder.exists(): shutil.rmtree(app_folder, ignore_errors=False) for game in iterate_enum(RandovaniaGame): prime_database.export_as_binary( default_data.read_json_then_binary(game)[1], _ROOT_FOLDER.joinpath("randovania", "data", "binary_data", f"{game.value}.bin")) if is_production(): server_suffix = "randovania" else: server_suffix = "randovania-staging" configuration = { "discord_client_id": 618134325921316864, "server_address": f"https://randovania.metroidprime.run/{server_suffix}", "socketio_path": f"/{server_suffix}/socket.io", } with _ROOT_FOLDER.joinpath( "randovania", "data", "configuration.json").open("w") as config_release: json.dump(configuration, config_release) await download_nintendont() subprocess.run([sys.executable, "-m", "PyInstaller", "randovania.spec"], check=True) if platform.system() == "Windows": create_windows_zip(package_folder) elif platform.system() == "Darwin": create_macos_zip(app_folder)
def update_items(self): self.clear() tree_item = {} for game in enum_lib.iterate_enum(RandovaniaGame): root = QtWidgets.QTreeWidgetItem(self) root.setText(0, game.long_name) root.setExpanded(True) tree_item[game] = root self.preset_to_item = {} # Included presets for preset in self.window_manager.preset_manager.included_presets.values( ): item = QtWidgets.QTreeWidgetItem(tree_item[preset.game]) item.setText(0, preset.name) item.setExpanded(True) item.setData(0, Qt.UserRole, preset.uuid) self.preset_to_item[preset.uuid] = item # Custom Presets for preset in self.window_manager.preset_manager.custom_presets.values( ): item = QtWidgets.QTreeWidgetItem(tree_item[preset.game]) item.setText(0, preset.name) item.setData(0, Qt.UserRole, preset.uuid) self.preset_to_item[preset.uuid] = item # Set parents after, so don't have issues with order for preset in sorted( self.window_manager.preset_manager.custom_presets.values(), key=lambda it: it.name): if preset.base_preset_uuid in self.preset_to_item: tree_item[preset.game].removeChild( self.preset_to_item[preset.uuid]) self.preset_to_item[preset.base_preset_uuid].addChild( self.preset_to_item[preset.uuid])
def setup_elevator_elements(self): for value in iterate_enum(TeleporterShuffleMode): self.elevators_combo.addItem(value.long_name, value) self.elevators_combo.currentIndexChanged.connect(self._update_elevator_mode) signal_handling.on_checked(self.skip_final_bosses_check, self._update_require_final_bosses) signal_handling.on_checked(self.elevators_allow_unvisited_names_check, self._update_allow_unvisited_names) # Elevator Source self._create_source_elevators() # Elevator Target self._elevator_target_for_world, self._elevator_target_for_area = self._create_area_list_selection( self.elevators_target_group, self.elevators_target_layout, TeleporterTargetList.areas_list(self.game_enum), self._on_elevator_target_check_changed, ) if self.game_enum == RandovaniaGame.PRIME3: self.patches_tab_widget.setTabText(self.patches_tab_widget.indexOf(self.elevator_tab), "Teleporters") self.elevators_description_label.setText( self.elevators_description_label.text().replace("elevator", "teleporter") )
import pytest from PySide2.QtCore import Qt from mock import patch, MagicMock, AsyncMock from randovania.game_connection.connection_base import GameConnectionStatus, InventoryItem from randovania.games.prime import dol_patcher from randovania.gui.debug_backend_window import DebugBackendWindow from randovania.lib.enum_lib import iterate_enum @pytest.fixture(name="backend") def debug_backend_window(skip_qtbot): return DebugBackendWindow() @pytest.mark.parametrize("expected_status", iterate_enum(GameConnectionStatus)) def test_current_status(backend, expected_status): all_status = list(iterate_enum(GameConnectionStatus)) backend.current_status_combo.setCurrentIndex( all_status.index(expected_status)) assert backend.current_status == expected_status @pytest.mark.asyncio async def test_display_message(backend): backend.patches = dol_patcher.ALL_VERSIONS_PATCHES[0] message = "Foo" await backend._perform_single_memory_operations( backend._write_string_to_game_buffer(message))
def setup_trick_level_elements(self): self.trick_level_minimal_logic_check.stateChanged.connect(self._on_trick_level_minimal_logic_check) self.trick_difficulties_layout = QtWidgets.QGridLayout() self._slider_for_trick = {} tricks_in_use = used_tricks(self.game_description) size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) self._create_difficulty_details_row() row = 2 for trick in sorted(self.resource_database.trick, key=lambda _trick: _trick.long_name): if trick not in tricks_in_use: continue if row > 1: self.trick_difficulties_layout.addItem(QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)) trick_label = QtWidgets.QLabel(self.trick_level_scroll_contents) trick_label.setSizePolicy(size_policy) trick_label.setWordWrap(True) trick_label.setFixedWidth(100) trick_label.setText(trick.long_name) self.trick_difficulties_layout.addWidget(trick_label, row, 1, 1, 1) slider_layout = QtWidgets.QGridLayout() slider_layout.setHorizontalSpacing(0) for i in range(12): slider_layout.setColumnStretch(i, 1) horizontal_slider = QtWidgets.QSlider(self.trick_level_scroll_contents) horizontal_slider.setMaximum(5) horizontal_slider.setPageStep(2) horizontal_slider.setOrientation(QtCore.Qt.Horizontal) horizontal_slider.setTickPosition(QtWidgets.QSlider.TicksBelow) horizontal_slider.setEnabled(False) horizontal_slider.valueChanged.connect(functools.partial(self._on_slide_trick_slider, trick)) self._slider_for_trick[trick] = horizontal_slider slider_layout.addWidget(horizontal_slider, 0, 1, 1, 10) used_difficulties = difficulties_for_trick(self.game_description, trick) for i, trick_level in enumerate(iterate_enum(LayoutTrickLevel)): if trick_level == LayoutTrickLevel.DISABLED or trick_level in used_difficulties: difficulty_label = QtWidgets.QLabel(self.trick_level_scroll_contents) difficulty_label.setAlignment(QtCore.Qt.AlignHCenter) difficulty_label.setText(trick_level.long_name) slider_layout.addWidget(difficulty_label, 1, 2 * i, 1, 2) self.trick_difficulties_layout.addLayout(slider_layout, row, 2, 1, 1) if self._window_manager is not None: tool_button = QtWidgets.QToolButton(self.trick_level_scroll_contents) tool_button.setText("?") tool_button.clicked.connect(functools.partial(self._open_trick_details_popup, trick)) self.trick_difficulties_layout.addWidget(tool_button, row, 3, 1, 1) row += 1 self.trick_level_layout.addLayout(self.trick_difficulties_layout)
def __init__(self, parent: QtWidgets.QWidget, game: GameDescription): super().__init__(parent) set_default_window_icon(self) self.setWindowTitle("Layers") self.root_widget = QtWidgets.QScrollArea() self.root_layout = QtWidgets.QVBoxLayout(self.root_widget) self.root_widget.setWidgetResizable(True) self.setWidget(self.root_widget) self.contents_widget = QtWidgets.QWidget() self.contents_layout = QtWidgets.QVBoxLayout(self.contents_widget) self.root_widget.setWidget(self.contents_widget) self.title_label = QtWidgets.QLabel(self.contents_widget) self.title_label.setText("Select visible layers") self.contents_layout.addWidget(self.title_label) self.layer_checks = [] for layer in game.layers: self.layer_checks.append( layer_check := QtWidgets.QCheckBox(self.contents_widget)) layer_check.setText(layer) layer_check.setChecked(True) signal_handling.on_checked(layer_check, lambda it: self._notify_change()) self.contents_layout.addWidget(layer_check) self.add_layer_button = QtWidgets.QPushButton(self.contents_widget) self.add_layer_button.setText("Add new layer") self.add_layer_button.setEnabled(False) self.add_layer_button.setToolTip("Not implemented") self.contents_layout.addWidget(self.add_layer_button) self.tricks_box = QtWidgets.QGroupBox(self.contents_widget) self.tricks_box.setTitle("Simplify connections with:") self.contents_layout.addWidget(self.tricks_box) self.tricks_layout = QtWidgets.QVBoxLayout(self.tricks_box) self.tricks = {} for trick in sorted(game.resource_database.trick, key=lambda it: it.long_name): trick_layout = QtWidgets.QHBoxLayout() self.tricks_layout.addLayout(trick_layout) trick_check = QtWidgets.QCheckBox(self.tricks_box) trick_check.setText(trick.long_name) trick_layout.addWidget(trick_check) trick_combo = ScrollProtectedComboBox(self.tricks_box) trick_layout.addWidget(trick_combo) for trick_level in enum_lib.iterate_enum(LayoutTrickLevel): trick_combo.addItem(trick_level.long_name, userData=trick_level.as_number) signal_handling.on_combo(trick_combo, lambda it: self._notify_change()) trick_combo.setEnabled(False) signal_handling.on_checked(trick_check, trick_combo.setEnabled) signal_handling.on_checked(trick_check, lambda it: self._notify_change()) self.tricks[(trick, trick_check)] = trick_combo self.load_preset_button = QtWidgets.QPushButton(self.contents_widget) self.load_preset_button.setText("Configure with preset") self.load_preset_button.clicked.connect(self._on_load_preset_slot) self.contents_layout.addWidget(self.load_preset_button) self.vertical_spacer = QtWidgets.QSpacerItem( 20, 30, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.contents_layout.addItem(self.vertical_spacer)
def case(self) -> MajorItemStateCase: for case in enum_lib.iterate_enum(MajorItemStateCase): if self == MajorItemState.from_case(case, self.included_ammo): return case
def add_echoes_default_hints_to_patches(rng: Random, patches: GamePatches, world_list: WorldList, num_joke: int, is_multiworld: bool, ) -> GamePatches: """ Adds hints that are present on all games. :param rng: :param patches: :param world_list: :param num_joke :param is_multiworld :return: """ for node in world_list.all_nodes: if isinstance(node, LogbookNode) and node.lore_type == LoreType.LUMINOTH_WARRIOR: patches = patches.assign_hint(node.resource(), Hint(HintType.LOCATION, PrecisionPair(HintLocationPrecision.KEYBEARER, HintItemPrecision.BROAD_CATEGORY, include_owner=True), PickupIndex(node.hint_index))) all_logbook_assets = [node.resource() for node in world_list.all_nodes if isinstance(node, LogbookNode) and node.resource() not in patches.hints and node.lore_type.holds_generic_hint] rng.shuffle(all_logbook_assets) # The 4 guaranteed hints indices_with_hint = [ (PickupIndex(24), HintLocationPrecision.LIGHT_SUIT_LOCATION), # Light Suit (PickupIndex(43), HintLocationPrecision.GUARDIAN), # Dark Suit (Amorbis) (PickupIndex(79), HintLocationPrecision.GUARDIAN), # Dark Visor (Chykka) (PickupIndex(115), HintLocationPrecision.GUARDIAN), # Annihilator Beam (Quadraxis) ] rng.shuffle(indices_with_hint) for index, location_type in indices_with_hint: if not all_logbook_assets: break logbook_asset = all_logbook_assets.pop() patches = patches.assign_hint(logbook_asset, Hint(HintType.LOCATION, PrecisionPair(location_type, HintItemPrecision.DETAILED, include_owner=False), index)) # Dark Temple hints temple_hints = list(iterate_enum(HintDarkTemple)) while all_logbook_assets and temple_hints: logbook_asset = all_logbook_assets.pop() patches = patches.assign_hint(logbook_asset, Hint(HintType.RED_TEMPLE_KEY_SET, None, dark_temple=temple_hints.pop(0))) # Jokes while num_joke > 0 and all_logbook_assets: logbook_asset = all_logbook_assets.pop() patches = patches.assign_hint(logbook_asset, Hint(HintType.JOKE, None)) num_joke -= 1 return patches
def __init__(self, game: GameDescription, node: Node): super().__init__() self.setupUi(self) common_qt_lib.set_default_window_icon(self) self.game = game self.node = node self._type_to_tab = { GenericNode: self.tab_generic, DockNode: self.tab_dock, PickupNode: self.tab_pickup, TeleporterNode: self.tab_teleporter, EventNode: self.tab_event, ConfigurableNode: self.tab_translator_gate, LogbookNode: self.tab_logbook, PlayerShipNode: self.tab_player_ship, } tab_to_type = {tab: node_type for node_type, tab in self._type_to_tab.items()} # Dynamic Stuff for i, node_type in enumerate(self._type_to_tab.keys()): self.node_type_combo.setItemData(i, node_type) self.layers_combo.clear() for layer in game.layers: self.layers_combo.addItem(layer) self.dock_type_combo.clear() for i, dock_type in enumerate(game.dock_weakness_database.dock_types): self.dock_type_combo.addItem(dock_type.long_name, userData=dock_type) refresh_if_needed(self.dock_type_combo, self.on_dock_type_combo) for world in sorted(game.world_list.worlds, key=lambda x: x.name): self.dock_connection_world_combo.addItem(world.name, userData=world) self.teleporter_destination_world_combo.addItem(world.name, userData=world) refresh_if_needed(self.teleporter_destination_world_combo, self.on_dock_connection_world_combo) refresh_if_needed(self.teleporter_destination_world_combo, self.on_teleporter_destination_world_combo) for event in sorted(game.resource_database.event, key=lambda it: it.long_name): self.event_resource_combo.addItem(event.long_name, event) if self.event_resource_combo.count() == 0: self.event_resource_combo.addItem("No events in database", None) self.event_resource_combo.setEnabled(False) for i, dock_type in enumerate(enum_lib.iterate_enum(LoreType)): self.lore_type_combo.setItemData(i, dock_type) refresh_if_needed(self.lore_type_combo, self.on_lore_type_combo) self.set_unlocked_by(Requirement.trivial()) # Signals self.button_box.accepted.connect(self.try_accept) self.button_box.rejected.connect(self.reject) self.name_edit.textEdited.connect(self.on_name_edit) self.node_type_combo.currentIndexChanged.connect(self.on_node_type_combo) self.dock_connection_world_combo.currentIndexChanged.connect(self.on_dock_connection_world_combo) self.dock_connection_area_combo.currentIndexChanged.connect(self.on_dock_connection_area_combo) self.dock_type_combo.currentIndexChanged.connect(self.on_dock_type_combo) self.dock_update_name_button.clicked.connect(self.on_dock_update_name_button) self.teleporter_destination_world_combo.currentIndexChanged.connect(self.on_teleporter_destination_world_combo) self.lore_type_combo.currentIndexChanged.connect(self.on_lore_type_combo) self.player_ship_unlocked_button.clicked.connect(self.on_player_ship_unlocked_button) # Hide the tab bar tab_bar: QtWidgets.QTabBar = self.tab_widget.findChild(QtWidgets.QTabBar) tab_bar.hide() # Values self.name_edit.setText(node.name) self.heals_check.setChecked(node.heal) self.location_group.setChecked(node.location is not None) if node.location is not None: self.location_x_spin.setValue(node.location.x) self.location_y_spin.setValue(node.location.y) self.location_z_spin.setValue(node.location.z) self.description_edit.setMarkdown(node.description) self.extra_edit.setPlainText(json.dumps(frozen_lib.unwrap(node.extra), indent=4)) try: visible_tab = self._fill_for_type(node) self.node_type_combo.setCurrentIndex(self.node_type_combo.findData(tab_to_type[visible_tab])) refresh_if_needed(self.node_type_combo, self.on_node_type_combo) except Exception: pass self.on_name_edit(self.name_edit.text())
def test_current_status(backend, expected_status): all_status = list(iterate_enum(GameConnectionStatus)) backend.current_status_combo.setCurrentIndex( all_status.index(expected_status)) assert backend.current_status == expected_status