def _load_sprite_data_from_anime(anime_path) -> RelevantSpriteData: common_module_service = locator.get_scoped("CommonModuleService") template = locator.get_scoped( "ModuleService").get_common_module_template("Sprite Bin Data") module = common_module_service.open_common_module(template, anime_path) entry = module.entries[0] return RelevantSpriteData(entry)
def open_support_conversation_for_characters(self, character1, character2): part1 = character1["PID"].value[4:] part2 = character2["PID"].value[4:] path1 = "m/%s_%s.bin.lz" % (part1, part2) path2 = "m/%s_%s.bin.lz" % (part2, part1) archive = self._try_open_conversation(path1) if not archive: archive = self._try_open_conversation(path2) if not archive: archive = MessageArchive() archive.title = "MESS_ARCHIVE_%s_%s" % (part1, part2) archive.insert_or_overwrite_message( "MID_支援_%s_%s_C" % (part1, part2), "") archive.insert_or_overwrite_message( "MID_支援_%s_%s_B" % (part1, part2), "") archive.insert_or_overwrite_message( "MID_支援_%s_%s_A" % (part1, part2), "") archive.insert_or_overwrite_message( "MID_支援_%s_%s_S" % (part1, part2), "") locator.get_scoped( "OpenFilesService").register_or_overwrite_message_archive( path1, archive) editor_title = "Support - %s and %s" % (character1.get_display_name(), character2.get_display_name()) editor = FE14ConversationEditor(archive, title=editor_title, owner=self, is_support=True) self._conversation_editors.append(editor) editor.show()
def import_values(self, values_json: Any): if not values_json: self.value = None else: self.value = values_json locator.get_scoped("Driver").register_unresolved_import_reference( self)
def get_display_name(self, fid: str): if fid == "FID_username": return self.get_avatar_name() else: portrait_entry = locator.get_scoped("PortraitService").get_portrait_entry_for_fid(fid, "st") if not portrait_entry: portrait_entry = locator.get_scoped("PortraitService").get_portrait_entry_for_fid("FID_フードマン", "st") return portrait_entry["Name"].value
def close(self, index): (key, value) = self._get_elem(index) logging.info("Closing file " + key) archive = value.file self.beginRemoveRows(QModelIndex(), index, index + 1) locator.get_scoped("Driver").close_archive(archive) self.endRemoveRows()
def get_portraits_for_fid(self, fid: str, mode: str = "st"): portrait_service = locator.get_scoped("PortraitService") if fid == "FID_username": portraits = locator.get_scoped("PortraitService").get_avatar_portraits(self.avatar_is_female()) else: portraits = portrait_service.get_portraits_for_fid(fid, mode) if not portraits: portraits = portrait_service.get_portraits_for_fid("FID_フードマン", mode) return portraits
def get_sprite_for_character(self, character: PropertyContainer, team: int) -> Optional[QPixmap]: assets_service = locator.get_scoped("AssetsService") class_module: TableModule = locator.get_scoped( "ModuleService").get_module("Classes") sprite_file_name = get_sprite_file_name_from_team(team) class_id = character[_CLASS_KEY].value job = class_module.entries[class_id] jid = job[_JID_KEY].value aid = character[_AID_KEY].value if not aid: aid = jid unique_path = _UNIQUE_FILE_TEMPLATE % (jid[4:], aid[4:], sprite_file_name) head_dir_path = _HEAD_FILE_TEMPLATE % (aid[4:], sprite_file_name) body_dir_path = _BODY_FILE_TEMPLATE % (jid[4:], sprite_file_name) body_anime_path = _BODY_ANIME_TEMPLATE % jid[4:] if unique_path in _CACHE: return _CACHE[unique_path].toqpixmap() if head_dir_path + body_dir_path in _CACHE: return _CACHE[head_dir_path + body_dir_path].toqpixmap() unique_texture: Optional[Dict[str, Texture]] = assets_service.load_bch( unique_path) if unique_texture: unique_texture_key = next(iter(unique_texture), None) if unique_texture_key: result = self._assemble_unique_sprite( unique_texture[unique_texture_key]) if result: _CACHE[unique_path] = result return result.toqpixmap() head_texture: Optional[Dict[str, Texture]] = assets_service.load_bch( head_dir_path) if not head_texture: return None body_texture: Optional[Dict[str, Texture]] = assets_service.load_bch( body_dir_path) if not body_texture: return None relevant_sprite_data = self._load_sprite_data_from_anime( body_anime_path) head_texture_key = next(iter(head_texture), None) body_texture_key = next(iter(body_texture), None) if head_texture_key and body_texture_key: result = self._assemble_sprite(head_texture[head_texture_key], body_texture[body_texture_key], relevant_sprite_data) if result: _CACHE[unique_path] = result return result.toqpixmap() else: return None
def _open_person(chapter): target_file = "%s.bin.lz" % chapter["CID"].value[4:] target_path = search_all_routes_for_file("/GameData/Person/", target_file) if not target_path: return None module_service = locator.get_scoped("ModuleService") common_module_service = locator.get_scoped("CommonModuleService") module_template = module_service.get_common_module_template("Person") module = common_module_service.open_common_module(module_template, target_path) module_service.set_module_in_use(module) return module
def save(): services_to_save = [ locator.get_scoped("DedicatedEditorsService"), locator.get_scoped("ModuleService"), locator.get_scoped("CommonModuleService"), locator.get_scoped("OpenFilesService") ] success = True for service in services_to_save: if not service.save(): success = False return success
def _open_map_config(chapter): truncated_cid = chapter["CID"].value[4:] target_path = "/map/config/%s.bin" % truncated_cid open_files_service = locator.get_scoped("OpenFilesService") if not open_files_service.exists(target_path): return None module_service = locator.get_scoped("ModuleService") common_module_service = locator.get_scoped("CommonModuleService") module_template = module_service.get_common_module_template("Map Config") module = common_module_service.open_common_module(module_template, target_path) module_service.set_module_in_use(module) return module
def _on_import_triggered(self): file_name, ok = QFileDialog.getOpenFileName(self, "Select file.", filter="*.json") if ok: try: locator.get_scoped("Driver").import_from_json(file_name) self.statusbar.showMessage("Import succeeded!", 5000) except: logging.exception("An error occurred during importing.") self.error_dialog = ErrorDialog( "Importing failed. See the log for details.") self.error_dialog.show() self.statusbar.showMessage("Importing failed.", 5000)
def _set_view_models(self): module_service = locator.get_scoped("ModuleService") dedicated_editors_service = locator.get_scoped( "DedicatedEditorsService") self.proxy_model = ModuleFilterModel() self.open_file_model = OpenFilesModel() self.proxy_model.setSourceModel(module_service.get_module_model()) self.module_list_view.setModel(self.proxy_model) self.editors_list_view.setModel( dedicated_editors_service.get_dedicated_editors_model()) self.file_list_view.setModel(self.open_file_model) self.module_list_view.setHeaderHidden(True) self.module_list_view.setEditTriggers( QtWidgets.QAbstractItemView.NoEditTriggers)
def run(self): locator.clear_scoped_services() try: locator.register_scoped("Driver", Driver(self.project)) locator.register_scoped("OpenFilesService", OpenFilesService(self.project.filesystem)) locator.register_scoped("ModuleDataService", ModuleDataService()) locator.register_scoped("ModuleService", ModuleService(self.project)) locator.register_scoped("CommonModuleService", CommonModuleService()) locator.register_scoped("DedicatedEditorsService", DedicatedEditorsService(self.project.game)) locator.get_scoped("ModuleService").load_files_and_generate_model() locator.get_static("SettingsService").save(self.project) self.over.emit() except Exception as e: logging.exception(e) self.failed.emit()
def _try_load_ai_data(self): self._loaded = True open_files_service = locator.get_scoped("OpenFilesService") try: bin_archive = open_files_service.open(_AI_DATA_PATH) reader = BinArchiveReader(bin_archive) ac_ptr = reader.read_internal_pointer() mi_ptr = reader.read_internal_pointer() at_ptr = reader.read_internal_pointer() mv_ptr = reader.read_internal_pointer() ac_table = self._read_null_terminated_list(reader, ac_ptr) mi_table = self._read_null_terminated_list(reader, mi_ptr) at_table = self._read_null_terminated_list(reader, at_ptr) mv_table = self._read_null_terminated_list(reader, mv_ptr) ac_labels = self._read_mapped_pointers(reader, ac_table) mi_labels = self._read_mapped_pointers(reader, mi_table) at_labels = self._read_mapped_pointers(reader, at_table) mv_labels = self._read_mapped_pointers(reader, mv_table) self.ac = ac_labels self.mi = mi_labels self.at = at_labels self.mv = mv_labels except: logging.exception("Unable to load AI data.")
def load(self): if not self.loaded: open_files_service = locator.get_scoped("OpenFilesService") for dialogue in self.dialogues: archive = open_files_service.open_message_archive(dialogue.path) self.archives[dialogue] = archive self.loaded = True
def open_common_module(self, module_template: Module, file_path: str) -> Module: # First, check the cache. key = (module_template, file_path) if key in self._open_modules: return self._open_modules[key] # Not in the cache. Need to open the selected file and create a module copy. # First, convert the file path to one that starts at the ROM root. open_files_service: OpenFilesService = locator.get_scoped( "OpenFilesService") valid_path = open_files_service.to_valid_path_in_filesystem(file_path) if not valid_path: raise ValueError # Create the module copy and attach to the target file. module = module_template.duplicate() archive = None # TODO: This should be a method of the Module class. try: archive = open_files_service.open(valid_path) module.attach_to(archive) except Exception as ex: logging.exception("Failed to attach to module.") open_files_service.close_archive(archive) raise ex self._open_modules[key] = module return module
def get_avatar_portraits(self, is_female: bool, mode: str = "st"): if self._cached_avatar and self._cached_avatar_is_female == is_female: return self._cached_avatar assets_service = locator.get_scoped("AssetsService") if is_female: portraits = self.get_portraits_from_arc( _FEMALE_AVATAR_PORTRAIT_FILE) hair = assets_service.load_bch(_FEMALE_AVATAR_HAIR_FILE) else: portraits = self.get_portraits_from_arc(_MALE_AVATAR_PORTRAIT_FILE) hair = assets_service.load_bch(_MALE_AVATAR_HAIR_FILE) if not portraits or not hair: portraits = self.get_portraits_for_fid("FID_フードマン", mode) else: hair_texture_key = next(iter(hair), None) if hair_texture_key: hair_texture: Texture = hair[hair_texture_key] for portrait in portraits.values(): if portrait != "汗" and portrait != "照": portrait.raw_image().paste(hair_texture.raw_image(), (0, 0), hair_texture.raw_image()) self._cached_avatar = portraits self._cached_avatar_is_female = is_female return portraits
def save(self): driver = locator.get_scoped("Driver") if driver.save(): self.statusbar.showMessage("Save succeeded!", 5000) else: self.statusbar.showMessage("Save failed. See the log for details.", 5000)
def __init__(self, is_person=False, parent=None): super().__init__(parent) self.is_person = is_person self.module: TableModule = locator.get_scoped("ModuleService").get_module("Characters") self.proxy_model = QSortFilterProxyModel() self.proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) self.proxy_model.setSourceModel(self.module.entries_model) self.characters_list_view.setModel(self.proxy_model) self.selection: Optional[PropertyContainer] = None self.character_details_form_1 = PropertyForm(self.module.element_template, category="character_description_1") self.character_details_form_contents_1.setLayout(self.character_details_form_1) self.character_details_form_2 = PropertyForm(self.module.element_template, category="character_description_2") self.character_details_form_contents_2.setLayout(self.character_details_form_2) self.character_details_form_2.fix_editor_width(100) self.stats_editor = MergedStatsEditor(["Bases", "Growths", "Modifiers", "Penalties", "Bonuses"]) self.stats_form = PropertyForm(self.module.element_template, category="stats") self.stats_layout.addWidget(self.stats_editor) self.stats_layout.addLayout(self.stats_form) self.skills_form = PropertyForm(self.module.element_template, category="skills", sort_editors=True) self.skills_contents.setLayout(self.skills_form) self.flags_editor = MergedFlagsEditor( ["Bitflags (1)", "Bitflags (2)", "Bitflags (3)", "Bitflags (4)"], self.module.element_template ) self.flags_editor_2 = MergedFlagsEditor( ["Bitflags (5)", "Bitflags (6)", "Bitflags (7)", "Bitflags (8)"], self.module.element_template ) self.misc_form = PropertyForm(self.module.element_template, category="misc") self.misc_layout.addWidget(self.flags_editor) self.misc_layout.addWidget(self.flags_editor_2) self.misc_layout.addLayout(self.misc_form) self.ids_form = PropertyForm(self.module.element_template, category="ids") self.ids_tab.setLayout(self.ids_form) self.classes_form = PropertyForm(self.module.element_template, category="classes", sort_editors=True) self.classes_tab.setLayout(self.classes_form) if not self.is_person: self.dialogue_tab = DialogueEditor() self.supports_tab = QWidget() self.supports_layout = QHBoxLayout() self.supports_widget = FE14SupportWidget() self.supports_scroll = QScrollArea() self.supports_scroll_contents = QWidget() self.supports_scroll.setWidget(self.supports_scroll_contents) self.supports_scroll.setWidgetResizable(True) self.supports_layout.addWidget(self.supports_widget) self.supports_layout.addWidget(self.supports_scroll) self.supports_tab.setLayout(self.supports_layout) self.supports_form = PropertyForm(self.module.element_template, category="supports") self.supports_scroll_contents.setLayout(self.supports_form) self.tab_widget.addTab(self.supports_tab, "Supports") self.tab_widget.addTab(self.dialogue_tab, "Dialogue") self.context_menu = QMenu(self) self.context_menu.addActions([self.action_add, self.action_remove, self.action_copy_to]) self.clear_selection_shortcut = QShortcut(QKeySequence.Cancel, self) self._install_signals() self._clear()
def __init__(self): super().__init__() self.setupUi(self) self.pushButton_2.setEnabled(False) self.pushButton_3.setEnabled(False) self.comboBox.setEnabled(False) self.setWindowTitle("Support Editor") self.setWindowIcon(QIcon("paragon.ico")) self.error_dialog = None module_service = locator.get_scoped("ModuleService") self.service = None self.current_character = None self.current_supports = None self.current_support = None self.model = module_service.get_module("Characters").entries_model self.proxy_model = QSortFilterProxyModel(self) self.proxy_model.setSourceModel(self.model) self.proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) self.characters_list_view.setModel(self.proxy_model) self.characters_list_view.selectionModel().currentRowChanged.connect(self._update_selection) self.listWidget.selectionModel().currentRowChanged.connect(self._on_target_character_changed) self.listWidget_2.selectionModel().currentRowChanged.connect(self._update_support_selection) self.lineEdit.textChanged.connect(self._update_filter) self.pushButton_2.clicked.connect(self._on_add_support_pressed) self.pushButton_3.clicked.connect(self._on_remove_support_pressed) self.comboBox.currentIndexChanged.connect(self._on_support_type_changed)
def _get_model_index_of_character(self, character): module_service = locator.get_scoped("ModuleService") entries = module_service.get_module("Characters").entries for i in range(0, len(entries)): if entries[i] == character: return self.model.index(i) return QModelIndex()
def _get_model_index_of_character(self, character): driver = locator.get_scoped("Driver") entries = driver.modules["Characters"].entries for i in range(0, len(entries)): if entries[i] == character: return self.model.index(i) return QModelIndex()
def get_blush_and_sweat_coordinates(self, fid: str, mode: str): if fid == "FID_username": if self.avatar_is_female(): fid = "FID_マイユニ_女2_顔A" else: fid = "FID_マイユニ_男1_顔B" return locator.get_scoped("PortraitService").get_blush_and_sweat_coordinates(fid, mode)
def __init__(self): open_files_service = locator.get_scoped("OpenFilesService") self.archive = open_files_service.open("GameData/GameData.bin.lz") self.editor = FE14SupportEditor() if self.archive: open_files_service.set_archive_in_use(self.archive)
def __init__(self): super().__init__() self.chapter_data = None module_service = locator.get_scoped("ModuleService") config_module = module_service.get_common_module_template("Map Config") self.module = module_service.get_module("Chapters") self.text_data_widget = FE14ChapterTextDataWidget() self.header_scroll, self.header_property_form = PropertyForm.create_with_scroll( self.module.element_template) self.config_scroll, self.config_property_form = PropertyForm.create_with_scroll( config_module.element_template) self.header_property_form.editors["CID"].setEnabled(False) self.header_property_form.editors["Key (CID)"].setEnabled(False) self.vertical_layout = QVBoxLayout(parent=self) self.splitter = QSplitter(parent=self) self.splitter.setOrientation(QtCore.Qt.Vertical) self.splitter2 = QSplitter(parent=self) self.splitter2.setOrientation(QtCore.Qt.Horizontal) self.splitter.addWidget(self.text_data_widget) self.splitter.addWidget(self.splitter2) self.splitter2.addWidget(self.header_scroll) self.splitter2.addWidget(self.config_scroll) self.vertical_layout.addWidget(self.splitter) self.scroll_content = QWidget() self.scroll_content.setLayout(self.vertical_layout) self.setWidget(self.scroll_content) self.setWidgetResizable(True)
def __init__(self): super().__init__() open_files_service = locator.get_scoped("OpenFilesService") self.archive = open_files_service.open("GameData/GameData.bin.lz") self.dialogues = self._read_dialogue_data() self.archives = {} self.loaded = False
def load_background() -> Optional[QPixmap]: assets_service = locator.get_scoped("AssetsService") arc = assets_service.load_arc("/effect/Tlp_Ev_t001.arc.lz") if not arc or "model.bch" not in arc: return None image: QImage = arc["model.bch"].image() return QPixmap.fromImage(image).copy(56, 8, 400, 240)
def _load_icons(self): if self._loaded: return self._loaded = True assets_service = locator.get_scoped("AssetsService") icons: Optional[Dict[str, Texture]] = assets_service.load_bch( "/icon/Icon.bch.lz") if not icons: return if _SKILL_TEXTURE_KEY in icons: skill_icons_texture = icons[_SKILL_TEXTURE_KEY] icon_width, icon_height = _SKILL_ICON_DIMENSIONS self._skill_icons = self._slice(skill_icons_texture, icon_width, icon_height) if _SKILL2_TEXTURE_KEY in icons: skill2_icons_texture = icons[_SKILL2_TEXTURE_KEY] icon_width, icon_height = _SKILL_ICON_DIMENSIONS self._skill_icons.extend( self._slice(skill2_icons_texture, icon_width, icon_height)) if _ITEM_TEXTURE_KEY in icons: item_icons_texture = icons[_ITEM_TEXTURE_KEY] icon_width, icon_height = _ITEM_ICON_DIMENSIONS self._item_icons = self._slice(item_icons_texture, icon_width, icon_height) if _FACTION_TEXTURE_KEY in icons: faction_icons_texture = icons[_FACTION_TEXTURE_KEY] icon_width, icon_height = _FACTION_ICON_DIMENSIONS self._faction_icons = self._slice(faction_icons_texture, icon_width, icon_height) real_faction_icons = [] for i in range(0, len(self._faction_icons)): if i not in [5, 11, 17]: real_faction_icons.append(self._faction_icons[i]) self._faction_icons = real_faction_icons
def _on_add_chapter_triggered(self): # Get the chapter to use as a base choices = self._create_chapter_choice_list() (choice, ok) = QInputDialog.getItem(self, "Select Base Chapter", "Base Chapter", choices) if not ok: return source_chapter = self._get_chapter_from_choice(choice, choices) # Get the desired CID. (desired_cid, ok) = QInputDialog.getText(self, "Enter a CID for the new chapter.", "CID") if not ok: return # Validate the CID. service = locator.get_scoped("ChapterService") if service.is_cid_in_use(desired_cid): self.error_dialog = ErrorDialog("The CID \"" + desired_cid + "\" is already in use.") self.error_dialog.show() return if not desired_cid.startswith("CID_"): self.error_dialog = ErrorDialog("CID must start with the \"CID_\"") self.error_dialog.show() return # Create the chapter service.create_chapter(source_chapter, desired_cid)
def __init__(self): super().__init__() self.setupUi(self) self.pushButton_2.setEnabled(False) self.pushButton_3.setEnabled(False) self.comboBox.setEnabled(False) self.setWindowTitle("Support Editor") self.setWindowIcon(QIcon("paragon.ico")) driver = locator.get_scoped("Driver") self.service = None self.current_character = None self.current_supports = None self.current_support = None self.model = driver.modules["Characters"].entries_model self.characters_list_view.setModel(self.model) self.characters_list_view.selectionModel().currentRowChanged.connect( self._update_selection) self.listWidget.selectionModel().currentRowChanged.connect( self._on_target_character_changed) self.listWidget_2.selectionModel().currentRowChanged.connect( self._update_support_selection) self.pushButton_2.clicked.connect(self._on_add_support_pressed) self.pushButton_3.clicked.connect(self._on_remove_support_pressed) self.comboBox.currentIndexChanged.connect( self._on_support_type_changed)