def __init__(self, game_param_bnd_source=None): """Unpack DS1 GameParams into a single modifiable structure. Args: game_param_bnd_source: any valid source for GameParam.parambnd[.dcx] (its file path, the directory containing it, the unpacked BND directory, or an existing BND instance). It will default to the DEFAULT_GAME package. The appropriate bundled ParamDef will be loaded automatically, detected based on the DCX compression of the BND. """ self._reload_warning = True self._data = {} if game_param_bnd_source is None: self._game_param_bnd = None self.paramdef_bnd = None return if isinstance(game_param_bnd_source, BaseBND): self._game_param_bnd = game_param_bnd_source else: if isinstance(game_param_bnd_source, (str, Path)): game_param_bnd_source = Path(game_param_bnd_source) if game_param_bnd_source.is_dir(): if (game_param_bnd_source / 'GameParam.parambnd').is_file(): game_param_bnd_source = game_param_bnd_source / 'GameParam.parambnd' elif (game_param_bnd_source / 'GameParam.parambnd.dcx').is_file(): game_param_bnd_source = game_param_bnd_source / 'GameParam.parambnd.dcx' try: self._game_param_bnd = BND(game_param_bnd_source) except TypeError: raise TypeError( "Could not load DarkSoulsGameParameters from given source." ) self.paramdef_bnd = PARAMDEF_BND( 'dsr' if self._game_param_bnd.dcx else 'ptde') for entry in self._game_param_bnd: p = self._data[entry.path] = ParamTable(entry.data, self.paramdef_bnd) try: # Nickname assigned here (ParamTable isn't aware of its own basename). param_nickname = PARAM_NICKNAMES[entry.name[:-len('.param')]] except KeyError: # ParamTables without nicknames (i.e. useless params) are excluded from this structure. pass else: setattr( self, param_nickname, p ) # NOTE: Reference to dictionary in self._data[entry.path]. p.nickname = param_nickname
def validate_model_subtype(self, model_subtype, name, map_id): """Check appropriate game model files to confirm the given model name is valid. Note that Character and Object models don't actually need `map_id` to validate them. """ model_subtype = MSBModelList.resolve_entry_subtype(model_subtype) dcx = ".dcx" if self.project.game_name == "Dark Souls Remastered" else "" if model_subtype == MSBModelSubtype.Character: if (self.project.game_root / f"chr/{name}.chrbnd{dcx}").is_file(): return True elif model_subtype == MSBModelSubtype.Object: if (self.project.game_root / f"obj/{name}.objbnd{dcx}").is_file(): return True elif model_subtype == MSBModelSubtype.MapPiece: if (self.project.game_root / f"map/{map_id}/{name}A{map_id[1:3]}.flver{dcx}").is_file(): return True elif model_subtype == MSBModelSubtype.Collision: # TODO: Rough BHD string scan until I have that file format. hkxbhd_path = self.project.game_root / f"map/{map_id}/h{map_id}.hkxbhd" if hkxbhd_path.is_file(): with hkxbhd_path.open("r") as f: if name + "A10.hkx" in f.read(): return True elif model_subtype == MSBModelSubtype.Navmesh: nvmbnd_path = self.project.game_root / f"map/{map_id}/{map_id}.nvmbnd{dcx}" if nvmbnd_path.is_file(): navmesh_bnd = BND(nvmbnd_path) if name + "A10.nvm" in navmesh_bnd.entries_by_basename.keys(): return True return False
def __init__(self, game_param_bnd_source=None): """Unpack DS1 GameParams into a single modifiable structure. Args: game_param_bnd_source: any valid source for GameParam.parambnd[.dcx] (its file path, the directory containing it, the unpacked BND directory, or an existing BND instance). It will default to the DEFAULT_GAME package. The appropriate bundled ParamDef will be loaded automatically, detected based on the DCX compression of the BND. """ self._reload_warning = True self._data = {} if game_param_bnd_source is None: self._game_param_bnd = None self.paramdef_bnd = None return if isinstance(game_param_bnd_source, BaseBND): self._game_param_bnd = game_param_bnd_source else: if isinstance(game_param_bnd_source, (str, Path)): game_param_bnd_source = Path(game_param_bnd_source) if game_param_bnd_source.is_dir(): if (game_param_bnd_source / "GameParam.parambnd").is_file(): game_param_bnd_source = game_param_bnd_source / "GameParam.parambnd" elif (game_param_bnd_source / "GameParam.parambnd.dcx").is_file(): game_param_bnd_source = game_param_bnd_source / "GameParam.parambnd.dcx" try: self._game_param_bnd = BND(game_param_bnd_source) except TypeError: raise TypeError( "Could not load DarkSoulsGameParameters from given source." ) self.paramdef_bnd = PARAMDEF_BND( "dsr" if self._game_param_bnd.dcx else "ptde") for entry in self._game_param_bnd: p = self._data[entry.path] = ParamTable(entry.data, self.paramdef_bnd) if p.param_info is not None: if p.nickname is None: setattr(self, _AMBIGUOUS_NICKNAMES[entry.name], p) else: setattr(self, p.nickname, p) # shortcut attribute
def __init__(self, draw_param_directory=None): """Unpack DS1 DrawParams into a single modifiable structure. Opens all DrawParam BNDs simultaneously for editing and repacking. The appropriate bundled ParamDef file will be loaded, with the game version determined by the DrawParam DCX compression. Args: draw_param_directory: Directory where all the 'aXX_DrawParam.parambnd[.dcx]' files are. This will be inside 'params/DrawParam' in your game directory.. """ self._reload_warning_given = True self._data = {} self._bnd_file_names = {} if draw_param_directory is None: return self._draw_param_directory = Path(draw_param_directory) for area_id in self._MAP_IDS: if isinstance(area_id, int): file_map_name = f'a{area_id}' map_name = f'm{area_id}' else: file_map_name = map_name = area_id file_map_name += '_DrawParam.parambnd' try: draw_param_bnd = BND(self._draw_param_directory / f'{file_map_name}.dcx', optional_dcx=False) file_map_name += '.dcx' except FileNotFoundError: try: draw_param_bnd = BND(self._draw_param_directory / file_map_name, optional_dcx=False) except FileNotFoundError: raise FileNotFoundError( f"Could not find '{file_map_name}[.dcx]' in directory " f"'{self._draw_param_directory}'.") self._data[map_name] = MapDrawParam(draw_param_bnd) self._bnd_file_names[map_name] = file_map_name setattr(self, map_name, self._data[map_name])
def __init__(self, draw_param_bnd): """Structure that manages double-slots and table nicknames for one DrawParam BND file (i.e. one map area).""" self._data = { } # type: Dict[str, List[Optional[DrawParamTable], Optional[DrawParamTable]]] self._bnd_entry_paths = {} # type: Dict[Tuple[str, int], str] paramdef_bnd = PARAMDEF_BND( 'dsr' if bool(draw_param_bnd.dcx) else 'ptde') if not isinstance(draw_param_bnd, BaseBND): draw_param_bnd = BND(draw_param_bnd) self._draw_param_bnd = draw_param_bnd for entry in draw_param_bnd: parts = entry.name[:-len('.param')].split('_') if len(parts) == 2: slot = 0 param_name = parts[1] elif len(parts) == 3: if parts[1] != '1': raise ValueError( "Only slot 0 (no slot number in path) and slot 1 ('_1_' in path) are valid in " "DrawParam.") slot = 1 param_name = parts[2] else: raise ValueError(f"Malformed ParamTable name: '{entry.name}'") if parts[0].startswith('s'): param_name = 's_' + param_name self._data.setdefault(param_name, [None, None])[slot] = DrawParamTable( entry.data, paramdef_bnd) self._bnd_entry_paths[param_name, slot] = entry.path try: param_nickname = PARAM_NICKNAMES[param_name] except KeyError: raise KeyError( f"Could not find nickname for DrawParam table: {param_name}" ) else: setattr(self, param_nickname, self._data[param_name])
def __init__(self, draw_param_bnd): """Structure that manages double-slots and table nicknames for one DrawParam BND file (i.e. one map area).""" self._data = { } # type: tp.Dict[str, tp.List[tp.Optional[DrawParamTable], tp.Optional[DrawParamTable]]] self._bnd_entry_paths = {} # type: tp.Dict[tp.Tuple[str, int], str] paramdef_bnd = PARAMDEF_BND( "dsr" if bool(draw_param_bnd.dcx) else "ptde") if not isinstance(draw_param_bnd, BaseBND): draw_param_bnd = BND(draw_param_bnd) self._draw_param_bnd = draw_param_bnd for entry in draw_param_bnd: parts = entry.name[:-len(".param")].split("_") if len(parts) == 2: slot = 0 param_name = parts[1] elif len(parts) == 3: if parts[1] != "1": raise ValueError( "Only slot 0 (no slot number in path) and slot 1 ('_1_' in path) are valid in " "DrawParam.") slot = 1 param_name = parts[2] else: raise ValueError(f"Malformed ParamTable name: '{entry.name}'") if parts[0].startswith("s"): param_name = "s_" + param_name self._data.setdefault(param_name, [None, None])[slot] = p = DrawParamTable( entry.data, paramdef_bnd) self._bnd_entry_paths[param_name, slot] = entry.path if p.param_info is not None: setattr(self, p.nickname, self._data[param_name])
class DarkSoulsGameParameters(object): AI: ParamTable Armor: ParamTable ArmorUpgrades: ParamTable Bosses: ParamTable Bullets: ParamTable Cameras: ParamTable Dialogue: ParamTable Faces: ParamTable Goods: ParamTable Players: ParamTable PlayerAttacks: ParamTable PlayerBehaviors: ParamTable ItemLots: ParamTable NonPlayers: ParamTable NonPlayerAttacks: ParamTable NonPlayerBehaviors: ParamTable MenuColors: ParamTable Movement: ParamTable Objects: ParamTable ObjectActivations: ParamTable Rings: ParamTable Shops: ParamTable SpecialEffects: ParamTable Spells: ParamTable Terrains: ParamTable Throws: ParamTable UpgradeMaterials: ParamTable Weapons: ParamTable WeaponUpgrades: ParamTable SpecialEffectVisuals: ParamTable param_names = [ 'Players', 'PlayerBehaviors', 'PlayerAttacks', 'NonPlayers', 'NonPlayerBehaviors', 'NonPlayerAttacks', 'AI', 'Bullets', 'Throws', 'SpecialEffects', 'Weapons', 'Armor', 'Rings', 'Goods', 'WeaponUpgrades', 'ArmorUpgrades', 'UpgradeMaterials', 'ItemLots', 'Bosses', 'Shops', 'Spells', 'Objects', 'ObjectActivations', 'Movement', 'Cameras', 'Terrains', 'Faces', 'Dialogue', 'MenuColors', 'SpecialEffectVisuals', ] def __init__(self, game_param_bnd_source=None): """Unpack DS1 GameParams into a single modifiable structure. Args: game_param_bnd_source: any valid source for GameParam.parambnd[.dcx] (its file path, the directory containing it, the unpacked BND directory, or an existing BND instance). It will default to the DEFAULT_GAME package. The appropriate bundled ParamDef will be loaded automatically, detected based on the DCX compression of the BND. """ self._reload_warning = True self._data = {} if game_param_bnd_source is None: self._game_param_bnd = None self.paramdef_bnd = None return if isinstance(game_param_bnd_source, BaseBND): self._game_param_bnd = game_param_bnd_source else: if isinstance(game_param_bnd_source, (str, Path)): game_param_bnd_source = Path(game_param_bnd_source) if game_param_bnd_source.is_dir(): if (game_param_bnd_source / 'GameParam.parambnd').is_file(): game_param_bnd_source = game_param_bnd_source / 'GameParam.parambnd' elif (game_param_bnd_source / 'GameParam.parambnd.dcx').is_file(): game_param_bnd_source = game_param_bnd_source / 'GameParam.parambnd.dcx' try: self._game_param_bnd = BND(game_param_bnd_source) except TypeError: raise TypeError( "Could not load DarkSoulsGameParameters from given source." ) self.paramdef_bnd = PARAMDEF_BND( 'dsr' if self._game_param_bnd.dcx else 'ptde') for entry in self._game_param_bnd: p = self._data[entry.path] = ParamTable(entry.data, self.paramdef_bnd) try: # Nickname assigned here (ParamTable isn't aware of its own basename). param_nickname = PARAM_NICKNAMES[entry.name[:-len('.param')]] except KeyError: # ParamTables without nicknames (i.e. useless params) are excluded from this structure. pass else: setattr( self, param_nickname, p ) # NOTE: Reference to dictionary in self._data[entry.path]. p.nickname = param_nickname def update_bnd(self): """Update the internal BND by packing the current ParamTables. Called automatically by `save()`.""" for param_table_entry_path, param_table in self._data.items(): self._game_param_bnd.entries_by_path[ param_table_entry_path].data = param_table.pack() def save(self, game_param_bnd_path=None, auto_pickle=False): """Save the DarkSoulsGameParameters. If no path is given, it will attempt to save to the same BND file.""" self.update_bnd() if auto_pickle: self.pickle() if game_param_bnd_path is not None and Path( game_param_bnd_path).is_dir(): game_param_bnd_path = Path( game_param_bnd_path) / 'GameParam.parambnd' self._game_param_bnd.write(game_param_bnd_path) _LOGGER.info( "Dark Souls game parameters (GameParam) written successfully.") if not self._reload_warning: _LOGGER.info("Remember to reload your game to see changes.") self._reload_warning = True def pickle(self, game_param_pickle_path=None): """Save the entire DarkSoulsGameParameters to a pickled file, which will be faster to load in future.""" if game_param_pickle_path is None: game_param_pickle_path = self._game_param_bnd.bnd_path if game_param_pickle_path is None: raise ValueError( "Could not automatically determine path to pickle DarkSoulsGameParameters." ) while game_param_pickle_path.suffix in {'.dcx', '.parambnd'}: game_param_pickle_path = game_param_pickle_path.parent / game_param_pickle_path.stem if not game_param_pickle_path.suffix != '.pickle': game_param_pickle_path = game_param_pickle_path.with_suffix( game_param_pickle_path.suffix + '.pickle') with Path(game_param_pickle_path).open('wb') as f: pickle.dump(self, f) def __getitem__(self, category) -> ParamTable: return getattr(self, category) def get_range(self, category, start, count): """Get a list of (id, entry) pairs from a certain range inside ID-sorted param dictionary.""" return self[category].get_range(start, count)
class DarkSoulsGameParameters: AI: ParamTable Armor: ParamTable ArmorUpgrades: ParamTable Bosses: ParamTable Bullets: ParamTable Cameras: ParamTable Characters: ParamTable Dialogue: ParamTable Faces: ParamTable Goods: ParamTable Players: ParamTable PlayerAttacks: ParamTable PlayerBehaviors: ParamTable ItemLots: ParamTable NonPlayerAttacks: ParamTable NonPlayerBehaviors: ParamTable MenuColors: ParamTable Movement: ParamTable Objects: ParamTable ObjectActivations: ParamTable Rings: ParamTable Shops: ParamTable SpecialEffects: ParamTable Spells: ParamTable Terrains: ParamTable Throws: ParamTable UpgradeMaterials: ParamTable Weapons: ParamTable WeaponUpgrades: ParamTable SpecialEffectVisuals: ParamTable GrowthCurves: ParamTable param_names = [ "Players", "Characters", "PlayerBehaviors", "PlayerAttacks", "NonPlayerBehaviors", "NonPlayerAttacks", "AI", "Bullets", "Throws", "SpecialEffects", "Weapons", "Armor", "Rings", "Goods", "WeaponUpgrades", "ArmorUpgrades", "UpgradeMaterials", "ItemLots", "Bosses", "Shops", "Spells", "Objects", "ObjectActivations", "Movement", "Cameras", "Terrains", "Faces", "Dialogue", "MenuColors", "SpecialEffectVisuals", "GrowthCurves", ] def __init__(self, game_param_bnd_source=None): """Unpack DS1 GameParams into a single modifiable structure. Args: game_param_bnd_source: any valid source for GameParam.parambnd[.dcx] (its file path, the directory containing it, the unpacked BND directory, or an existing BND instance). It will default to the DEFAULT_GAME package. The appropriate bundled ParamDef will be loaded automatically, detected based on the DCX compression of the BND. """ self._reload_warning = True self._data = {} if game_param_bnd_source is None: self._game_param_bnd = None self.paramdef_bnd = None return if isinstance(game_param_bnd_source, BaseBND): self._game_param_bnd = game_param_bnd_source else: if isinstance(game_param_bnd_source, (str, Path)): game_param_bnd_source = Path(game_param_bnd_source) if game_param_bnd_source.is_dir(): if (game_param_bnd_source / "GameParam.parambnd").is_file(): game_param_bnd_source = game_param_bnd_source / "GameParam.parambnd" elif (game_param_bnd_source / "GameParam.parambnd.dcx").is_file(): game_param_bnd_source = game_param_bnd_source / "GameParam.parambnd.dcx" try: self._game_param_bnd = BND(game_param_bnd_source) except TypeError: raise TypeError( "Could not load DarkSoulsGameParameters from given source." ) self.paramdef_bnd = PARAMDEF_BND( "dsr" if self._game_param_bnd.dcx else "ptde") for entry in self._game_param_bnd: p = self._data[entry.path] = ParamTable(entry.data, self.paramdef_bnd) if p.param_info is not None: if p.nickname is None: setattr(self, _AMBIGUOUS_NICKNAMES[entry.name], p) else: setattr(self, p.nickname, p) # shortcut attribute def update_bnd(self): """Update the internal BND by packing the current ParamTables. Called automatically by `save()`.""" for param_table_entry_path, param_table in self._data.items(): self._game_param_bnd.entries_by_path[ param_table_entry_path].data = param_table.pack() def save(self, game_param_bnd_path=None, auto_pickle=False): """Save the DarkSoulsGameParameters. If no path is given, it will attempt to save to the same BND file.""" self.update_bnd() if auto_pickle: self.pickle() if game_param_bnd_path is not None and Path( game_param_bnd_path).is_dir(): game_param_bnd_path = Path( game_param_bnd_path) / "GameParam.parambnd" self._game_param_bnd.write(game_param_bnd_path) _LOGGER.info( "Dark Souls game parameters (GameParam) written successfully.") if not self._reload_warning: _LOGGER.info("Remember to reload your game to see changes.") self._reload_warning = True def pickle(self, game_param_pickle_path=None): """Save the entire DarkSoulsGameParameters to a pickled file, which will be faster to load in future.""" if game_param_pickle_path is None: game_param_pickle_path = self._game_param_bnd.bnd_path if game_param_pickle_path is None: raise ValueError( "Could not automatically determine path to pickle DarkSoulsGameParameters." ) while game_param_pickle_path.suffix in {".dcx", ".parambnd"}: game_param_pickle_path = game_param_pickle_path.parent / game_param_pickle_path.stem if not game_param_pickle_path.suffix != ".pickle": game_param_pickle_path = game_param_pickle_path.with_suffix( game_param_pickle_path.suffix + ".pickle") with Path(game_param_pickle_path).open("wb") as f: pickle.dump(self, f) def __getitem__(self, param_nickname) -> ParamTable: return getattr(self, param_nickname) def get_range(self, category, start, count): """Get a list of (id, entry) pairs from a certain range inside ID-sorted param dictionary.""" return self[category].get_range(start, count) def rename_entries_from_text(self, text: DarkSoulsText, param_nickname=None): """Rename item param entries according to their (presumably more desirable) names in DS1 Text data. Args: text (DarkSoulsText): text data structure to pull names from. param_nickname (str or None): specific ParamTable name to rename, or None to rename all (default). Valid names are "Weapons", "Armor", "Rings", "Goods", and "Spells" (or None). """ if param_nickname: param_nickname = param_nickname.lower().rstrip("s") if param_nickname not in { "weapon", "armor", "ring", "good", "spell" }: raise ValueError( f"Invalid item type: {param_nickname}. Must be 'Weapons', 'Armor', 'Rings', " f"'Goods', or 'Spells'.") for item_type_check, param_table, text_dict in zip( ("weapon", "armor", "ring", "good", "spell"), (self.Weapons, self.Armor, self.Rings, self.Goods, self.Spells), (text.WeaponNames, text.ArmorNames, text.RingNames, text.GoodNames, text.SpellNames), ): if not param_nickname or param_nickname == item_type_check: for param_id, param_entry in param_table.items(): if param_id in text_dict: param_entry.name = text_dict[param_id]