def _string_to_Map(self, string): try: return get_map(string) except (KeyError, ValueError): raise InvalidFieldValueError( f"Could not interpret input as a Map specifier for field {self.field_nickname}. Try a string like " f"'m10_02_00_00'.")
def on_map_choice(self, event=None): """Check if current text has changed (and warn), then switch to other text.""" if not self._ignored_unsaved(): game_map = get_map(self.selected_map_id) self.map_choice.var.set( f"{game_map.emevd_file_stem} [{game_map.verbose_name}]") return self.selected_map_id = self.map_choice.var.get().split(' [')[0] if self.global_map_choice_func and event is not None: self.global_map_choice_func(self.selected_map_id) self.text_editor.delete(1.0, 'end') self.text_editor.insert(1.0, self.evs_text[self.selected_map_id]) self.text_editor.mark_set("insert", "1.0") self.text_editor.color_syntax()
def refresh(self): game_maps = [get_map(m) for m in self.evs_file_paths] map_options = [ f"{game_map.emevd_file_stem} [{game_map.verbose_name}]" for game_map in game_maps ] self.map_choice["values"] = map_options if map_options: self.map_choice.var.set(map_options[0]) self.selected_map_id = self.map_choice.get().split(' [')[0] self.text_editor.delete(1.0, "end") self.text_editor.insert(1.0, self.evs_text[self.selected_map_id]) self.text_editor.mark_set("insert", "1.0") self.text_editor.color_syntax()
def _export_talk(self, export_directory): """Exports all TalkESDBND files to game from individual map folders in '[project]/talk'.""" if self.game_name == "Dark Souls Prepare to Die Edition": game_version = "ptde" elif self.game_name == "Dark Souls Remastered": game_version = "dsr" else: raise ValueError("Cannot export talk for non-DS1 game.") for map_directory in (self.project_root / "talk").glob("*"): if map_directory.name not in [g.name for g in ALL_MAPS]: continue # unexpected folder bnd_file_name = get_map( map_directory.name).esd_file_stem + ".talkesdbnd" talk = TalkESDBND(map_directory, game_version=game_version) talk.write(export_directory / bnd_file_name)
def _import_talk(self, import_directory): if self.game_name == "Dark Souls Prepare to Die Edition": game_version = "ptde" elif self.game_name == "Dark Souls Remastered": game_version = "dsr" else: raise ValueError("Cannot export talk for non-DS1 game.") for talkesdbnd in import_directory.glob( f"*.talkesdbnd{'.dcx' if game_version == 'dsr' else ''}"): map_id = talkesdbnd.name.split(".talkesdbnd")[0] if map_id in ("m12_00_00_01", "m14_02_00_00"): continue # skipped try: map_name = get_map(map_id).name except KeyError: _LOGGER.warning( f"Ignoring unexpected `.talkesdbnd` file in Dark Souls files: {talkesdbnd.name}" ) continue TalkESDBND(talkesdbnd, game_version=game_version).write_all_esp( self.project_root / f"talk/{map_name}")
def set_global_map_choice(self, map_id, ignore_tabs=()): game_map = get_map(map_id) if "maps" not in ignore_tabs: if game_map.msb_file_stem is not None: self.maps_tab.map_choice.var.set(f"{game_map.msb_file_stem} [{game_map.verbose_name}]") self.maps_tab.on_map_choice() if "entities" not in ignore_tabs: if game_map.msb_file_stem is not None: self.entities_tab.map_choice.var.set(f"{game_map.msb_file_stem} [{game_map.verbose_name}]") self.entities_tab.on_map_choice() if "events" not in ignore_tabs: if game_map.emevd_file_stem is not None: self.events_tab.map_choice.var.set(f"{game_map.emevd_file_stem} [{game_map.verbose_name})") self.events_tab.on_map_choice() if "ai" not in ignore_tabs: if game_map.ai_file_stem is not None: self.ai_tab.map_choice.var.set(f"{game_map.ai_file_stem} [{game_map.verbose_name}]") self.ai_tab.on_map_choice() if "talk" not in ignore_tabs: if game_map.esd_file_stem is not None: self.talk_tab.map_choice.var.set(f"{game_map.esd_file_stem} [{game_map.verbose_name}]") self.talk_tab.on_map_choice()
def refresh(self): """Reloads all ESP files from all maps.""" self.esp_file_paths = {} self.esp_text = {} for map_directory in self.esp_directory.glob("*"): try: game_map = get_map(map_directory.name) except ValueError: _LOGGER.warning( f"Unexpected folder found in project '/talk' directory and ignored: " f"{map_directory.name}") continue for esp_path in map_directory.glob("*.esp.py"): talk_match = _TALK_ESP_MATCH.match(esp_path.name) if not talk_match: _LOGGER.warning( f"Invalid ESP file found and ignored: {str(esp_path)}") continue talk_id = int(talk_match.group(1)) self.esp_file_paths.setdefault(game_map.esd_file_stem, {})[talk_id] = esp_path with esp_path.open(mode="r", encoding="utf-8") as f: self.esp_text.setdefault(game_map.esd_file_stem, {})[talk_id] = f.read()
def get_selected_msb(self): map_name = get_map(self.map_choice_id).name return self.Maps[map_name]
def __getitem__(self, map_source): game_map = get_map(map_source) return self._data[game_map.name]
def _import_entities_module(self): """Reads '{map_id}_entities.py' file and loads names from it into map data. Also tries to read descriptions from inline comments with regex. (Messing too much with the formatting in the module file may interfere with this.) """ game_map = get_map(self.map_choice_id) msb = self.get_selected_msb() module_path = self.evs_directory / f"{game_map.emevd_file_stem}_entities.py" if not module_path.is_file(): return self.error_dialog( "No Entity Module", "Entity module not yet created in project 'events' folder.") evs_path = self.evs_directory / f"{game_map.emevd_file_stem}.evs.py" if not evs_path.is_file(): return self.error_dialog( "No EVS Script", "EVS script not yet imported into project 'events' folder.") sys.path.append(str(module_path.parent)) try: entity_module = import_module(module_path.stem) except Exception as e: return self.error_dialog( "Import Error", f"Could not import {module_path.name}. Error:\n\n{str(e)}") entries_by_entity_enum = {} found_map_entry_class_names = [] not_found = [] skipped = set() # will also skip description for attr_name, attr in [ m[0:2] for m in inspect.getmembers(entity_module, inspect.isclass) if m[1].__module__ == entity_module.__name__ ]: for entry_game_type in ENTITY_GAME_TYPES: if entry_game_type in attr.__bases__: break else: continue # ignore this class found_map_entry_class_names.append(attr_name) for entity_enum in attr: entry = msb.get_entry_with_entity_id(entity_enum.value, allow_multiple=True) if entry is None: not_found.append(entity_enum.value) continue if isinstance(entry, list): choice = self.CustomDialog( title="Multiple Entries with Same ID", message= f"Entity ID {entity_enum.value} in Python module '{module_path.stem}' appears " f"multiple times in MSB. This will rename only the first one found (type " f"{entry[0].ENTRY_SUBTYPE.name}). Is this OK?", button_kwargs=("YES", "NO", "NO"), button_names=("Yes, change it", "No, skip it", "No, abort import"), default_output=2, cancel_output=2, ) if choice == 2: return elif choice == 1: skipped.add(entity_enum.value) continue else: entry = entry[0] entry_type_name = entry.ENTRY_SUBTYPE.get_pluralized_type_name( ) entry_subtype_name = entry.ENTRY_SUBTYPE.name if entry_game_type.get_msb_entry_type_subtype() != ( entry_type_name, entry_subtype_name): choice = self.CustomDialog( title="Entry Type Mismatch", message= f"Entity ID {entity_enum.value} in Python module '{module_path.stem}' has type " f"'{attr_name}', but has different type '{entry_type_name}.{entry_subtype_name} in MSB. " f"Change name anyway?", button_kwargs=("YES", "NO", "NO"), button_names=("Yes, change it", "No, skip it", "No, abort import"), default_output=2, cancel_output=2, ) if choice == 2: return elif choice == 1: skipped.add(entity_enum.value) continue entries_by_entity_enum[entity_enum] = entry if not entries_by_entity_enum: return self.CustomDialog( "No IDs to Update", "No IDs in the Python entities module are present in the MSB.") if not_found: not_found_string = word_wrap(", ".join(not_found), 50) if (self.CustomDialog( title="Allow Missing IDs?", message= f"These entity IDs in the Python module could not be found in the MSB:" f"\n\n{not_found_string}" f"\n\nContinue updating {len(entries_by_entity_enum)} other IDs?", button_kwargs=("YES", "NO"), button_names=("Yes, continue", "No, abort import"), default_output=0, cancel_output=1, ) == 1): return # Find descriptions with regular expressions. skip_description = skipped.union(not_found) current_attr_name = "" descriptions_by_attr_and_id = {} with module_path.open("r", encoding="utf-8") as module: for line in module: class_match = _RE_ENTITIES_ENUM_CLASS.match(line) if class_match: attr_name = class_match.group(1) if attr_name in found_map_entry_class_names: current_attr_name = attr_name continue if current_attr_name: member_match = _RE_ENTITIES_ENUM_MEMBER.match(line) if member_match: entity_id, description = member_match.group(2, 3) if entity_id not in skip_description: descriptions_by_attr_and_id[ current_attr_name, int(entity_id)] = description.strip() for entity_enum, entry in entries_by_entity_enum.items(): entry.name = entity_enum.name for (attr_name, entity_id), description in descriptions_by_attr_and_id.items(): # attr_name not actually used, as entity ID uniqueness should have already been resolved entry = msb.get_entry_with_entity_id(entity_id, allow_multiple=True) if not entry: continue # shouldn't happen (as missing entity ID should be skipped) but just in case if isinstance(entry, list): entry = entry[0] # could happen entry.description = description self.refresh_entries() self.CustomDialog( "Import Successful", f"Entity names and descriptions imported successfully.")
def _get_map(self): return get_map(self.map_choice_id)
def get_map(self) -> Map: return get_map(self.map_choice_id)