Example #1
0
class MSBEnvironmentEvent(MSBEvent):
    ENTRY_SUBTYPE = MSBEventSubtype.Environment
    EVENT_TYPE_DATA_STRUCT = BinaryStruct(
        ("unk_x00_x04", "i"),
        ("unk_x04_x08", "f"),
        ("unk_x08_x0c", "f"),
        ("unk_x0c_x10", "f"),
        ("unk_x10_x14", "f"),
        ("unk_x14_x18", "f"),
        "8x",
    )

    FIELD_INFO = MSBEvent.FIELD_INFO | {
        "base_part_name":
        MapFieldInfo(
            "Draw Parent",
            MapPart,
            None,
            "Environment (or 'map spot') will be drawn whenever its parent is drawn.",
        ),
        "base_region_name":
        MapFieldInfo(
            "Environment Region",
            Region,
            None,
            "Region (usually a Point) at which Environment appears (whatever that means).",
        ),
        "entity_id":
        MapFieldInfo(
            "Environment ID",
            int,
            -1,
            "Unknown index. Note that this replaces the usual Entity ID field.",
        ),
        "unk_x00_x04":
        MapFieldInfo(
            "Unknown [00-04]",
            int,
            0,
            "Unknown Environment parameter (integer).",
        ),
        "unk_x04_x08":
        MapFieldInfo(
            "Unknown [04-08]",
            float,
            1.0,
            "Unknown Environment parameter (floating-point number).",
        ),
        "unk_x08_x0c":
        MapFieldInfo(
            "Unknown [08-0c]",
            float,
            1.0,
            "Unknown Environment parameter (floating-point number).",
        ),
        "unk_x0c_x10":
        MapFieldInfo(
            "Unknown [0c-10]",
            float,
            1.0,
            "Unknown Environment parameter (floating-point number).",
        ),
        "unk_x10_x14":
        MapFieldInfo(
            "Unknown [10-14]",
            float,
            1.0,
            "Unknown Environment parameter (floating-point number).",
        ),
        "unk_x14_x18":
        MapFieldInfo(
            "Unknown [14-18]",
            float,
            1.0,
            "Unknown Environment parameter (floating-point number).",
        ),
    }

    FIELD_ORDER = (
        "entity_id",
        "base_part_name",
        "base_region_name",
        "unk_x00_x04",
        "unk_x04_x08",
        "unk_x08_x0c",
        "unk_x0c_x10",
        "unk_x10_x14",
        "unk_x14_x18",
    )

    unk_x00_x04: int
    unk_x04_x08: float
    unk_x08_x0c: float
    unk_x0c_x10: float
    unk_x10_x14: float
    unk_x14_x18: float
Example #2
0
class MSBPlatoonEvent(MSBEvent):
    """Defines a group (platoon) of enemies."""

    ENTRY_SUBTYPE = MSBEventSubtype.Platoon
    EVENT_TYPE_DATA_STRUCT = BinaryStruct(
        ("platoon_id_script_active", "i"),
        ("state", "i"),
        "16x",
        ("_platoon_character_indices", "30i"),
        ("_platoon_parent_indices", "2i"),
    )

    FIELD_INFO = MSBEvent.FIELD_INFO | {
        "base_part_name":
        MapFieldInfo(
            "Draw Parent",
            MapPart,
            None,
            "Probably unused for Platoon.",
        ),
        "base_region_name":
        MapFieldInfo(
            "Base Region",
            Region,
            None,
            "Probably unused for Platoon.",
        ),
        "entity_id":
        MapFieldInfo(
            "Entity ID",
            int,
            -1,
            "Probably unused for Platoon.",
        ),
        "platoon_character_names":
        MapFieldInfo(
            "Platoon Character Names",
            GameObjectSequence((Character, 30)),
            [None] * 30,
            "Characters in this Platoon.",
        ),
        "platoon_parent_names":
        MapFieldInfo(
            "Platoon Parent Names",
            GameObjectSequence((MapPart, 2)),
            [None] * 2,
            "Parent parts of this Platoon.",
        ),
        "platoon_id_script_active":
        MapFieldInfo(
            "Platoon Active Script ID",
            int,
            -1,
            "Unknown. Possibly an AI param ID.",
        ),
        "state":
        MapFieldInfo(
            "Platoon State",
            int,
            -1,
            "Unknown.",
        )
    }

    FIELD_ORDER = (
        "entity_id",
        "base_part_name",
        "base_region_name",
        "platoon_character_names",
        "platoon_parent_names",
        "platoon_id_script_active",
        "state",
    )

    def __init__(self, source, **kwargs):
        self._platoon_character_names = [None] * 30
        self._platoon_character_indices = [-1] * 30
        self._platoon_parent_names = [None] * 2
        self._platoon_parent_indices = [-1] * 2
        super().__init__(source=source, **kwargs)

    @property
    def platoon_character_names(self):
        return self._platoon_character_names

    @platoon_character_names.setter
    def platoon_character_names(self, value):
        """Pads out to 30 names with `None`. Also replaces empty strings with `None`."""
        names = []
        for v in value:
            if v is not None and not isinstance(v, str):
                raise TypeError(
                    "Platoon character names must be strings or `None`.")
            names.append(v if v else None)
        self._platoon_character_names = value
        while len(self._platoon_character_names) < 30:
            self._platoon_character_names.append(None)

    @property
    def platoon_parent_names(self):
        return self._platoon_parent_names

    @platoon_parent_names.setter
    def platoon_parent_names(self, value):
        """Pads out to 2 names with `None`. Also replaces empty strings with `None`."""
        names = []
        for v in value:
            if v is not None and not isinstance(v, str):
                raise TypeError(
                    "Platoon parent names must be strings or `None`.")
            names.append(v if v else None)
        self._platoon_parent_names = value
        while len(self._platoon_parent_names) < 2:
            self._platoon_parent_names.append(None)

    def set_indices(self, event_index, local_event_index, region_indices,
                    part_indices):
        super().set_indices(event_index, local_event_index, region_indices,
                            part_indices)
        self._platoon_character_indices = [
            part_indices[n] if n else -1 for n in self._platoon_character_names
        ]
        while len(self._platoon_character_indices) < 30:
            self._platoon_character_indices.append(-1)
        self._platoon_parent_indices = [
            part_indices[n] if n else -1 for n in self._platoon_parent_names
        ]
        while len(self._platoon_parent_indices) < 2:
            self._platoon_parent_indices.append(-1)

    def set_names(self, region_names, part_names):
        super().set_names(region_names, part_names)
        self._platoon_character_names = [
            part_names[i] if i != -1 else None
            for i in self._platoon_character_indices
        ]
        while len(self._platoon_character_names) < 30:
            self._platoon_character_names.append(None)
        self._platoon_parent_names = [
            part_names[i] if i != -1 else None
            for i in self._platoon_parent_indices
        ]
        while len(self._platoon_parent_names) < 2:
            self._platoon_parent_names.append(None)
Example #3
0
class MSBMultiSummonEvent(MSBEvent):
    ENTRY_SUBTYPE = MSBEventSubtype.MultiSummon
    EVENT_TYPE_DATA_STRUCT = BinaryStruct(
        ("unk_x00_x04", "i"),
        ("unk_x04_x06", "h"),
        ("unk_x06_x08", "h"),
        ("unk_x08_x0a", "h"),
        ("unk_x0a_x0c", "h"),
        "4x",
    )

    FIELD_INFO = MSBEvent.FIELD_INFO | {
        "base_part_name":
        MapFieldInfo(
            "Draw Parent",
            MapPart,
            None,
            "Probably unused for Multi Summon.",
        ),
        "base_region_name":
        MapFieldInfo(
            "Base Region",
            Region,
            None,
            "Probably unused for Multi Summon.",
        ),
        "entity_id":
        MapFieldInfo(
            "Entity ID",
            int,
            -1,
            "Probably unused for Multi Summon.",
        ),
        "unk_x00_x04":
        MapFieldInfo(
            "Unknown [00-04]",
            int,
            1,
            "Unknown integer.",
        ),
        "unk_x04_x06":
        MapFieldInfo(
            "Unknown [04-06]",
            int,
            1,
            "Unknown 16-bit integer.",
        ),
        "unk_x06_x08":
        MapFieldInfo(
            "Unknown [06-08]",
            int,
            1,
            "Unknown 16-bit integer.",
        ),
        "unk_x08_x0a":
        MapFieldInfo(
            "Unknown [08-0a]",
            int,
            1,
            "Unknown 16-bit integer.",
        ),
        "unk_x0a_x0c":
        MapFieldInfo(
            "Unknown [0a-0c]",
            int,
            1,
            "Unknown 16-bit integer.",
        ),
    }

    FIELD_ORDER = (
        "entity_id",
        "base_part_name",
        "base_region_name",
        "unk_x00_x04",
        "unk_x04_x06",
        "unk_x06_x08",
        "unk_x08_x0a",
        "unk_x0a_x0c",
    )

    unk_x00_x04: int
    unk_x04_x06: int
    unk_x06_x08: int
    unk_x08_x0a: int
    unk_x0a_x0c: int
Example #4
0
class MSBPart(_BaseMSBPart, abc.ABC):
    PART_HEADER_STRUCT = BinaryStruct(
        ("__description_offset", "q"),
        ("__name_offset", "q"),
        ("_instance_index", "i"),  # TK says "Unknown; appears to count up with each instance of a model added."
        ("__part_type", "i"),
        ("_part_type_index", "i"),
        ("_model_index", "i"),
        ("__sib_path_offset", "q"),
        ("translate", "3f"),
        ("rotate", "3f"),
        ("scale", "3f"),
        ("__draw_groups", "8I"),
        ("__display_groups", "8I"),
        ("__backread_groups", "8I"),
        "4x",
        ("__base_data_offset", "q"),
        ("__type_data_offset", "q"),
        ("__gparam_data_offset", "q"),
        ("__scene_gparam_data_offset", "q"),
    )
    PART_BASE_DATA_STRUCT = BinaryStruct(
        ("entity_id", "i"),
        ("base_unk_x04_x05", "b"),
        ("base_unk_x05_x06", "b"),
        ("base_unk_x06_x07", "b"),
        ("base_unk_x07_x08", "b"),
        "4x",
        ("lantern_id", "b"),
        ("lod_id", "b"),
        ("base_unk_x0e_x0f", "b"),
        ("base_unk_x0f_x10", "b"),
    )
    NAME_ENCODING = "utf-16-le"
    FLAG_SET_SIZE = 256

    FIELD_INFO = _BaseMSBPart.FIELD_INFO | {
        "draw_groups": MapFieldInfo(
            "Draw Groups",
            list,
            set(range(FLAG_SET_SIZE)),
            "Draw groups of part. This part will be drawn when the corresponding display group is active.",
        ),
        "display_groups": MapFieldInfo(
            "Display Groups",
            list,
            set(range(FLAG_SET_SIZE)),
            "Display groups are present in all MSB Parts, but only function for collisions.",
        ),
        "backread_groups": MapFieldInfo(
            "Backread Groups",
            list,
            set(range(FLAG_SET_SIZE)),
            "Backread groups are present in all MSB Parts, but only function for collisions (probably).",
        ),
        "base_unk_x04_x05": MapFieldInfo(
            "Unknown Base [04-05]",
            int,
            -1,
            "Unknown base data integer.",
        ),
        "base_unk_x05_x06": MapFieldInfo(
            "Unknown Base [05-06]",
            int,
            -1,
            "Unknown base data integer.",
        ),
        "base_unk_x06_x07": MapFieldInfo(
            "Unknown Base [06-07]",
            int,
            -1,
            "Unknown base data integer.",
        ),
        "base_unk_x07_x08": MapFieldInfo(
            "Unknown Base [07-08]",
            int,
            -1,
            "Unknown base data integer.",
        ),
        "lantern_id": MapFieldInfo(
            "Lantern ID",
            int,
            0,
            "Lantern param ID.",
        ),
        "lod_id": MapFieldInfo(
            "LoD ID",
            int,
            -1,
            "LoD (level of detail) param ID.",
        ),
        "base_unk_x0e_x0f": MapFieldInfo(
            "Unknown Base [0e-0f]",
            int,
            20,
            "Unknown base data integer.",
        ),
        "base_unk_x0f_x10": MapFieldInfo(
            "Unknown Base [0f-10]",
            int,
            0,
            "Unknown base data integer.",
        ),
    }

    FIELD_ORDER = (
        "base_unk_x04_x05",
        "base_unk_x05_x06",
        "base_unk_x06_x07",
        "base_unk_x07_x08",
        "lantern_id",
        "lod_id",
        "base_unk_x0e_x0f",
        "base_unk_x0f_x10",
    )

    base_unk_x04_x05: int
    base_unk_x05_x06: int
    base_unk_x06_x07: int
    base_unk_x07_x08: int
    lantern_id: int
    lod_id: int
    base_unk_x0e_x0f: int
    base_unk_x0f_x10: int

    def __init__(self, source=None, **kwargs):
        self._instance_index = 0
        self._draw_groups = set()
        self._display_groups = set()
        self._backread_groups = set()
        super().__init__(source, **kwargs)

    def unpack(self, msb_reader: BinaryReader):
        part_offset = msb_reader.position

        header = msb_reader.unpack_struct(self.PART_HEADER_STRUCT)
        if header["__part_type"] != self.ENTRY_SUBTYPE:
            raise ValueError(f"Unexpected part type enum {header['part_type']} for class {self.__class__.__name__}.")
        self._instance_index = header["_instance_index"]
        self._model_index = header["_model_index"]
        self._part_type_index = header["_part_type_index"]
        for transform in ("translate", "rotate", "scale"):
            setattr(self, transform, Vector3(header[transform]))
        self._draw_groups = int_group_to_bit_set(header["__draw_groups"], assert_size=8)
        self._display_groups = int_group_to_bit_set(header["__display_groups"], assert_size=8)
        self._backread_groups = int_group_to_bit_set(header["__backread_groups"], assert_size=8)
        self.description = msb_reader.unpack_string(
            offset=part_offset + header["__description_offset"], encoding="utf-16-le",
        )
        self.name = msb_reader.unpack_string(
            offset=part_offset + header["__name_offset"], encoding="utf-16-le",
        )
        self.sib_path = msb_reader.unpack_string(
            offset=part_offset + header["__sib_path_offset"], encoding="utf-16-le",
        )

        msb_reader.seek(part_offset + header["__base_data_offset"])
        base_data = msb_reader.unpack_struct(self.PART_BASE_DATA_STRUCT)
        self.set(**base_data)

        msb_reader.seek(part_offset + header["__type_data_offset"])
        self.unpack_type_data(msb_reader)

        self._unpack_gparam_data(msb_reader, part_offset, header)
        self._unpack_scene_gparam_data(msb_reader, part_offset, header)

    def pack(self) -> bytes:
        """Pack to bytes, presumably as part of a full `MSB` pack."""

        # Validate draw/display/backread groups before doing any real work.
        draw_groups = bit_set_to_int_group(self._draw_groups, group_size=8)
        display_groups = bit_set_to_int_group(self._display_groups, group_size=8)
        backread_groups = bit_set_to_int_group(self._backread_groups, group_size=8)

        description_offset = self.PART_HEADER_STRUCT.size
        packed_description = self.description.encode("utf-16-le") + b"\0\0"
        name_offset = description_offset + len(packed_description)
        packed_name = self.get_name_to_pack().encode("utf-16-le") + b"\0\0"
        sib_path_offset = name_offset + len(packed_name)
        packed_sib_path = self.sib_path.encode("utf-16-le") + b"\0\0" if self.sib_path else b"\0\0"
        strings_size = len(packed_description + packed_name + packed_sib_path)
        if strings_size <= 0x38:
            packed_sib_path += b"\0" * (0x3c - strings_size)
        else:
            packed_sib_path += b"\0" * 8
        while len(packed_description + packed_name + packed_sib_path) % 4 != 0:
            packed_sib_path += b"\0"  # Not done in SoulsFormats, but makes sense to me.

        base_data_offset = sib_path_offset + len(packed_sib_path)
        packed_base_data = self.PART_BASE_DATA_STRUCT.pack(self)
        type_data_offset = base_data_offset + len(packed_base_data)
        packed_type_data = self.pack_type_data()
        gparam_data_offset = type_data_offset + len(packed_type_data)
        packed_gparam_data = self._pack_gparam_data()
        scene_gparam_data_offset = gparam_data_offset + len(packed_gparam_data)
        packed_scene_gparam_data = self._pack_scene_gparam_data()
        try:
            packed_header = self.PART_HEADER_STRUCT.pack(
                __description_offset=description_offset,
                __name_offset=name_offset,
                _instance_index=self._instance_index,
                __part_type=self.ENTRY_SUBTYPE,
                _part_type_index=self._part_type_index,
                _model_index=self._model_index,
                __sib_path_offset=sib_path_offset,
                translate=list(self.translate),
                rotate=list(self.rotate),
                scale=list(self.scale),
                __draw_groups=draw_groups,
                __display_groups=display_groups,
                __backread_groups=backread_groups,
                __base_data_offset=base_data_offset,
                __type_data_offset=type_data_offset,
                __gparam_data_offset=gparam_data_offset,
                __scene_gparam_data_offset=scene_gparam_data_offset,
            )
        except struct.error:
            raise MapError(f"Could not pack header data of MSB part '{self.name}'. See traceback.")
        return (
            packed_header + packed_description + packed_name + packed_sib_path
            + packed_base_data + packed_type_data + packed_gparam_data + packed_scene_gparam_data
        )

    @property
    def backread_groups(self):
        return self._backread_groups

    @backread_groups.setter
    def backread_groups(self, value):
        """Converts value to a `set()` (possibly empty) and validates index range."""
        if value is None or isinstance(value, str) and value in {"None", ""}:
            self._display_groups = set()
            return
        try:
            display_groups = set(value)
        except (TypeError, ValueError):
            raise TypeError(
                "Backread groups must be a set, sequence, `None`, 'None', or ''. Or use `set` methods like `.add()`."
            )
        for i in display_groups:
            if not isinstance(i, int) and 0 <= i < self.FLAG_SET_SIZE:
                raise ValueError(f"Invalid backread group: {i}. Must be 0 <= i < {self.FLAG_SET_SIZE}.")
        self._display_groups = display_groups

    def _unpack_gparam_data(self, msb_reader: BinaryReader, part_offset, header):
        pass

    def _pack_gparam_data(self):
        return b""

    def _unpack_scene_gparam_data(self, msb_reader: BinaryReader, part_offset, header):
        pass

    def _pack_scene_gparam_data(self):
        return b""
Example #5
0
class MSBPatrolRouteEvent(MSBEvent):
    """Defines a patrol route through a sequence of up to 32 regions."""

    ENTRY_SUBTYPE = MSBEventSubtype.PatrolRoute
    EVENT_TYPE_DATA_STRUCT = BinaryStruct(
        ("unk_x00_x04", "i"),
        "12x",
        ("_patrol_region_indices", "32h"),
    )

    FIELD_INFO = MSBEvent.FIELD_INFO | {
        "base_part_name":
        MapFieldInfo(
            "Draw Parent",
            MapPart,
            None,
            "Probably unused for Patrol Route.",
        ),
        "base_region_name":
        MapFieldInfo(
            "Base Region",
            Region,
            None,
            "Probably unused for Patrol Route.",
        ),
        "entity_id":
        MapFieldInfo(
            "Entity ID",
            int,
            -1,
            "Probably unused for Patrol Route.",
        ),
        "unk_x00_x04":
        MapFieldInfo(
            "Unknown [00-04]",
            int,
            1,
            "Unknown integer.",
        ),
        "patrol_region_names":
        MapFieldInfo("Patrol Regions", GameObjectSequence(
            (Region, 32)), [None] * 32,
                     "List of regions that define this Patrol Route."),
    }

    FIELD_ORDER = (
        "entity_id",
        "base_part_name",
        "base_region_name",
        "unk_x00_x04",
        "patrol_region_names",
    )

    unk_x00_x04: int

    def __init__(self, source, **kwargs):
        self._patrol_region_names = [None] * 32
        self._patrol_region_indices = [-1] * 32
        super().__init__(source=source, **kwargs)

    @property
    def patrol_region_names(self):
        return self._patrol_region_names

    @patrol_region_names.setter
    def patrol_region_names(self, value):
        """Pads out to 32 names with `None`. Also replaces empty strings with `None`."""
        names = []
        for v in value:
            if v is not None and not isinstance(v, str):
                raise TypeError(
                    "Patrol point names must be strings or `None`.")
            names.append(v if v else None)
        self._patrol_region_names = value
        while len(self._patrol_region_names) < 32:
            self._patrol_region_names.append(None)

    def set_indices(self, event_index, local_event_index, region_indices,
                    part_indices):
        super().set_indices(event_index, local_event_index, region_indices,
                            part_indices)
        self._patrol_region_indices = [
            region_indices[n] if n else -1 for n in self._patrol_region_names
        ]
        while len(self._patrol_region_indices) < 32:
            self._patrol_region_indices.append(-1)

    def set_names(self, region_names, part_names):
        super().set_names(region_names, part_names)
        self._patrol_region_names = [
            region_names[i] if i != -1 else None
            for i in self._patrol_region_indices
        ]
        while len(self._patrol_region_names) < 32:
            self._patrol_region_names.append(None)
Example #6
0
class MSBTreasureEvent(MSBEvent):
    ENTRY_SUBTYPE = MSBEventSubtype.Treasure

    EVENT_TYPE_DATA_STRUCT = BinaryStruct(
        "4x",
        ("_treasure_part_index", "i"),
        ("item_lot_1", "i"),
        ("minus_one_1", "i", -1),
        ("item_lot_2", "i"),
        ("minus_one_2", "i", -1),
        ("item_lot_3", "i"),
        ("minus_one_3", "i", -1),
        ("item_lot_4", "i"),
        ("minus_one_4", "i", -1),
        ("item_lot_5", "i"),
        ("minus_one_5", "i", -1),
        ("is_in_chest", "?"),
        ("is_hidden", "?"),
        "2x",
    )

    FIELD_INFO = MSBEvent.FIELD_INFO | {
        "treasure_part_name":
        MapFieldInfo(
            "Treasure Object",
            Object,
            None,
            "Object on which treasure will appear (usually a corpse or chest).",
        ),
        "item_lot_1":
        MapFieldInfo(
            "Item Lot 1",
            ItemLotParam,
            -1,
            "First item lot of treasure. (Note that the item lots that are +1 to +5 from this ID will also be "
            "awarded.)",
        ),
        "item_lot_2":
        MapFieldInfo(
            "Item Lot 2",
            ItemLotParam,
            -1,
            "Second item lot of treasure. (Note that the item lots that are +1 to +5 from this ID will also be "
            "awarded.)",
        ),
        "item_lot_3":
        MapFieldInfo(
            "Item Lot 3",
            ItemLotParam,
            -1,
            "Third item lot of treasure. (Note that the item lots that are +1 to +5 from this ID will also be "
            "awarded.)",
        ),
        "is_in_chest":
        MapFieldInfo(
            "Is In Chest",
            bool,
            False,
            "Indicates if this treasure is inside a chest (affects appearance).",  # TODO: effect?
        ),
        "is_hidden":
        MapFieldInfo(
            "Is Hidden",
            bool,
            False,
            "If True, this treasure will start disabled and will need to be enabled manually with an event script.",
        ),
        "item_lot_4":
        MapFieldInfo(
            "Item Lot 4",
            ItemLotParam,
            -1,
            "Fourth item lot of treasure. (Note that the item lots that are +1 to +5 from this ID will also be "
            "awarded.)",
        ),
        "item_lot_5":
        MapFieldInfo(
            "Item Lot 5",
            ItemLotParam,
            -1,
            "Fifth item lot of treasure. (Note that the item lots that are +1 to +5 from this ID will also be "
            "awarded.)",
        ),
    }

    FIELD_ORDER = (
        "treasure_part_name",
        "item_lot_1",
        "item_lot_2",
        "item_lot_3",
        "item_lot_4",
        "item_lot_5",
        "is_in_chest",
        "is_hidden",
    )

    REFERENCE_FIELDS = {
        "parts": ["base_part_name", "treasure_part_name"],
        "regions": ["base_region_name"]
    }

    treasure_part_name: tp.Optional[str]
    item_lot_1: int
    item_lot_2: int
    item_lot_3: int
    is_in_chest: bool
    is_hidden: bool

    def __init__(self, source=None, **kwargs):
        self._treasure_part_index = None
        self._treasure_part_name = None
        self.item_lot_1 = -1
        self.item_lot_2 = -1
        self.item_lot_3 = -1
        self.item_lot_4 = -1
        self.item_lot_5 = -1
        super().__init__(source=source, **kwargs)

    @property
    def treasure_part_name(self):
        return self._treasure_part_name

    @treasure_part_name.setter
    def treasure_part_name(self, value: tp.Union[None, str]):
        if isinstance(value, str):
            self._treasure_part_name = value if value else None
        elif value is None:
            self._treasure_part_name = None
        else:
            raise TypeError(
                f"`treasure_part_name` must be a string or `None`, not {value}."
            )

    def set_indices(self, indices: EventsIndicesData):
        super().set_indices(indices)
        self._treasure_part_index = indices.part_indices[
            self._treasure_part_name] if self.treasure_part_name else -1

    def set_names(self, names: EventsNamesData):
        super().set_names(names)
        self._treasure_part_name = (names.part_names[self._treasure_part_index]
                                    if self._treasure_part_index != -1 else
                                    None)
Example #7
0
class MSBMessageEvent(MSBEvent):
    ENTRY_SUBTYPE = MSBEventSubtype.Message
    EVENT_TYPE_DATA_STRUCT = BinaryStruct(
        ("text_id", "h"),
        ("unk_x02_x04", "h"),
        ("is_hidden", "?"),
        "3x",
    )

    FIELD_INFO = MSBEvent.FIELD_INFO | {
        "base_part_name":
        MapFieldInfo(
            "Draw Parent",
            MapPart,
            None,
            "Message will be drawn as long as this parent (usually a Collision or Map Piece part) is drawn.",
        ),
        "base_region_name":
        MapFieldInfo(
            "Message Region",
            Region,
            None,
            "Region (usually a Point) at which Message appears.",
        ),
        "entity_id":
        MapFieldInfo(
            "Entity ID",
            int,
            -1,
            "Entity ID used to refer to the Message in other game files.",
        ),
        "text_id":
        MapFieldInfo(
            "Message Text ID",
            SoapstoneMessage,
            -1,
            "Soapstone Messages text ID shown when soapstone message is examined.",
        ),
        "unk_x02_x04":
        MapFieldInfo(
            "Unknown [02-04]",
            int,
            2,
            "Unknown. Often set to 2.",
        ),
        "is_hidden":
        MapFieldInfo(
            "Is Hidden",
            bool,
            False,
            "If True, the message must be manually enabled with an event script or by using Seek Guidance.",
        ),
    }

    FIELD_ORDER = (
        "entity_id",
        "base_part_name",
        "base_region_name",
        "text_id",
        "unk_x02_x04",
        "is_hidden",
    )

    text_id: int
    unk_x02_x04: int
    is_hidden: bool
Example #8
0
class MSBObject(_BaseMSBObject, MSBPart):
    PART_TYPE_DATA_STRUCT = BinaryStruct(
        "4x",
        ("_draw_parent_index", "i"),
        ("break_term", "b"),
        ("net_sync_type", "b"),
        "2x",
        ("default_animation", "h"),
        ("unk_x0e_x10", "h"),
        ("unk_x10_x14", "i"),
        "4x",
    )

    FIELD_INFO = MSBPart.FIELD_INFO | _BaseMSBObject.FIELD_INFO | {
        "break_term": MapFieldInfo(
            "Break Term",
            int,
            0,
            "Unknown. Related to object breakage.",
        ),
        "net_sync_type": MapFieldInfo(
            "Net Sync Type",
            int,
            0,
            "Unknown. Related to online object synchronization.",
        ),
        "default_animation": MapFieldInfo(
            "Default Animation",
            int,  # TODO: Animation
            0,
            "Object animation ID to auto-play on map load, e.g. for different corpse poses.",
        ),
        "unk_x0e_x10": MapFieldInfo(
            "Unknown [0e-10]",
            int,
            0,
            "Unknown.",
        ),
        "unk_x10_x14": MapFieldInfo(
            "Unknown [10-14]",
            int,
            0,
            "Unknown.",
        ),
    }

    FIELD_ORDER = (
        "model_name",
        "entity_id",
        "translate",
        "rotate",
        # "scale",
        "draw_parent_name",
        "draw_groups",
        # "display_groups",
        "break_term",
        "net_sync_type",
        "default_animation",
        # "unk_x0e_x10",
        # "unk_x10_x14",
    ) + MSBPart.LIGHTING_FIELD_ORDER + (
        "is_shadow_source",
        "is_shadow_destination",
        "is_shadow_only",
        "draw_by_reflect_cam",
        "draw_only_reflect_cam",
        # "use_depth_bias_float",
        "disable_point_light_effect",
    )

    break_term: int
    net_sync_type: int
    default_animation: int
    unk_x0e_x10: int
    unk_x10_x14: int

    def __init__(self, source=None, **kwargs):
        if source is None:
            # Set some defaults.
            kwargs.setdefault("is_shadow_source", True)
            kwargs.setdefault("is_shadow_destination", True)
            kwargs.setdefault("draw_by_reflect_cam", True)
        super().__init__(source=source, **kwargs)
Example #9
0
class MSBPart(_BaseMSBPart):
    PART_HEADER_STRUCT = BinaryStruct(
        ("__name_offset", "i"),
        ("__part_type", "i"),
        ("_part_type_index", "i"),
        ("_model_index", "I"),
        ("__sib_path_offset", "i"),
        ("translate", "3f"),
        ("rotate", "3f"),
        ("scale", "3f"),
        ("__draw_groups", "4I"),
        ("__display_groups", "4I"),
        ("__base_data_offset", "i"),
        ("__type_data_offset", "i"),
        "4x",
    )
    PART_BASE_DATA_STRUCT = BinaryStruct(
        ("entity_id", "i"),
        ("ambient_light_id", "b"),
        ("fog_id", "b"),
        ("scattered_light_id", "b"),
        ("lens_flare_id", "b"),
        ("shadow_id", "b"),
        ("dof_id", "b"),
        ("tone_map_id", "b"),
        ("tone_correction_id", "b"),
        ("point_light_id", "b"),
        ("lod_id", "b"),
        "x",
        ("is_shadow_source", "?"),
        ("is_shadow_destination", "?"),
        ("is_shadow_only", "?"),
        ("draw_by_reflect_cam", "?"),
        ("draw_only_reflect_cam", "?"),
        ("use_depth_bias_float", "?"),
        ("disable_point_light_effect", "?"),
        "2x",
    )
    NAME_ENCODING = "shift-jis"
    FLAG_SET_SIZE = 128

    FIELD_INFO = _BaseMSBPart.FIELD_INFO | {
        "draw_groups": MapFieldInfo(
            "Draw Groups",
            list,
            set(range(FLAG_SET_SIZE)),
            "Draw groups of part. This part will be drawn when the corresponding display group is active.",
        ),
        "display_groups": MapFieldInfo(
            "Display Groups",
            list,
            set(range(FLAG_SET_SIZE)),
            "Display groups are present in all MSB Parts, but only function for collisions.",
        ),
        "ambient_light_id": MapFieldInfo(
            "Ambient Light ID",
            AmbientLightParam,
            0,
            "ID of Ambient Light parameter to use from this map's lighting parameters (DrawParam).",
        ),
        "fog_id": MapFieldInfo(
            "Fog ID",
            FogParam,
            0,
            "ID of Fog parameter to use from this map's lighting parameters (DrawParam).",
        ),
        "scattered_light_id": MapFieldInfo(
            "Scattered Light ID",
            ScatteredLightParam,
            0,
            "ID of Scattered Light parameter to use from this map's lighting parameters (DrawParam).",
        ),
        "lens_flare_id": MapFieldInfo(
            "Lens Flare ID",
            LensFlareParam,
            0,
            "ID of Lens Flare parameter (both types) to use from this map's lighting parameters (DrawParam).",
        ),
        "shadow_id": MapFieldInfo(
            "Shadow ID",
            ShadowParam,
            0,
            "ID of Shadow parameter to use from this map's lighting parameters (DrawParam).",
        ),
        "dof_id": MapFieldInfo(
            "Depth of Field ID",
            DepthOfFieldParam,
            0,
            "ID of Depth Of Field ID parameter to use from this map's lighting parameters (DrawParam).",
        ),
        "tone_map_id": MapFieldInfo(
            "Tone Map ID",
            ToneMappingParam,
            0,
            "ID of Tone Map parameter to use from this map's lighting parameters (DrawParam).",
        ),
        "point_light_id": MapFieldInfo(
            "Point Light ID",
            PointLightParam,
            0,
            "ID of Point Light parameter to use from this map's lighting parameters (DrawParam).",
        ),
        "tone_correction_id": MapFieldInfo(
            "Tone Correction ID",
            ToneCorrectionParam,
            0,
            "ID of Tone Correction parameter to use from this map's lighting parameters (DrawParam).",
        ),
        "lod_id": MapFieldInfo(
            "Level of Detail ID",
            int,
            0,  # only ever 0 or -1, seemingly at random
            "Level of Detail (LoD) parameter. Always -1 or 0, probably unused.",
        ),
        "is_shadow_source": MapFieldInfo(
            "Casts Shadow",
            bool,
            False,
            "If True, this entity will cast dynamic shadows.",
        ),
        "is_shadow_destination": MapFieldInfo(
            "Receives Shadow",
            bool,
            False,
            "If True, this entity can have dynamic shadows cast onto it.",
        ),
        "is_shadow_only": MapFieldInfo(
            "Only Casts Shadow",
            bool,
            False,
            "If True, this entity only casts shadows.",
        ),
        "draw_by_reflect_cam": MapFieldInfo(
            "Is Reflected",
            bool,
            False,
            "If True, this entity will be reflected in water, etc.",
        ),
        "draw_only_reflect_cam": MapFieldInfo(
            "Is Only Reflected",
            bool,
            False,
            "If True, this entity will only be drawn in reflections in water, etc.",
        ),
        "use_depth_bias_float": MapFieldInfo(
            "Use Depth Bias Float",
            bool,
            False,
            "Unknown.",
        ),
        "disable_point_light_effect": MapFieldInfo(
            "Ignore Point Lights",
            bool,
            False,
            "If True, this entity will not be illuminated by point lights (I think).",
        ),
    }

    LIGHTING_FIELD_ORDER = (
        "ambient_light_id",
        "fog_id",
        "scattered_light_id",
        "lens_flare_id",
        "shadow_id",
        "dof_id",
        "tone_map_id",
        "point_light_id",
        "tone_correction_id",
        # "lod_id",
    )

    entity_id: int
    translate: Vector3
    rotate: Vector3
    scale: Vector3
    draw_groups: set[int]
    display_groups: set[int]
    ambient_light_id: int
    fog_id: int
    scattered_light_id: int
    lens_flare_id: int
    shadow_id: int
    dof_id: int
    tone_map_id: int
    point_light_id: int
    tone_correction_id: int
    lod_id: int
    is_shadow_source: bool
    is_shadow_destination: bool
    is_shadow_only: bool
    draw_by_reflect_cam: bool
    draw_only_reflect_cam: bool
    use_depth_bias_float: bool
    disable_point_light_effect: bool

    def unpack(self, msb_buffer):
        part_offset = msb_buffer.tell()
        header = self.PART_HEADER_STRUCT.unpack(msb_buffer)
        if header["__part_type"] != self.ENTRY_SUBTYPE:
            raise ValueError(f"Unexpected part type enum {header['part_type']} for class {self.__class__.__name__}.")
        self._model_index = header["_model_index"]
        self._part_type_index = header["_part_type_index"]
        for transform in ("translate", "rotate", "scale"):
            setattr(self, transform, Vector3(getattr(header, transform)))
        self._draw_groups = int_group_to_bit_set(header["__draw_groups"], assert_size=4)
        self._display_groups = int_group_to_bit_set(header["__display_groups"], assert_size=4)
        self.name = read_chars_from_buffer(
            msb_buffer, offset=part_offset + header["__name_offset"], encoding=self.NAME_ENCODING
        )
        self.sib_path = read_chars_from_buffer(
            msb_buffer, offset=part_offset + header["__sib_path_offset"], encoding=self.NAME_ENCODING
        )
        msb_buffer.seek(part_offset + header["__base_data_offset"])
        base_data = self.PART_BASE_DATA_STRUCT.unpack(msb_buffer)
        self.set(**base_data)
        msb_buffer.seek(part_offset + header["__type_data_offset"])
        self.unpack_type_data(msb_buffer)

    def pack(self):
        """Pack to bytes, presumably as part of a full `MSB` pack."""

        # Validate draw/display groups before doing any real work.
        draw_groups = bit_set_to_int_group(self._draw_groups, group_size=4)
        display_groups = bit_set_to_int_group(self._display_groups, group_size=4)

        name_offset = self.PART_HEADER_STRUCT.size
        packed_name = self.get_name_to_pack().encode(self.NAME_ENCODING) + b"\0"  # Name not padded on its own.
        sib_path_offset = name_offset + len(packed_name)
        packed_sib_path = self.sib_path.encode(self.NAME_ENCODING) + b"\0" if self.sib_path else b"\0" * 6
        while len(packed_name + packed_sib_path) % 4 != 0:
            packed_sib_path += b"\0"
        base_data_offset = sib_path_offset + len(packed_sib_path)
        packed_base_data = self.PART_BASE_DATA_STRUCT.pack(self)
        type_data_offset = base_data_offset + len(packed_base_data)
        packed_type_data = self.pack_type_data()
        try:
            packed_header = self.PART_HEADER_STRUCT.pack(
                __name_offset=name_offset,
                __part_type=self.ENTRY_SUBTYPE,
                _part_type_index=self._part_type_index,
                _model_index=self._model_index,
                __sib_path_offset=sib_path_offset,
                translate=list(self.translate),
                rotate=list(self.rotate),
                scale=list(self.scale),
                __draw_groups=draw_groups,
                __display_groups=display_groups,
                __base_data_offset=base_data_offset,
                __type_data_offset=type_data_offset,
            )
        except struct.error:
            raise MapError(f"Could not pack header data of `{self.__class__.__name__}` '{self.name}'. See traceback.")
        return packed_header + packed_name + packed_sib_path + packed_base_data + packed_type_data
Example #10
0
class MSBNavigationEvent(MSBEvent):
    ENTRY_SUBTYPE = MSBEventSubtype.Navigation
    EVENT_TYPE_DATA_STRUCT = BinaryStruct(
        ("_navigation_region_index", "i"),
        "12x",
    )

    FIELD_INFO = MSBEvent.FIELD_INFO | {
        "entity_id":
        MapFieldInfo(
            "Entity ID",
            int,
            -1,
            "Entity ID used to refer to the Navigation event in other game files.",
        ),
        "navigation_region_name":
        MapFieldInfo(
            "Navmesh Region",
            Region,
            None,
            "Region to which Navigation event is attached, which encloses faces of one or more Navmesh parts.",
        ),
    }

    FIELD_ORDER = (
        "entity_id",
        "navigation_region_name",
    )

    REFERENCE_FIELDS = {
        "parts": ["base_part_name"],
        "regions": ["base_region_name", "navigation_region_name"]
    }

    navigation_region_name: tp.Optional[str]

    def __init__(self, source=None, **kwargs):
        self._navigation_region_index = None
        self._navigation_region_name = None
        super().__init__(source=source, **kwargs)

    @property
    def navigation_region_name(self):
        return self._navigation_region_name

    @navigation_region_name.setter
    def navigation_region_name(self, value: tp.Union[None, str]):
        if isinstance(value, str):
            self._navigation_region_name = value if value else None
        elif value is None:
            self._navigation_region_name = None
        else:
            raise TypeError(
                f"`navigation_region_name` must be a string or `None`, not {value}."
            )

    def set_indices(self, indices: EventsIndicesData):
        super().set_indices(indices)
        if self.navigation_region_name:
            self._navigation_region_index = indices.region_indices[
                self._navigation_region_name]
        else:
            self._navigation_region_index = -1

    def set_names(self, names: EventsNamesData):
        super().set_names(names)
        if self._navigation_region_index != -1:
            self._navigation_region_name = names.region_names[
                self._navigation_region_index]
        else:
            self._navigation_region_name = None
Example #11
0
class MSBTreasureEvent(MSBEvent):
    ENTRY_SUBTYPE = MSBEventSubtype.Treasure
    EVENT_TYPE_DATA_STRUCT = BinaryStruct(
        "8x",
        ("_treasure_part_index", "i"),
        "4x",
        ("item_lot_1", "i"),
        ("item_lot_2", "i"),
        ("item_lot_3", "i"),
        ("unknown_x1c_x20", "i"),
        ("unknown_x20_x24", "i"),
        ("unknown_x24_x28", "i"),
        ("unknown_x28_x2c", "i"),
        ("unknown_x2c_x30", "i"),
        ("unknown_x30_x34", "i"),
        ("unknown_x34_x38", "i"),
        ("unknown_x38_x3c", "i"),
        ("unknown_x3c_x40", "i"),
        ("is_in_chest", "?"),
        ("is_hidden", "?"),
        ("unknown_x42_x44", "h"),
        ("unknown_x44_x48", "i"),
        ("unknown_x48_x4c", "i"),
        "4x",
    )

    FIELD_INFO = {
        "treasure_part_name":
        MapFieldInfo(
            "Treasure Object",
            Object,
            None,
            "Object on which treasure will appear (usually a corpse or chest).",
        ),
        "item_lot_1":
        MapFieldInfo(
            "Item Lot 1",
            ItemLotParam,
            -1,
            "First item lot of treasure. (Note that the item lots that are +1 to +5 from this ID will also be "
            "awarded.)",
        ),
        "item_lot_2":
        MapFieldInfo(
            "Item Lot 2",
            ItemLotParam,
            -1,
            "Second item lot of treasure. (Note that the item lots that are +1 to +5 from this ID will also be "
            "awarded.)",
        ),
        "item_lot_3":
        MapFieldInfo(
            "Item Lot 3",
            ItemLotParam,
            -1,
            "Third item lot of treasure. (Note that the item lots that are +1 to +5 from this ID will also be "
            "awarded.)",
        ),
        "is_in_chest":
        MapFieldInfo(
            "Is In Chest",
            bool,
            False,
            "Indicates if this treasure is inside a chest (affects appearance).",  # TODO: effect?
        ),
        "is_hidden":
        MapFieldInfo(
            "Is Hidden",
            bool,
            False,
            "If True, this treasure will start disabled and will need to be enabled manually with an event script.",
        ),
        "unknown_x1c_x20":
        MapFieldInfo(
            "Unknown [1c-20]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x20_x24":
        MapFieldInfo(
            "Unknown [20-24]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x24_x28":
        MapFieldInfo(
            "Unknown [24-28]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x28_x2c":
        MapFieldInfo(
            "Unknown [28-2c]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x2c_x30":
        MapFieldInfo(
            "Unknown [2c-30]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x30_x34":
        MapFieldInfo(
            "Unknown [30-34]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x34_x38":
        MapFieldInfo(
            "Unknown [34-38]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x38_x3c":
        MapFieldInfo(
            "Unknown [38-3c]",
            int,
            0,
            "Unknown integer.",
        ),
        "unknown_x3c_x40":
        MapFieldInfo(
            "Unknown [3c-40]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x42_x44":
        MapFieldInfo(
            "Unknown [42-44]",
            int,
            0,
            "Unknown short.",
        ),
        "unknown_x44_x48":
        MapFieldInfo(
            "Unknown [44-48]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x48_x4c":
        MapFieldInfo(
            "Unknown [48-4c]",
            int,
            -1,
            "Unknown integer.",
        ),
    }

    FIELD_ORDER = (
        "treasure_part_name",
        "item_lot_1",
        "item_lot_2",
        "item_lot_3",
        "is_in_chest",
        "is_hidden",
        "unknown_x1c_x20",
        "unknown_x20_x24",
        "unknown_x24_x28",
        "unknown_x28_x2c",
        "unknown_x2c_x30",
        "unknown_x30_x34",
        "unknown_x34_x38",
        "unknown_x38_x3c",
        "unknown_x3c_x40",
        "unknown_x42_x44",
        "unknown_x44_x48",
        "unknown_x48_x4c",
    )

    REFERENCE_FIELDS = {
        "parts": ["base_part_name", "treasure_part_name"],
        "regions": ["base_region_name"]
    }

    treasure_part_name: tp.Optional[str]
    item_lot_1: int
    item_lot_2: int
    item_lot_3: int
    is_in_chest: bool
    is_hidden: bool
    unknown_x1c_x20: int
    unknown_x20_x24: int
    unknown_x24_x28: int
    unknown_x28_x2c: int
    unknown_x2c_x30: int
    unknown_x30_x34: int
    unknown_x34_x38: int
    unknown_x38_x3c: int
    unknown_x3c_x40: int
    unknown_x42_x44: int
    unknown_x44_x48: int
    unknown_x48_x4c: int

    def __init__(self, source=None, **kwargs):
        self._treasure_part_index = None
        self._treasure_part_name = None
        self.item_lot_1 = -1
        self.item_lot_2 = -1
        self.item_lot_3 = -1
        self.unknown_x1c_x20 = -1
        self.unknown_x20_x24 = -1
        self.unknown_x24_x28 = -1
        self.unknown_x28_x2c = -1
        self.unknown_x2c_x30 = -1
        self.unknown_x30_x34 = -1
        self.unknown_x34_x38 = -1
        self.unknown_x38_x3c = -1
        self.unknown_x3c_x40 = -1
        self.unknown_x42_x44 = -1
        self.unknown_x44_x48 = -1
        self.unknown_x48_x4c = -1
        super().__init__(source=source, **kwargs)

    @property
    def treasure_part_name(self):
        return self._treasure_part_name

    @treasure_part_name.setter
    def treasure_part_name(self, value: tp.Union[None, str]):
        if isinstance(value, str):
            self._treasure_part_name = value if value else None
        elif value is None:
            self._treasure_part_name = None
        else:
            raise TypeError(
                f"`treasure_part_name` must be a string or `None`, not {value}."
            )

    def set_indices(self, indices: EventsIndicesData):
        super().set_indices(indices)
        self._treasure_part_index = indices.part_indices[
            self._treasure_part_name] if self.treasure_part_name else -1

    def set_names(self, names: EventsNamesData):
        super().set_names(names)
        self._treasure_part_name = (names.part_names[self._treasure_part_index]
                                    if self._treasure_part_index != -1 else
                                    None)
Example #12
0
class MSBSpawnPointEvent(MSBEvent):
    ENTRY_SUBTYPE = MSBEventSubtype.SpawnPoint
    EVENT_TYPE_DATA_STRUCT = BinaryStruct(
        ("_spawn_point_region_index", "i"),
        "12x",
    )

    FIELD_INFO = MSBEvent.FIELD_INFO | {
        "base_part_name":
        MapFieldInfo(
            "Draw Parent",
            MapPart,
            None,
            "Some Spawn Points use this; unclear what it does, but it is presumably the Collision of the Spawn Point.",
        ),
        "entity_id":
        MapFieldInfo(
            "Entity ID",
            int,
            -1,
            "Entity ID used to refer to the Spawn Point in other game files.",
        ),
        "spawn_point_region_name":
        MapFieldInfo(
            "Spawn Point Region",
            Region,
            None,
            "Region where player will spawn when registered to this spawn point.",
        ),
    }

    FIELD_ORDER = (
        "entity_id",
        "base_part_name",
        "spawn_point_region_name",
    )

    REFERENCE_FIELDS = {
        "parts": ["base_part_name"],
        "regions": ["base_region_name", "spawn_point_region_name"]
    }

    spawn_point_region_name: tp.Optional[str]

    def __init__(self, source=None, **kwargs):
        self._spawn_point_region_index = None
        self._spawn_point_region_name = None
        super().__init__(source=source, **kwargs)

    @property
    def spawn_point_region_name(self):
        return self._spawn_point_region_name

    @spawn_point_region_name.setter
    def spawn_point_region_name(self, value: tp.Union[None, str]):
        if isinstance(value, str):
            self._spawn_point_region_name = value if value else None
        elif value is None:
            self._spawn_point_region_name = None
        else:
            raise TypeError(
                f"`spawn_point_region_name` must be a string or `None`, not {value}."
            )

    def set_indices(self, indices: EventsIndicesData):
        super().set_indices(indices)
        if self.spawn_point_region_name:
            self._spawn_point_region_index = indices.region_indices[
                self._spawn_point_region_name]
        else:
            self._spawn_point_region_index = -1

    def set_names(self, names: EventsNamesData):
        super().set_names(names)
        if self._spawn_point_region_index != -1:
            self._spawn_point_region_name = names.region_names[
                self._spawn_point_region_index]
        else:
            self._spawn_point_region_name = None
Example #13
0
class MSBCollision(_BaseMSBCollision, MSBPartSceneGParam):
    PART_TYPE_DATA_STRUCT = BinaryStruct(
        ("hit_filter_id", "B"),
        ("sound_space_type", "B"),
        ("_environment_event_index", "h"),
        ("reflect_plane_height", "f"),
        ("__area_name_id", "h"),
        ("starts_disabled", "?"),
        ("unk_x0b_x0c", "B"),
        ("attached_bonfire", "i"),
        ("__play_region_id", "i"),
        ("camera_1_id", "h"),
        ("camera_2_id", "h"),
    )

    FIELD_INFO = MSBPartSceneGParam.FIELD_INFO | _BaseMSBCollision.FIELD_INFO | {
        "unk_x0b_x0c": MapFieldInfo(
            "Unknown [0b-0c]",
            int,
            0,
            "Unknown. Almost always zero (in DS1 at least).",
        ),
        "attached_bonfire": MapFieldInfo(
            "Attached Lantern",
            int,
            -1,
            "If this is set to a lantern entity ID, that lantern will be unusable if any living enemy characters are "
            "on this collision. Note that this also checks for enemies that are disabled by events.",
        ),
        # TODO: Confirm Bloodborne uses the same signed system for Stable Footing Flag.
    }

    FIELD_ORDER = (
        "model_name",
        "entity_id",
        "translate",
        "rotate",
        "draw_groups",
        "display_groups",
        "backread_groups",
        "hit_filter_id",
        "sound_space_type",
        "environment_event_name",
        "reflect_plane_height",
        "area_name_id",
        "force_area_banner",
        "starts_disabled",
        "unk_x0b_x0c",
        "attached_bonfire",
        "play_region_id",
        "stable_footing_flag",
        "camera_1_id",
        "camera_2_id",
    ) + MSBPartSceneGParam.FIELD_ORDER

    unk_x0b_x0c: int

    def pack_type_data(self):
        """Pack to bytes, presumably as part of a full `MSB` pack."""

        if self.area_name_id == -1 and not self._force_area_banner:
            raise InvalidFieldValueError("`force_area_banner` must be enabled if `area_name_id == -1` (default).")
        signed_area_name_id = self.area_name_id * (-1 if self.area_name_id >= 0 and self._force_area_banner else 1)
        if self._stable_footing_flag != 0:
            play_region_id = -self._stable_footing_flag - 10
        else:
            play_region_id = self._play_region_id
        return self.PART_TYPE_DATA_STRUCT.pack(
            hit_filter_id=self.hit_filter_id,
            sound_space_type=self.sound_space_type,
            _environment_event_index=self._environment_event_index,
            reflect_plane_height=self.reflect_plane_height,
            __area_name_id=signed_area_name_id,
            starts_disabled=self.starts_disabled,
            unk_x0b_x0c=self.unk_x0b_x0c,
            attached_bonfire=self.attached_bonfire,
            __play_region_id=play_region_id,
            camera_1_id=self.camera_1_id,
            camera_2_id=self.camera_2_id,
        )
Example #14
0
class MSBObject(_BaseMSBObject, MSBPartGParam):
    """Interactable object. Note that Bloodborne has six-digit model IDs for Objects."""

    PART_TYPE_DATA_STRUCT = BinaryStruct(
        "4x",
        ("_draw_parent_index", "i"),
        ("break_term", "b"),
        ("net_sync_type", "b"),
        ("collision_hit_filter", "?"),
        ("set_main_object_structure_bools", "?"),
        ("animation_ids", "4h"),
        ("model_vfx_param_id_offsets", "4h"),
    )

    FIELD_INFO = MSBPartGParam.FIELD_INFO | _BaseMSBObject.FIELD_INFO | {
        "break_term": MapFieldInfo(
            "Break Term",
            int,
            0,
            "Unknown. Related to object breakage.",
        ),
        "net_sync_type": MapFieldInfo(
            "Net Sync Type",
            int,
            0,
            "Unknown. Related to online object synchronization.",
        ),
        "collision_hit_filter": MapFieldInfo(
            "Collision Hit Filter",
            bool,
            False,
            "Unclear what this does when enabled.",
        ),
        "set_main_object_structure_bools": MapFieldInfo(
            "Set Main Object Structure Bools",
            bool,
            False,
            "Unknown.",
        ),
        "animation_ids": MapFieldInfo(
            "Animation IDs",
            list,
            [-1, -1, -1, -1],
            "Default animation IDs for object (e.g. corpse poses). Only the first is used, according to Pav.",
        ),
        "model_vfx_param_id_offsets": MapFieldInfo(
            "Model VFX Param ID Offsets",
            list,
            [0, 0, 0, 0],
            "Offsets for model VFX param IDs. Only the first is used, according to Pav.",
        ),
    }

    FIELD_ORDER = _BaseMSBObject.FIELD_ORDER + (
        # "backread_groups",
        "break_term",
        "net_sync_type",
        "collision_hit_filter",
        "set_main_object_structure_bools",
        "animation_ids",
        "model_vfx_param_id_offsets",
    ) + MSBPartGParam.FIELD_ORDER

    break_term: int
    net_sync_type: int
    collision_hit_filter: bool
    set_main_object_structure_bools: bool
    animation_ids: list[int, int, int, int]
    model_vfx_param_id_offsets: list[int, int, int, int]
Example #15
0
class MSBNPCInvasionEvent(MSBEvent):
    ENTRY_SUBTYPE = MSBEventSubtype.NPCInvasion
    EVENT_TYPE_DATA_STRUCT = BinaryStruct(
        ("host_entity_id", "i"),
        ("invasion_flag_id", "i"),
        ("_spawn_point_region_index", "i"),
        "4x",
    )

    FIELD_INFO = MSBEvent.FIELD_INFO | {
        "base_region_name":
        MapFieldInfo(
            "Invasion Region",
            Region,
            None,
            "Region in which NPC Invasion event can be triggered (e.g. with Black Eye Orb).",
        ),
        "host_entity_id":
        MapFieldInfo(
            "Host Entity ID",
            int,
            -1,
            "Entity ID of NPC character to be invaded.",
        ),
        "invasion_flag_id":
        MapFieldInfo(
            "Invasion Flag",
            Flag,
            -1,
            "Flag that is enabled while the invasion is active, which should trigger changes to the world.",
        ),
        "spawn_point_region_name":
        MapFieldInfo(
            "Spawn Point Region",
            Region,
            None,
            "Region where player will spawn during invasion event.",
        ),
    }

    FIELD_ORDER = (
        "base_region_name",
        "host_entity_id",
        "invasion_flag_id",
        "spawn_point_region_name",
    )

    REFERENCE_FIELDS = {
        "parts": ["base_part_name"],
        "regions": ["base_region_name", "spawn_point_region_name"]
    }

    host_entity_id: int
    invasion_flag_id: int
    spawn_point_region_name: tp.Optional[str]

    def __init__(self, source=None, **kwargs):
        self._spawn_point_region_index = None
        self._spawn_point_region_name = None
        super().__init__(source=source, **kwargs)

    @property
    def spawn_point_region_name(self):
        return self._spawn_point_region_name

    @spawn_point_region_name.setter
    def spawn_point_region_name(self, value: tp.Union[None, str]):
        if isinstance(value, str):
            self._spawn_point_region_name = value if value else None
        elif value is None:
            self._spawn_point_region_name = None
        else:
            raise TypeError(
                f"`spawn_point_region_name` must be a string or `None`, not {value}."
            )

    def set_indices(self, indices: EventsIndicesData):
        super().set_indices(indices)
        if self.spawn_point_region_name:
            self._spawn_point_region_index = indices.region_indices[
                self._spawn_point_region_name]
        else:
            self._spawn_point_region_index = -1

    def set_names(self, names: EventsNamesData):
        super().set_names(names)
        if self._spawn_point_region_index != -1:
            self._spawn_point_region_name = names.region_names[
                self._spawn_point_region_index]
        else:
            self._spawn_point_region_name = None
Example #16
0
class MSBCollision(_BaseMSBCollision, MSBPart):
    """Dark Souls collision includes navmesh groups and Vagrants."""

    PART_TYPE_DATA_STRUCT = BinaryStruct(
        ("hit_filter_id", "B"),
        ("sound_space_type", "B"),
        ("_environment_event_index", "h"),
        ("reflect_plane_height", "f"),
        ("__navmesh_groups", "4I"),
        ("vagrant_entity_ids", "3i"),
        ("__area_name_id", "h"),
        ("starts_disabled", "?"),
        ("unk_x27_x28", "B"),
        ("attached_bonfire", "i"),
        ("minus_ones", "3i", [-1, -1, -1]),  # Never used. Possibly more bonfires?
        ("__play_region_id", "i"),
        ("camera_1_id", "h"),
        ("camera_2_id", "h"),
        "16x",
    )

    FIELD_INFO = MSBPart.FIELD_INFO | _BaseMSBCollision.FIELD_INFO | {
        "navmesh_groups": MapFieldInfo(
            "Navmesh Groups",
            list,
            set(range(MSBPart.FLAG_SET_SIZE)),
            "Controls collision backread.",
        ),
        "vagrant_entity_ids": MapFieldInfo(
            "Vagrant Entity IDs",
            list,
            [-1, -1, -1],
            "Unknown.",
        ),
        "unk_x27_x28": MapFieldInfo(
            "Unknown [27-28]",
            int,
            0,
            "Unknown. Almost always zero, but see e.g. Anor Londo spinning tower collision.",
        ),
    }

    FIELD_ORDER = (
        "model_name",
        "entity_id",
        "translate",
        "rotate",
        # "scale",
        "draw_groups",
        "display_groups",
        "navmesh_groups",
        "hit_filter_id",
        "sound_space_type",
        "environment_event_name",
        "reflect_plane_height",
        "vagrant_entity_ids",
        "area_name_id",
        "force_area_banner",
        "starts_disabled",
        # "unk_x27_x28",
        "attached_bonfire",
        "play_region_id",
        "stable_footing_flag",
        "camera_1_id",
        "camera_2_id",
    ) + MSBPart.LIGHTING_FIELD_ORDER + (
        "is_shadow_source",
        "is_shadow_destination",
        "is_shadow_only",
        "draw_by_reflect_cam",
        "draw_only_reflect_cam",
        # "use_depth_bias_float",
        "disable_point_light_effect",
    )

    vagrant_entity_ids: list[int, int, int]
    unk_x27_x28: int

    def __init__(self, source=None, **kwargs):
        self._navmesh_groups = set()
        if source is None:
            kwargs.setdefault("is_shadow_source", True)
            kwargs.setdefault("is_shadow_destination", True)
            kwargs.setdefault("draw_by_reflect_cam", True)
        super().__init__(source=source, **kwargs)

    def unpack_type_data(self, msb_buffer):
        data = self.PART_TYPE_DATA_STRUCT.unpack(msb_buffer, exclude_asserted=True)
        self.set(**data)
        self._navmesh_groups = int_group_to_bit_set(data["__navmesh_groups"], assert_size=4)
        self.area_name_id = abs(data["__area_name_id"]) if data["__area_name_id"] != -1 else -1
        self._force_area_banner = data["__area_name_id"] < 0  # Custom field.
        if data["__play_region_id"] > -10:
            self._play_region_id = data["__play_region_id"]
            self._stable_footing_flag = 0
        else:
            self._play_region_id = 0
            self._stable_footing_flag = -data["__play_region_id"] - 10

    def pack_type_data(self):
        """Pack to bytes, presumably as part of a full `MSB` pack."""

        # Validate navmesh groups before doing any real work.
        navmesh_groups = bit_set_to_int_group(self._navmesh_groups, group_size=4)

        if self.area_name_id == -1 and not self._force_area_banner:
            raise InvalidFieldValueError("`force_area_banner` must be enabled if `area_name_id == -1` (default).")
        signed_area_name_id = self.area_name_id * (-1 if self.area_name_id >= 0 and self._force_area_banner else 1)
        if self._stable_footing_flag != 0:
            play_region_id = -self._stable_footing_flag - 10
        else:
            play_region_id = self._play_region_id
        return self.PART_TYPE_DATA_STRUCT.pack(
            hit_filter_id=self.hit_filter_id,
            sound_space_type=self.sound_space_type,
            _environment_event_index=self._environment_event_index,
            reflect_plane_height=self.reflect_plane_height,
            __navmesh_groups=navmesh_groups,
            vagrant_entity_ids=self.vagrant_entity_ids,
            __area_name_id=signed_area_name_id,
            starts_disabled=self.starts_disabled,
            unk_x27_x28=self.unk_x27_x28,
            attached_bonfire=self.attached_bonfire,
            __play_region_id=play_region_id,
            camera_1_id=self.camera_1_id,
            camera_2_id=self.camera_2_id,
        )

    @property
    def navmesh_groups(self):
        return self._navmesh_groups

    @navmesh_groups.setter
    def navmesh_groups(self, value):
        """Converts value to a `set()` (possibly empty) and validates index range."""
        if value is None or isinstance(value, str) and value in {"None", ""}:
            self._navmesh_groups = set()
            return
        try:
            navmesh_groups = set(value)
        except (TypeError, ValueError):
            raise TypeError(
                "Navmesh groups must be a set, sequence, `None`, 'None', or ''. Or use `set` methods like `.add()`."
            )
        for i in navmesh_groups:
            if not isinstance(i, int) and 0 <= i < self.FLAG_SET_SIZE:
                raise ValueError(f"Invalid navmesh group: {i}. Must be 0 <= i < {self.FLAG_SET_SIZE}.")
        self._navmesh_groups = navmesh_groups
Example #17
0
class MSBWindEvent(MSBEvent):
    ENTRY_SUBTYPE = MSBEventSubtype.Wind
    EVENT_TYPE_DATA_STRUCT = BinaryStruct(
        ("wind_vector_min", "3f"),
        ("unk_x04_x08", "f"),
        ("wind_vector_max", "3f"),
        ("unk_x0c_x10", "f"),
        ("wind_swing_cycles", "4f"),
        ("wind_swing_powers", "4f"),
    )

    FIELD_INFO = MSBEvent.FIELD_INFO | {
        "wind_vector_min":
        MapFieldInfo(
            "Wind Vector Min",
            Vector3,
            Vector3.zero(),
            "Wind vector minimum.",
        ),
        "unk_x04_x08":
        MapFieldInfo(
            "Unknown [04-08]",
            float,
            0.0,
            "Unknown Wind parameter (floating-point number).",
        ),
        "wind_vector_max":
        MapFieldInfo(
            "Wind Vector Max",
            Vector3,
            Vector3.zero(),
            "Wind vector maximum.",
        ),
        "unk_x0c_x10":
        MapFieldInfo(
            "Unknown [0c-10]",
            float,
            0.0,
            "Unknown Wind parameter (floating-point number).",
        ),
        "wind_swing_cycles":
        MapFieldInfo(
            "Wind Swing Cycles",
            list,
            [0.0, 0.0, 0.0, 0.0],
            "Wind swing cycles (four values).",
        ),
        "wind_swing_powers":
        MapFieldInfo(
            "Wind Swing Powers",
            list,
            [0.0, 0.0, 0.0, 0.0],
            "Wind swing powers (four values).",
        ),
    }

    FIELD_ORDER = (
        "wind_vector_min",
        "unk_x04_x08",
        "wind_vector_max",
        "unk_x0c_x10",
        "wind_swing_cycles",
        "wind_swing_powers",
    )

    unk_x04_x08: float
    unk_x0c_x10: float

    def __init__(self, source=None, **kwargs):
        """Most of these floats have been mapped out."""
        self._wind_vector_min = Vector3.zero()
        self._wind_vector_max = Vector3.zero()
        self._wind_swing_cycles = [0.0, 0.0, 0.0, 0.0]
        self._wind_swing_powers = [0.0, 0.0, 0.0, 0.0]
        super().__init__(source=source, **kwargs)

    @property
    def wind_vector_min(self):
        return self._wind_vector_min

    @wind_vector_min.setter
    def wind_vector_min(self, value):
        self._wind_vector_min = Vector3(value)

    @property
    def wind_vector_max(self):
        return self._wind_vector_max

    @wind_vector_max.setter
    def wind_vector_max(self, value):
        self._wind_vector_max = Vector3(value)

    @property
    def wind_swing_cycles(self):
        return self._wind_swing_cycles

    @wind_swing_cycles.setter
    def wind_swing_cycles(self, value):
        try:
            value = list(value)
            if not len(value) == 4 or not all(
                    isinstance(v, (int, float)) for v in value):
                raise ValueError
        except (TypeError, ValueError):
            raise ValueError(
                f"`wind_swing_cycles` must be a sequence of four numbers.")
        self._wind_swing_cycles = value

    @property
    def wind_swing_powers(self):
        return self._wind_swing_powers

    @wind_swing_powers.setter
    def wind_swing_powers(self, value):
        try:
            value = list(value)
            if not len(value) == 4 or not all(
                    isinstance(v, (int, float)) for v in value):
                raise ValueError
        except (TypeError, ValueError):
            raise ValueError(
                f"`wind_swing_powers` must be a sequence of four numbers.")
        self._wind_swing_powers = value
Example #18
0
class MSBTreasureEvent(_BaseMSBTreasureEvent, MSBEvent):
    EVENT_TYPE_DATA_STRUCT = BinaryStruct(
        "8x",
        ("_treasure_part_index", "i"),
        "4x",
        ("item_lot_1", "i"),
        ("item_lot_2", "i"),
        ("item_lot_3", "i"),
        ("unknown_x1c_x20", "i"),
        ("unknown_x20_x24", "i"),
        ("unknown_x24_x28", "i"),
        ("unknown_x28_x2c", "i"),
        ("unknown_x2c_x30", "i"),
        ("unknown_x30_x34", "i"),
        ("unknown_x34_x38", "i"),
        ("unknown_x38_x3c", "i"),
        ("unknown_x3c_x40", "i"),
        ("is_in_chest", "?"),
        ("is_hidden", "?"),
        ("unknown_x42_x44", "h"),
        ("unknown_x44_x48", "i"),
        ("unknown_x48_x4c", "i"),
        "4x",
    )

    FIELD_INFO = _BaseMSBTreasureEvent.FIELD_INFO | {
        "unknown_x1c_x20":
        MapFieldInfo(
            "Unknown [1c-20]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x20_x24":
        MapFieldInfo(
            "Unknown [20-24]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x24_x28":
        MapFieldInfo(
            "Unknown [24-28]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x28_x2c":
        MapFieldInfo(
            "Unknown [28-2c]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x2c_x30":
        MapFieldInfo(
            "Unknown [2c-30]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x30_x34":
        MapFieldInfo(
            "Unknown [30-34]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x34_x38":
        MapFieldInfo(
            "Unknown [34-38]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x38_x3c":
        MapFieldInfo(
            "Unknown [38-3c]",
            int,
            0,
            "Unknown integer.",
        ),
        "unknown_x3c_x40":
        MapFieldInfo(
            "Unknown [3c-40]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x42_x44":
        MapFieldInfo(
            "Unknown [42-44]",
            int,
            0,
            "Unknown short.",
        ),
        "unknown_x44_x48":
        MapFieldInfo(
            "Unknown [44-48]",
            int,
            -1,
            "Unknown integer.",
        ),
        "unknown_x48_x4c":
        MapFieldInfo(
            "Unknown [48-4c]",
            int,
            -1,
            "Unknown integer.",
        ),
    }

    FIELD_ORDER = (
        "treasure_part_name",
        "item_lot_1",
        "item_lot_2",
        "item_lot_3",
        "is_in_chest",
        "is_hidden",
        "unknown_x1c_x20",
        "unknown_x20_x24",
        "unknown_x24_x28",
        "unknown_x28_x2c",
        "unknown_x2c_x30",
        "unknown_x30_x34",
        "unknown_x34_x38",
        "unknown_x38_x3c",
        "unknown_x3c_x40",
        "unknown_x42_x44",
        "unknown_x44_x48",
        "unknown_x48_x4c",
    )

    unknown_x1c_x20: int
    unknown_x20_x24: int
    unknown_x24_x28: int
    unknown_x28_x2c: int
    unknown_x2c_x30: int
    unknown_x30_x34: int
    unknown_x34_x38: int
    unknown_x38_x3c: int
    unknown_x3c_x40: int
    unknown_x42_x44: int
    unknown_x44_x48: int
    unknown_x48_x4c: int
Example #19
0
class MSBSpawnerEvent(MSBEvent):

    ENTRY_SUBTYPE = MSBEventSubtype.Spawner
    EVENT_TYPE_DATA_STRUCT = BinaryStruct(
        ("max_count", "B"),
        ("spawner_type", "b"),
        ("limit_count", "h"),
        ("min_spawner_count", "h"),
        ("max_spawner_count", "h"),
        ("min_interval", "f"),
        ("max_interval", "f"),
        ("initial_spawn_count", "i"),
        "28x",
        ("_spawn_region_indices", "4i"),
        ("_spawn_part_indices", "32i"),
        "64x",
    )

    SPAWN_REGION_COUNT = 4

    FIELD_INFO = MSBEvent.FIELD_INFO | {
        "entity_id":
        MapFieldInfo(
            "Entity ID",
            int,
            -1,
            "Entity ID used to refer to the Spawner in other game files.",
        ),
        "max_count":
        MapFieldInfo(
            "Max Count",
            int,
            255,
            "Unsure; I suspect this is the total number of entities this spawner can produce.",
        ),
        "spawner_type":
        MapFieldInfo(
            "Spawner Type",
            int,
            0,
            "Unsure what this enumeration is.",
        ),
        "limit_count":
        MapFieldInfo(
            "Limit Count",
            int,
            -1,
            "Unsure; I suspect this is the number of spawned entities that can be alive at once.",
        ),
        "min_spawner_count":
        MapFieldInfo(
            "Min Spawner Count",
            int,
            1,
            "Unsure.",
        ),
        "max_spawner_count":
        MapFieldInfo(
            "Max Spawner Count",
            int,
            1,
            "Unsure.",
        ),
        "min_interval":
        MapFieldInfo(
            "Min Interval",
            float,
            1.0,
            "Minimum number of seconds between spawns.",
        ),
        "max_interval":
        MapFieldInfo(
            "Max Interval",
            float,
            1.0,
            "Maximum number of seconds between spawns.",
        ),
        "initial_spawn_count":
        MapFieldInfo(
            "Initial Spawn Count",
            int,
            1,
            "Unsure; I suspect this is the number of entities spawned immediately on map load.",
        ),
        "spawn_part_names":
        MapFieldInfo(
            "Spawn Characters",
            GameObjectSequence((Character, 32)),  # TODO: ditto
            [None] * 32,
            "Entities that will be spawned at given regions.",
        ),
        "spawn_region_names":
        MapFieldInfo(
            "Spawn Regions",
            GameObjectSequence((Region, SPAWN_REGION_COUNT)),
            [None] * SPAWN_REGION_COUNT,
            "Regions where entities will be spawned.",
        ),
    }

    REFERENCE_FIELDS = {
        "parts": ["base_part_name", "spawn_part_names"],
        "regions": ["base_region_name", "spawn_region_names"]
    }

    FIELD_ORDER = (
        "entity_id",
        "max_count",
        "spawner_type",
        "limit_count",
        "min_spawner_count",
        "max_spawner_count",
        "min_interval",
        "max_interval",
        "initial_spawn_count",
        "spawn_part_names",
        "spawn_region_names",
    )

    spawn_region_names: list[tp.Union[str, None]]
    spawn_part_names: list[tp.Union[str, None]]

    def __init__(self, source=None, **kwargs):
        self._spawn_region_indices = [-1] * self.SPAWN_REGION_COUNT
        self._spawn_part_indices = [-1] * 32
        super().__init__(source=source, **kwargs)

    def set_indices(self, indices: EventsIndicesData):
        super().set_indices(indices)
        self._spawn_region_indices = [
            indices.region_indices[n] if n else -1
            for n in self.spawn_region_names
        ]
        while len(self._spawn_region_indices) < self.SPAWN_REGION_COUNT:
            self._spawn_part_indices.append(-1)
        self._spawn_part_indices = [
            indices.part_indices[n] if n else -1 for n in self.spawn_part_names
        ]
        while len(self._spawn_part_indices) < 32:
            self._spawn_part_indices.append(-1)

    def set_names(self, names: EventsNamesData):
        super().set_names(names)
        self.spawn_region_names = [
            names.region_names[i] if i != -1 else None
            for i in self._spawn_region_indices
        ]
        while len(self.spawn_region_names) < self.SPAWN_REGION_COUNT:
            self.spawn_region_names.append(None)
        self.spawn_part_names = [
            names.part_names[i] if i != -1 else None
            for i in self._spawn_part_indices
        ]
        while len(self.spawn_part_names) < 32:
            self.spawn_part_names.append(None)
Example #20
0
class MSBWindVFXEvent(MSBEvent):
    ENTRY_SUBTYPE = MSBEventSubtype.WindVFX
    EVENT_TYPE_DATA_STRUCT = BinaryStruct(
        ("vfx_id", "i"),
        ("_wind_region_index", "i"),
        ("unk_x08_x0c", "f"),
        "4x",
    )

    FIELD_INFO = MSBEvent.FIELD_INFO | {
        "base_part_name":
        MapFieldInfo(
            "Draw Parent",
            MapPart,
            None,
            "VFX will be drawn if this parent (usually a Collision or Map Piece part) is drawn. Possibly unused.",
        ),
        "base_region_name":
        MapFieldInfo(
            "Base Region",
            Region,
            None,
            "Base event region. Probably unused with WindVFX (see Wind Region Name instead).",
        ),
        "wind_region_name":
        MapFieldInfo(
            "VFX Region",
            Region,
            None,
            "Region at or in which WindVFX appears.",
        ),
        "entity_id":
        MapFieldInfo(
            "Entity ID",
            int,
            -1,
            "Entity ID used to refer to this VFX in other game files. Possibly unused with WindVFX.",
        ),
        "vfx_id":
        MapFieldInfo(
            "VFX ID",
            int,
            -1,
            "Visual effect ID, which refers to a loaded VFX file.",
        ),
        "unk_x08_x0c":
        MapFieldInfo(
            "Unknown [08-0c]",
            float,
            1.0,
            "Unknown floating-point number.",
        ),
    }

    FIELD_ORDER = (
        "entity_id",
        "base_part_name",
        "base_region_name",
        "vfx_id",
        "unk_x08_x0c",
    )

    wind_region_name: tp.Optional[str]
    vfx_id: int
    unk_x08_x0c: float

    def __init__(self, source, **kwargs):
        self._wind_region_index = None
        super().__init__(source=source, **kwargs)

    def set_indices(self, event_index, local_event_index, region_indices,
                    part_indices):
        super().set_indices(event_index, local_event_index, region_indices,
                            part_indices)
        self._wind_region_index = part_indices[
            self.wind_region_name] if self.wind_region_name else -1

    def set_names(self, region_names, part_names):
        super().set_names(region_names, part_names)
        self.wind_region_name = part_names[
            self._wind_region_index] if self._wind_region_index != -1 else None
Example #21
0
class MSBObjActEvent(MSBEvent):
    ENTRY_SUBTYPE = MSBEventSubtype.ObjAct
    EVENT_TYPE_DATA_STRUCT = BinaryStruct(
        ("obj_act_entity_id", "i"),
        ("_obj_act_part_index", "i"),
        ("obj_act_param_id", "h"),
        ("obj_act_state", "B"),
        "x",
        ("obj_act_flag", "i"),
    )

    FIELD_INFO = MSBEvent.FIELD_INFO | {
        "obj_act_entity_id":
        MapFieldInfo(
            "ObjAct Entity ID",
            int,
            -1,
            "ID that identifies this object activation event in event scripts.",
        ),
        "obj_act_part_name":
        MapFieldInfo(
            "Object",
            Object,
            None,
            "Object to which this object activation event is attached.",
        ),
        "obj_act_param_id":
        MapFieldInfo(
            "ObjAct Param",
            ObjActParam,
            -1,
            "Param entry containing information about this object activation event. If it is -1, it will "
            "default to the model ID of the object it is attached to.",
        ),
        "obj_act_state":
        MapFieldInfo(
            "ObjAct State",
            int,
            0,
            "State of object activation. Known values include Default (0), Door (1), and Loop (2).",
        ),
        "obj_act_flag":
        MapFieldInfo(
            "ObjAct Flag",
            Flag,
            0,
            "Flag that stores the persistent state (e.g. open/closed) of this object activation.",
        ),
    }

    FIELD_ORDER = (
        "obj_act_entity_id",
        "obj_act_part_name",
        "obj_act_param_id",
        "obj_act_state",
        "obj_act_flag",
    )

    REFERENCE_FIELDS = {
        "parts": ["base_part_name", "obj_act_part_name"],
        "regions": ["base_region_name"]
    }

    obj_act_entity_id: int
    obj_act_part_name: tp.Optional[str]
    obj_act_param_id: int
    obj_act_state: int
    obj_act_flag: int

    def __init__(self, source=None, **kwargs):
        self._obj_act_part_index = None
        self._obj_act_part_name = None
        super().__init__(source=source, **kwargs)

    @property
    def obj_act_part_name(self):
        return self._obj_act_part_name

    @obj_act_part_name.setter
    def obj_act_part_name(self, value: tp.Union[None, str]):
        if isinstance(value, str):
            self._obj_act_part_name = value if value else None
        elif value is None:
            self._obj_act_part_name = None
        else:
            raise TypeError(
                f"`obj_act_part_name` must be a string or `None`, not {value}."
            )

    def set_indices(self, indices: EventsIndicesData):
        super().set_indices(indices)
        self._obj_act_part_index = indices.part_indices[
            self._obj_act_part_name] if self.obj_act_part_name else -1

    def set_names(self, names: EventsNamesData):
        super().set_names(names)
        self._obj_act_part_name = names.part_names[
            self.
            _obj_act_part_index] if self._obj_act_part_index != -1 else None
Example #22
0
class MSBPartSceneGParam(MSBPartGParam, abc.ABC):
    """Subclass of `MSBPart` that includes SceneGParam (and GParam) fields."""
    PART_SCENE_GPARAM_STRUCT = BinaryStruct(
        ("sg_unk_x00_x04", "i"),
        ("sg_unk_x04_x08", "i"),
        ("sg_unk_x08_x0c", "i"),
        ("sg_unk_x0c_x10", "i"),
        ("sg_unk_x10_x14", "i"),
        ("sg_unk_x14_x18", "i"),
        "36x",
        ("event_ids", "4b"),
        ("sg_unk_x40_x44", "f"),
        "12x",
    )

    FIELD_INFO = MSBPartGParam.FIELD_INFO | {
        "sg_unk_x00_x04": MapFieldInfo(
            "Unk SceneG [00-04]",
            int,
            0,
            "Unknown integer.",
        ),
        "sg_unk_x04_x08": MapFieldInfo(
            "Unk SceneG [04-08]",
            int,
            0,
            "Unknown integer.",
        ),
        "sg_unk_x08_x0c": MapFieldInfo(
            "Unk SceneG [08-0c]",
            int,
            0,
            "Unknown integer.",
        ),
        "sg_unk_x0c_x10": MapFieldInfo(
            "Unk SceneG [0c-10]",
            int,
            0,
            "Unknown integer.",
        ),
        "sg_unk_x10_x14": MapFieldInfo(
            "Unk SceneG [10-14]",
            int,
            0,
            "Unknown integer.",
        ),
        "sg_unk_x14_x18": MapFieldInfo(
            "Unk SceneG [14-18]",
            int,
            0,
            "Unknown integer.",
        ),
        "event_ids": MapFieldInfo(
            "Event IDs",
            list,
            [-1, -1, -1, -1],
            "List of four byte-sized event IDs.",
        ),
        "sg_unk_x40_x44": MapFieldInfo(
            "Unk SceneG [40-44]",
            float,
            0.0,
            "Unknown floating-point number.",
        ),
    }

    FIELD_ORDER = MSBPartGParam.FIELD_ORDER + (
        "sg_unk_x00_x04",
        "sg_unk_x04_x08",
        "sg_unk_x08_x0c",
        "sg_unk_x0c_x10",
        "sg_unk_x10_x14",
        "sg_unk_x14_x18",
        "event_ids",
        "sg_unk_x40_x44",
    )

    sg_unk_x00_x04: int
    sg_unk_x04_x08: int
    sg_unk_x08_x0c: int
    sg_unk_x0c_x10: int
    sg_unk_x10_x14: int
    sg_unk_x14_x18: int
    event_ids: list[int, int, int, int]
    sg_unk_x40_x44: float

    def _unpack_scene_gparam_data(self, msb_reader: BinaryReader, part_offset, header):
        if header["__scene_gparam_data_offset"] == 0:
            raise ValueError(f"Zero SceneGParam offset found in SceneGParam-supporting part {self.name}.")
        msb_reader.seek(part_offset + header["__scene_gparam_data_offset"])
        scene_gparam_data = msb_reader.unpack_struct(self.PART_SCENE_GPARAM_STRUCT)
        self.set(**scene_gparam_data)

    def _pack_scene_gparam_data(self):
        return self.PART_SCENE_GPARAM_STRUCT.pack(self)