def import_preset_file(self, path: Path): preset = VersionedPreset.from_file_sync(path) try: preset.get_preset() except InvalidPreset: QtWidgets.QMessageBox.critical( self._window_manager, "Error loading preset", "The file at '{}' contains an invalid preset.".format(path)) return existing_preset = self._window_manager.preset_manager.preset_for_uuid( preset.uuid) if existing_preset is not None: user_response = QtWidgets.QMessageBox.warning( self._window_manager, "Preset ID conflict", "The new preset '{}' has the same ID as existing '{}'. Do you want to overwrite it?" .format( preset.name, existing_preset.name, ), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel) if user_response == QtWidgets.QMessageBox.Cancel: return elif user_response == QtWidgets.QMessageBox.No: preset = VersionedPreset.with_preset( dataclasses.replace(preset.get_preset(), uuid=uuid.uuid4())) self._add_new_preset(preset)
def _get_preset(preset_json: dict) -> VersionedPreset: try: preset = VersionedPreset(preset_json) preset.get_preset() # test if valid return preset except Exception as e: raise InvalidAction(f"invalid preset: {e}")
def add_new_preset(self, new_preset: VersionedPreset) -> bool: """ Adds a new custom preset. :param: new_preset :return True, if there wasn't any preset with that name """ assert new_preset.uuid not in self.included_presets existed_before = new_preset.uuid in self.custom_presets self.custom_presets[new_preset.uuid] = new_preset path = self._file_name_for_preset(new_preset) new_preset.save_to_file(path) _commit(f"Update preset '{new_preset.name}'", path, self._data_dir.parent, False) return not existed_before
def add_new_preset(self, new_preset: VersionedPreset) -> bool: """ Adds a new custom preset. :param: new_preset :return True, if there wasn't any preset with that name """ if self._included_preset_with_name(new_preset.name) is not None: raise ValueError("A default preset with name '{}' already exists.".format(new_preset.name)) existed_before = new_preset.name in self.custom_presets self.custom_presets[new_preset.name] = new_preset path = self._file_name_for_preset(new_preset) new_preset.save_to_file(path) return not existed_before
def _on_import_preset(self): path = common_qt_lib.prompt_user_for_preset_file(self._window_manager, new_file=False) if path is None: return preset = VersionedPreset.from_file_sync(path) try: preset.get_preset() except (ValueError, KeyError): QMessageBox.critical( self._window_manager, "Error loading preset", "The file at '{}' contains an invalid preset.".format(path)) return if self._window_manager.preset_manager.preset_for_name( preset.name) is not None: user_response = QMessageBox.warning( self._window_manager, "Preset name conflict", "A preset named '{}' already exists. Do you want to overwrite it?" .format(preset.name), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if user_response == QMessageBox.No: return self._add_new_preset(preset)
def _add_new_preset(self, preset: VersionedPreset): with self._options as options: options.selected_preset_name = preset.name if self._window_manager.preset_manager.add_new_preset(preset): self._create_button_for_preset(preset) self.on_preset_changed(preset.get_preset())
def from_json_dict(cls, json_dict: dict) -> "LayoutDescription": json_dict = migrate_description(json_dict) has_spoiler = "game_modifications" in json_dict if not has_spoiler: raise ValueError( "Unable to read details of seed log with spoiler disabled") permalink = Permalink( seed_number=json_dict["info"]["seed"], spoiler=has_spoiler, presets={ index: VersionedPreset(preset).get_preset() for index, preset in enumerate(json_dict["info"]["presets"]) }, ) return LayoutDescription( version=json_dict["info"]["version"], permalink=permalink, all_patches=game_patches_serializer.decode( json_dict["game_modifications"], { index: preset.configuration for index, preset in permalink.presets.items() }), item_order=json_dict["item_order"], )
def from_json(cls, data) -> "GameSessionEntry": player_entries = [ PlayerSessionEntry.from_json(player_json) for player_json in data["players"] ] return GameSessionEntry( id=data["id"], name=data["name"], presets=[ VersionedPreset(preset_json) for preset_json in data["presets"] ], players={ player_entry.id: player_entry for player_entry in player_entries }, actions=[ GameSessionAction.from_json(item) for item in data["actions"] ], seed_hash=data["seed_hash"], word_hash=data["word_hash"], spoiler=data["spoiler"], permalink=data["permalink"], state=GameSessionState(data["state"]), generation_in_progress=data["generation_in_progress"], )
async def load_user_presets(self): all_files = self._data_dir.glob(f"*.{VersionedPreset.file_extension()}") user_presets = await asyncio.gather(*[VersionedPreset.from_file(f) for f in all_files]) for preset in user_presets: if preset.name in self.custom_presets or self._included_preset_with_name(preset.name) is not None: continue self.custom_presets[preset.name] = preset
def from_json_dict(cls, json_dict: dict) -> "LayoutDescription": version = json_dict.get("schema_version") if version != cls.schema_version(): raise RuntimeError("Unsupported log file version '{}'. Expected {}.".format(version, cls.schema_version())) has_spoiler = "game_modifications" in json_dict if not has_spoiler: raise ValueError("Unable to read details of seed log with spoiler disabled") permalink = Permalink( seed_number=json_dict["info"]["seed"], spoiler=has_spoiler, presets={ index: VersionedPreset(preset).get_preset() for index, preset in enumerate(json_dict["info"]["presets"]) }, ) return LayoutDescription( version=json_dict["info"]["version"], permalink=permalink, all_patches=game_patches_serializer.decode( json_dict["game_modifications"], { index: preset.layout_configuration for index, preset in permalink.presets.items() }), item_order=json_dict["item_order"], )
def dropEvent(self, event: QtGui.QDropEvent) -> None: item: QtWidgets.QTreeWidgetItem = self.itemAt(event.pos()) if not item: return event.setDropAction(Qt.IgnoreAction) source = self.preset_for_item(self.currentItem()) target = self.preset_for_item(item) if source is None or target is None: return event.setDropAction(Qt.IgnoreAction) if source.game != target.game or source.base_preset_uuid is None: return event.setDropAction(Qt.IgnoreAction) try: source_preset = source.get_preset() except InvalidPreset: return event.setDropAction(Qt.IgnoreAction) self.window_manager.preset_manager.add_new_preset( VersionedPreset.with_preset( dataclasses.replace(source_preset, base_preset_uuid=target.uuid))) return super().dropEvent(event)
def __init__(self, data_dir: Optional[Path]): self.included_presets = [VersionedPreset.from_file_sync(f) for f in read_preset_list()] self.custom_presets = {} if data_dir is not None: self._data_dir = data_dir.joinpath("presets") else: self._data_dir = None
def test_game_session_admin_session_change_layout_description( clean_database, preset_manager, mock_emit_session_update, mocker, flask_app): mock_verify_no_layout_description = mocker.patch( "randovania.server.game_session._verify_no_layout_description", autospec=True) mock_from_json_dict: MagicMock = mocker.patch( "randovania.layout.layout_description.LayoutDescription.from_json_dict" ) preset_as_json = json.dumps(preset_manager.default_preset.as_json) user1 = database.User.create(id=1234, name="The Name") session = database.GameSession.create(id=1, name="Debug", state=GameSessionState.SETUP, creator=user1, generation_in_progress=user1) database.GameSessionPreset.create(session=session, row=0, preset=preset_as_json) database.GameSessionPreset.create(session=session, row=1, preset=preset_as_json) database.GameSessionMembership.create(user=user1, session=session, row=None, admin=True) new_preset = preset_manager.default_preset.get_preset() new_preset = dataclasses.replace(new_preset, configuration=dataclasses.replace( new_preset.configuration, menu_mod=False)) sio = MagicMock() sio.get_current_user.return_value = user1 layout_description = mock_from_json_dict.return_value layout_description.as_json = "some_json_string" layout_description.permalink.player_count = 2 layout_description.permalink.presets = {i: new_preset for i in (0, 1)} # Run with flask_app.test_request_context(): game_session.game_session_admin_session( sio, 1, SessionAdminGlobalAction.CHANGE_LAYOUT_DESCRIPTION.value, "layout_description_json") # Assert mock_emit_session_update.assert_called_once_with(session) mock_verify_no_layout_description.assert_called_once_with(session) assert database.GameSession.get_by_id( 1).layout_description_json == '"some_json_string"' assert database.GameSession.get_by_id(1).generation_in_progress is None new_session = database.GameSession.get_by_id(1) new_json = json.dumps(VersionedPreset.with_preset(new_preset).as_json) assert [preset.preset for preset in new_session.presets] == [new_json] * 2
async def on_message(self, message: discord.Message): if message.author == self.user: return if message.guild.id != self.configuration["guild"]: return for attachment in message.attachments: filename: str = attachment.filename if filename.endswith(VersionedPreset.file_extension()): data = await attachment.read() versioned_preset = VersionedPreset( json.loads(data.decode("utf-8"))) await reply_for_preset(message, versioned_preset) channel: discord.TextChannel = message.channel if self.configuration["channel_name_filter"] in channel.name: await look_for_permalinks(message.content, channel)
def refresh_presets_command_logic(args): base_path = get_data_path().joinpath("presets") with base_path.joinpath("presets.json").open() as presets_file: preset_list = json.load(presets_file)["presets"] for preset_relative_path in preset_list: 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)
def __init__(self, data_dir: Optional[Path]): self.included_presets = { preset.uuid: preset for preset in [VersionedPreset.from_file_sync(f) for f in read_preset_list()] } self.custom_presets = {} if data_dir is not None: self._data_dir = data_dir else: self._data_dir = None
async def reply_for_preset(message: discord.Message, versioned_preset: VersionedPreset): try: preset = versioned_preset.get_preset() except ValueError as e: logging.info("Invalid preset '{}' from {}: {}".format( versioned_preset.name, message.author.display_name, e)) return embed = discord.Embed(title=preset.name, description=preset.description) for category, items in preset_describer.describe(preset): embed.add_field(name=category, value="\n".join(items), inline=True) await message.reply(embed=embed)
async def _on_customize_button(self): if self._logic_settings_window is not None: self._logic_settings_window.raise_() return editor = PresetEditor(self._current_preset_data.get_preset()) self._logic_settings_window = LogicSettingsWindow(self._window_manager, editor) self._logic_settings_window.on_preset_changed(editor.create_custom_preset_with()) editor.on_changed = lambda: self._logic_settings_window.on_preset_changed(editor.create_custom_preset_with()) result = await async_dialog.execute_dialog(self._logic_settings_window) self._logic_settings_window = None if result == QDialog.Accepted: self._add_new_preset(VersionedPreset.with_preset(editor.create_custom_preset_with()))
def dragEnterEvent(self, event: QtGui.QDragEnterEvent): from randovania.layout.preset_migration import VersionedPreset valid_extensions = [ LayoutDescription.file_extension(), VersionedPreset.file_extension(), ] valid_extensions_with_dot = { f".{extension}" for extension in valid_extensions } for url in event.mimeData().urls(): ext = os.path.splitext(url.toLocalFile())[1] if ext in valid_extensions_with_dot: event.acceptProposedAction() return
def as_json(self) -> dict: result = { "schema_version": CURRENT_DESCRIPTION_SCHEMA_VERSION, "info": { "version": self.version, "permalink": self.permalink.as_base64_str, "seed": self.permalink.seed_number, "presets": [ VersionedPreset.with_preset(preset).as_json for preset in self.permalink.presets.values() ], } } if self.permalink.spoiler: result["game_modifications"] = self._serialized_patches result["item_order"] = self.item_order return result
def _change_layout_description(sio: ServerApp, session: GameSession, description_json: Optional[dict]): _verify_has_admin(sio, session.id, None) _verify_in_setup(session) rows_to_update = [] if description_json is None: description = None else: if session.generation_in_progress != sio.get_current_user(): if session.generation_in_progress is None: raise InvalidAction(f"Not waiting for a layout.") else: raise InvalidAction( f"Waiting for a layout from {session.generation_in_progress.name}." ) _verify_no_layout_description(session) description = LayoutDescription.from_json_dict(description_json) permalink = description.permalink if permalink.player_count != session.num_rows: raise InvalidAction( f"Description is for a {permalink.player_count} players," f" while the session is for {session.num_rows}.") for permalink_preset, preset_row in zip(permalink.presets.values(), session.presets): preset_row = typing.cast(GameSessionPreset, preset_row) if _get_preset(json.loads( preset_row.preset)).get_preset() != permalink_preset: preset = VersionedPreset.with_preset(permalink_preset) if preset.game != RandovaniaGame.PRIME2: raise InvalidAction("Only Prime 2 presets allowed.") preset_row.preset = json.dumps(preset.as_json) rows_to_update.append(preset_row) with database.db.atomic(): for preset_row in rows_to_update: preset_row.save() session.generation_in_progress = None session.layout_description = description session.save()
def _file_name_for_preset(self, preset: VersionedPreset) -> Path: return self._data_dir.joinpath("{}.{}".format(preset.slug_name, preset.file_extension()))
async def load_user_presets(self): all_files = self._data_dir.glob(f"*.{VersionedPreset.file_extension()}") user_presets = await asyncio.gather(*[VersionedPreset.from_file(f) for f in all_files]) for preset in typing.cast(List[VersionedPreset], user_presets): self.custom_presets[preset.uuid] = preset
def test_included_presets_are_valid(f): VersionedPreset.from_file_sync(f).get_preset()
def all_presets(self) -> List[Preset]: return [ VersionedPreset(json.loads(preset.preset)).get_preset() for preset in sorted(self.presets, key=lambda it: it.row) ]
def _on_duplicate_preset(self): old_preset = self._current_preset_data self._add_new_preset( VersionedPreset.with_preset(old_preset.get_preset().fork()))