예제 #1
0
    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
예제 #2
0
    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
예제 #3
0
    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
예제 #4
0
    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])
예제 #5
0
    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])
예제 #6
0
    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])
예제 #7
0
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)
예제 #8
0
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]