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 _create_categories_boxes(self, size_policy): self._boxes_for_category = {} current_row = 0 for major_item_category in iterate_enum(ItemCategory): if not major_item_category.is_major_category and major_item_category != ItemCategory.ENERGY_TANK: continue category_button = QToolButton(self.major_items_box) category_button.setGeometry(QRect(20, 30, 24, 21)) category_button.setText("+") category_label = QLabel(self.major_items_box) category_label.setSizePolicy(size_policy) category_label.setText(major_item_category.long_name) category_box = QGroupBox(self.major_items_box) 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.major_items_layout.addWidget(category_button, 2 * current_row + 1, 0, 1, 1) self.major_items_layout.addWidget(category_label, 2 * current_row + 1, 1, 1, 1) self.major_items_layout.addWidget(category_box, 2 * current_row + 2, 0, 1, 2) self._boxes_for_category[major_item_category] = category_box, category_layout, {} category_button.clicked.connect(partial(_toggle_box_visibility, category_button, category_box)) category_box.setVisible(False) current_row += 1
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")) configuration = { "discord_client_id": 618134325921316864, "server_address": "https://randovania.metroidprime.run/randovania", "socketio_path": "/randovania/socket.io", } with _ROOT_FOLDER.joinpath( "randovania", "data", "configuration.json").open("w") as config_release: json.dump(configuration, config_release) 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 __init__(self, editor: PresetEditor): super().__init__() self.setupUi(self) self._editor = editor randomizer_data = default_data.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 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 __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 _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 _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
def __init__(self): super().__init__() self.window = QMainWindow() self.setupUi(self.window) common_qt_lib.set_default_window_icon(self.window) for status in enum_lib.iterate_enum(ConnectionStatus): self.current_status_combo.addItem(status.pretty_text, status) self.permanent_pickups = [] self.pickups = [] self._inventory = {} 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)
def __init__(self): super().__init__() 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[1] self._game_memory = bytearray(24 * (2**20)) self._write_memory(self._expected_patches.build_string_address, self._expected_patches.build_string) # CPlayerState self._write_memory( self._expected_patches.string_display.cstate_manager_global + 0x150c, 0xA00000.to_bytes(4, "big"))
def setup_elevator_elements(self): for value in iterate_enum(LayoutElevators): self.elevators_combo.addItem(value.long_name, value) self.elevators_combo.options_field_name = "layout_configuration_elevators" self.elevators_combo.currentIndexChanged.connect(functools.partial(_update_options_by_value, self._editor, self.elevators_combo)) 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") )
def _create_open_map_tracker_actions(self): base_layout = self.preset_manager.default_preset.get_preset( ).layout_configuration for trick_level in iterate_enum(LayoutTrickLevel): if trick_level != LayoutTrickLevel.MINIMAL_LOGIC: action = QtWidgets.QAction(self) action.setText(trick_level.long_name) self.menu_map_tracker.addAction(action) configuration = dataclasses.replace( base_layout, trick_level_configuration=TrickLevelConfiguration( trick_level, {})) action.triggered.connect( partial(self.open_map_tracker, configuration))
def pretty_description(self) -> str: if self.minimal_logic: return LayoutTrickLevel.MINIMAL_LOGIC.long_name difficulties = collections.defaultdict(int) for trick in _all_tricks(): difficulties[self.level_for_trick(trick)] += 1 if len(difficulties) == 1: for level in difficulties.keys(): return 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 pretty_description(self) -> str: if self.minimal_logic: return "Minimal Logic" trick_list = _all_tricks(default_data.read_json_then_binary(self.game)[1]) difficulties = collections.defaultdict(int) for trick in trick_list: 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)) current_row = 0 for major_item_category in sorted( categories, key=lambda it: all_categories.index(it)): category_button = QToolButton(self.major_items_box) category_button.setGeometry(QRect(20, 30, 24, 21)) category_button.setText("+") category_label = QLabel(self.major_items_box) category_label.setSizePolicy(size_policy) category_label.setText(major_item_category.long_name) category_box = QGroupBox(self.major_items_box) 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.major_items_layout.addWidget(category_button, 2 * current_row + 1, 0, 1, 1) self.major_items_layout.addWidget(category_label, 2 * current_row + 1, 1, 1, 1) self.major_items_layout.addWidget(category_box, 2 * current_row + 2, 0, 1, 2) self._boxes_for_category[ major_item_category] = category_box, category_layout, {} category_button.clicked.connect( partial(_toggle_box_visibility, category_button, category_box)) category_box.setVisible(False) current_row += 1
def _setup_difficulties_menu(self): game = default_database.default_prime2_game_description() tricks_in_use = used_tricks(game) for trick in sorted(game.resource_database.trick, key=lambda _trick: _trick.long_name): if trick not in tricks_in_use: continue trick_menu = QMenu(self) trick_menu.setTitle(trick.long_name) self.menu_trick_details.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 = 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, trick, trick_level))
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, {}
@pytest.fixture(name="pickup") def _pickup(echoes_game_description) -> PickupEntry: resource = echoes_game_description.resource_database.energy_tank return PickupEntry( name="Pickup", model_index=0, item_category=ItemCategory.MOVEMENT, broad_category=ItemCategory.LIFE_SUPPORT, resources=(ConditionalResources(None, None, ((resource, 2), )), ), ) @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.NO_TRICKS 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 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
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.world = game.world_list.nodes_to_world(node) world = self.world self._type_to_tab = { GenericNode: self.tab_generic, DockNode: self.tab_dock, PickupNode: self.tab_pickup, TeleporterNode: self.tab_teleporter, EventNode: self.tab_event, TranslatorGateNode: self.tab_translator_gate, LogbookNode: self.tab_logbook, PlayerShipNode: self.tab_player_ship, } tab_to_type = { tab: node_type for node_type, tab in self._type_to_tab.items() } # Dynamic Stuff for i, node_type in enumerate(self._type_to_tab.keys()): self.node_type_combo.setItemData(i, node_type) for area in world.areas: self.dock_connection_area_combo.addItem(area.name, area) refresh_if_needed(self.dock_connection_area_combo, self.on_dock_connection_area_combo) for i, enum in enumerate(enum_lib.iterate_enum(DockType)): self.dock_type_combo.setItemData(i, enum) for world in sorted(game.world_list.worlds, key=lambda x: x.name): self.teleporter_destination_world_combo.addItem( "{0.name} ({0.dark_name})".format(world), userData=world) refresh_if_needed(self.teleporter_destination_world_combo, self.on_teleporter_destination_world_combo) for event in sorted(game.resource_database.event, key=lambda it: it.long_name): self.event_resource_combo.addItem(event.long_name, event) if self.event_resource_combo.count() == 0: self.event_resource_combo.addItem("No events in database", None) self.event_resource_combo.setEnabled(False) for i, enum in enumerate(enum_lib.iterate_enum(LoreType)): self.lore_type_combo.setItemData(i, enum) refresh_if_needed(self.lore_type_combo, self.on_lore_type_combo) self.set_unlocked_by(Requirement.trivial()) # Signals self.button_box.accepted.connect(self.try_accept) self.button_box.rejected.connect(self.reject) self.node_type_combo.currentIndexChanged.connect( self.on_node_type_combo) self.dock_connection_area_combo.currentIndexChanged.connect( self.on_dock_connection_area_combo) self.dock_connection_node_combo.currentIndexChanged.connect( self.on_dock_connection_node_combo) self.dock_type_combo.currentIndexChanged.connect( self.on_dock_type_combo) self.teleporter_destination_world_combo.currentIndexChanged.connect( self.on_teleporter_destination_world_combo) self.lore_type_combo.currentIndexChanged.connect( self.on_lore_type_combo) self.player_ship_unlocked_button.clicked.connect( self.on_player_ship_unlocked_button) # Hide the tab bar tab_bar: QtWidgets.QTabBar = self.tab_widget.findChild( QtWidgets.QTabBar) tab_bar.hide() # Values self.name_edit.setText(node.name) self.heals_check.setChecked(node.heal) self.location_group.setChecked(node.location is not None) if node.location is not None: self.location_x_spin.setValue(node.location.x) self.location_y_spin.setValue(node.location.y) self.location_z_spin.setValue(node.location.z) visible_tab = self._fill_for_type(node) self.node_type_combo.setCurrentIndex( self.node_type_combo.findData(tab_to_type[visible_tab])) refresh_if_needed(self.node_type_combo, self.on_node_type_combo)