From ccf87418b58335f29e81ef50b56e759810571aa8 Mon Sep 17 00:00:00 2001 From: dyceron Date: Thu, 26 Dec 2024 12:03:24 -0500 Subject: [PATCH 01/38] Update formatting + rename some fields --- .../formats/bmsld.py | 126 ++++++++---------- 1 file changed, 59 insertions(+), 67 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index 7c7c3874..5f024206 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -21,8 +21,8 @@ logger = logging.getLogger(__name__) FunctionArgument = Struct( - type=StaticPaddedString(4, "ascii"), - value=construct.Switch( + "type" / StaticPaddedString(4, "ascii"), + "value" / construct.Switch( construct.this.type, { "s": StrId, @@ -36,36 +36,36 @@ Components = { "TRIGGER": Struct( - command=StrId, - arguments=make_vector(FunctionArgument), + "command" / StrId, + "arguments" / make_vector(FunctionArgument), ), "SPAWNGROUP": Struct( - command=StrId, - arguments=make_vector(FunctionArgument), + "command" / StrId, + "arguments" / make_vector(FunctionArgument), ), "SPAWNPOINT": Struct( - command=StrId, - arguments=make_vector(FunctionArgument), + "command" / StrId, + "arguments" / make_vector(FunctionArgument), ), "STARTPOINT": Struct( - command=StrId, - arguments=make_vector(FunctionArgument), + "command" / StrId, + "arguments" / make_vector(FunctionArgument), ), "MODELUPDATER": Struct( - command=StrId, - arguments=make_vector(FunctionArgument), + "command" / StrId, + "arguments" / make_vector(FunctionArgument), ), } ProperActor = Struct( - type=StrId, - position=CVector3D, - rotation=CVector3D, - components=make_vector( + "type" / StrId, + "position" / CVector3D, + "rotation" / CVector3D, + "components" / make_vector( Struct( - component_type=StrId, - command=StrId, - arguments=make_vector(FunctionArgument), + "component_type" / StrId, + "command" / StrId, + "arguments" / make_vector(FunctionArgument), # data=construct.Switch( # construct.this.component_type, # Components, @@ -76,8 +76,8 @@ ) CollisionObject = Struct( - object_type=StrId, - data=Switch( + "object_type" / StrId, + "data" / Switch( construct.this.object_type, collision_formats, ErrorWithMessage(lambda ctx: f"Type {ctx.type} not known, valid types are {list(collision_formats.keys())}."), @@ -85,64 +85,56 @@ ) BMSLD = Struct( - _magic=Const(b"MSLD"), - version=VersionAdapter("1.20.0"), - unk1=Int32ul, - unk2=Int32ul, - unk3=Int32ul, - unk4=Int32ul, - objects_a=make_vector( + "_magic" / Const(b"MSLD"), + "version" / VersionAdapter("1.20.0"), + "unk1" / Int32ul, + "unk2" / Int32ul, + "unk3" / Int32ul, + "unk4" / Int32ul, + "objects_a" / make_vector( Struct( - name=StrId, - unk1=Hex(Int32ul), - unk2=Hex(Int32ul), - unk3=Hex(Int32ul), - unk4=Hex(Int32ul), - unk5=Hex(Int32ul), - unk6=Hex(Int32ul), + "name" / StrId, + "unk1" / Hex(Int32ul), + "unk2" / Hex(Int32ul), + "unk3" / Hex(Int32ul), + "unk4" / Hex(Int32ul), + "unk5" / Hex(Int32ul), + "unk6" / Hex(Int32ul), ) ), - object_b=make_vector( + "enemy_paths" / make_vector( Struct( - name=StrId, - unk01=Hex(Int32ul), - unk02=make_vector( - Struct( - x=Float32l, - y=Float32l, - z=Float32l, - ) - ), + "name" / StrId, + "unk01" / Hex(Int32ul), + "coordinates" / make_vector(CVector3D), ) ), - objects_c=make_dict(CollisionObject), - objects_d=make_dict(CollisionObject), - objects_e=make_vector( + "logic_shapes" / make_dict(CollisionObject), + "spawn_groups" / make_dict(CollisionObject), + "bosses" / make_vector( Struct( - name=StrId, - unk01=StrId, - unk02=Hex(Int32ul), - unk03=Hex(Int32ul), - unk04=Hex(Int32ul), - unk05=Hex(Int32ul), - unk06=Hex(Int32ul), - unk07=Hex(Int32ul), - unk08=Hex(Int32ul), - unk09=Float, - unk10=Float, - unk11=Hex(Int32ul), - unk13=StrId, - unk14=Hex(Int32ul), + "name" / StrId, + "unk01" / StrId, + "unk02" / Hex(Int32ul), + "unk03" / Hex(Int32ul), + "unk04" / Hex(Int32ul), + "unk05" / Hex(Int32ul), + "unk06" / Hex(Int32ul), + "unk07" / Hex(Int32ul), + "unk08" / Hex(Int32ul), + "position?" / CVector3D, + "type" / StrId, + "unk14" / Hex(Int32ul), ) ), - actors=make_dict(ProperActor)[18], - sub_areas=make_vector( + "actors" / make_dict(ProperActor)[18], + "sub_areas" / make_vector( Struct( - name=StrId, - names=make_vector(StrId), + "name" / StrId, + "objects" / make_vector(StrId), ) ), - rest=construct.GreedyBytes, + construct.Terminated, ).compile() From be816c323bb7b6693f5baeee4c9bf50cb8bf31ff Mon Sep 17 00:00:00 2001 From: dyceron Date: Thu, 26 Dec 2024 16:08:52 -0500 Subject: [PATCH 02/38] Move functions from patcher --- .../formats/bmsld.py | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index 5f024206..c6574399 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -1,5 +1,6 @@ from __future__ import annotations +import copy import logging from typing import TYPE_CHECKING @@ -155,7 +156,7 @@ def all_actor_groups(self) -> Iterator[tuple[str, Container]]: def is_actor_in_group(self, group_name: str, actor_name: str) -> bool: generator = (area for area in self.raw.sub_areas if area.name == group_name) for area in generator: - return actor_name in area.names + return actor_name in area.objects return False def get_actor_group(self, group_name: str) -> Container: @@ -170,13 +171,13 @@ def all_actor_group_names_for_actor(self, actor_name: str) -> list[str]: return [ actor_group_name for actor_group_name, actor_group in self.all_actor_groups() - if actor_name in actor_group.names + if actor_name in actor_group.objects ] def remove_actor_from_group(self, group_name: str, actor_name: str): logger.debug("Remove actor %s from group %s", actor_name, group_name) group = self.get_actor_group(group_name) - group.names.remove(actor_name) + group.objects.remove(actor_name) def remove_actor_from_all_groups(self, actor_name: str): group_names = self.all_actor_group_names_for_actor(actor_name) @@ -212,5 +213,32 @@ def compare_func(first: str, second: str) -> bool: def insert_into_entity_group(self, sub_area: Container, name_to_add: str) -> None: # MSR requires to have the names in the sub area list sorted by their crc32 value - sub_area.names.append(name_to_add) - sub_area.names.sort(key=crc32) + sub_area.objects.append(name_to_add) + sub_area.objects.sort(key=crc32) + + def get_layer(self, layer_index = int) -> Container: + return self.raw.actors[layer_index] + + def resolve_actor_reference(self, ref: dict) -> Container: + # FIXME: There is no "default" as layer in SR + layer = int(ref.get("layer", "default")) + return self.raw.actors[layer][ref["actor"]] + + def get_actor(self, layer_index = int, actor_name = str) -> Container: + return self.raw.actors[layer_index][actor_name] + + def copy_actor(self, coords: list[float], template_actor: Container, new_name: str, + layer_index: int, offset: tuple = (0, 0, 0)) -> Container: + new_actor = copy.deepcopy(template_actor) + self.raw.actors[layer_index][new_name] = new_actor + for i in range(2): + new_actor["position"][i] = coords[i] + offset[i] + + return new_actor + + def remove_entity(self, reference: dict) -> None: + layer = reference["layer"] + actor_name = reference["actor"] + + self.raw.actors[layer].pop(actor_name) + self.remove_actor_from_all_groups(actor_name) From 7ee2345b3e72b157293ee962909a920578d444a3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 26 Dec 2024 17:10:02 +0000 Subject: [PATCH 03/38] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../formats/bmsld.py | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index c6574399..f471302e 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING import construct -from construct import Const, Construct, Container, Flag, Float32l, Hex, Int32ul, Struct, Switch +from construct import Const, Construct, Container, Flag, Hex, Int32ul, Struct, Switch from mercury_engine_data_structures.base_resource import BaseResource from mercury_engine_data_structures.common_types import CVector3D, Float, StrId, VersionAdapter, make_dict, make_vector @@ -23,7 +23,8 @@ FunctionArgument = Struct( "type" / StaticPaddedString(4, "ascii"), - "value" / construct.Switch( + "value" + / construct.Switch( construct.this.type, { "s": StrId, @@ -62,7 +63,8 @@ "type" / StrId, "position" / CVector3D, "rotation" / CVector3D, - "components" / make_vector( + "components" + / make_vector( Struct( "component_type" / StrId, "command" / StrId, @@ -78,7 +80,8 @@ CollisionObject = Struct( "object_type" / StrId, - "data" / Switch( + "data" + / Switch( construct.this.object_type, collision_formats, ErrorWithMessage(lambda ctx: f"Type {ctx.type} not known, valid types are {list(collision_formats.keys())}."), @@ -92,7 +95,8 @@ "unk2" / Int32ul, "unk3" / Int32ul, "unk4" / Int32ul, - "objects_a" / make_vector( + "objects_a" + / make_vector( Struct( "name" / StrId, "unk1" / Hex(Int32ul), @@ -103,7 +107,8 @@ "unk6" / Hex(Int32ul), ) ), - "enemy_paths" / make_vector( + "enemy_paths" + / make_vector( Struct( "name" / StrId, "unk01" / Hex(Int32ul), @@ -112,7 +117,8 @@ ), "logic_shapes" / make_dict(CollisionObject), "spawn_groups" / make_dict(CollisionObject), - "bosses" / make_vector( + "bosses" + / make_vector( Struct( "name" / StrId, "unk01" / StrId, @@ -129,7 +135,8 @@ ) ), "actors" / make_dict(ProperActor)[18], - "sub_areas" / make_vector( + "sub_areas" + / make_vector( Struct( "name" / StrId, "objects" / make_vector(StrId), From 9f0dfed5e9675a4dcb68afeb44b8747828b2e6c5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 26 Dec 2024 21:09:02 +0000 Subject: [PATCH 04/38] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/mercury_engine_data_structures/formats/bmsld.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index f471302e..55e59337 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -223,7 +223,7 @@ def insert_into_entity_group(self, sub_area: Container, name_to_add: str) -> Non sub_area.objects.append(name_to_add) sub_area.objects.sort(key=crc32) - def get_layer(self, layer_index = int) -> Container: + def get_layer(self, layer_index=int) -> Container: return self.raw.actors[layer_index] def resolve_actor_reference(self, ref: dict) -> Container: @@ -231,11 +231,12 @@ def resolve_actor_reference(self, ref: dict) -> Container: layer = int(ref.get("layer", "default")) return self.raw.actors[layer][ref["actor"]] - def get_actor(self, layer_index = int, actor_name = str) -> Container: + def get_actor(self, layer_index=int, actor_name=str) -> Container: return self.raw.actors[layer_index][actor_name] - def copy_actor(self, coords: list[float], template_actor: Container, new_name: str, - layer_index: int, offset: tuple = (0, 0, 0)) -> Container: + def copy_actor( + self, coords: list[float], template_actor: Container, new_name: str, layer_index: int, offset: tuple = (0, 0, 0) + ) -> Container: new_actor = copy.deepcopy(template_actor) self.raw.actors[layer_index][new_name] = new_actor for i in range(2): From 6819dd8376e3f5c2f8ab4141fc8524ded50c7875 Mon Sep 17 00:00:00 2001 From: dyceron Date: Thu, 26 Dec 2024 17:08:31 -0500 Subject: [PATCH 05/38] Remove unused things --- .../formats/bmsld.py | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index 55e59337..9517e9c5 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -36,29 +36,6 @@ ), ) -Components = { - "TRIGGER": Struct( - "command" / StrId, - "arguments" / make_vector(FunctionArgument), - ), - "SPAWNGROUP": Struct( - "command" / StrId, - "arguments" / make_vector(FunctionArgument), - ), - "SPAWNPOINT": Struct( - "command" / StrId, - "arguments" / make_vector(FunctionArgument), - ), - "STARTPOINT": Struct( - "command" / StrId, - "arguments" / make_vector(FunctionArgument), - ), - "MODELUPDATER": Struct( - "command" / StrId, - "arguments" / make_vector(FunctionArgument), - ), -} - ProperActor = Struct( "type" / StrId, "position" / CVector3D, @@ -69,11 +46,6 @@ "component_type" / StrId, "command" / StrId, "arguments" / make_vector(FunctionArgument), - # data=construct.Switch( - # construct.this.component_type, - # Components, - # ErrorWithMessage(lambda ctx: f"Unknown component type: {ctx.component_type}", construct.SwitchError), - # ), ) ), ) From 1cde91c92d1230dce576c1882d17202da3fc5523 Mon Sep 17 00:00:00 2001 From: dyceron Date: Thu, 26 Dec 2024 17:23:42 -0500 Subject: [PATCH 06/38] Fix gui scenarios testing --- src/mercury_engine_data_structures/formats/bmsld.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index 9517e9c5..43bddb67 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -60,6 +60,11 @@ ), ) +ExtraActors = Struct( + "group" / StrId, + "actors" / make_vector(Struct("name" / StrId)), +) + BMSLD = Struct( "_magic" / Const(b"MSLD"), "version" / VersionAdapter("1.20.0"), @@ -114,6 +119,7 @@ "objects" / make_vector(StrId), ) ), + "extra_sub_area" / construct.Optional(make_vector(ExtraActors)), construct.Terminated, ).compile() From bbdfafccc75328ebc92aadcb8a818fd2605ec2d8 Mon Sep 17 00:00:00 2001 From: dyceron Date: Thu, 26 Dec 2024 18:20:23 -0500 Subject: [PATCH 07/38] Add new test cases --- .../formats/bmsld.py | 13 +++++++-- tests/formats/test_bmsld.py | 28 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index 43bddb67..bcd7f377 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -201,7 +201,8 @@ def insert_into_entity_group(self, sub_area: Container, name_to_add: str) -> Non sub_area.objects.append(name_to_add) sub_area.objects.sort(key=crc32) - def get_layer(self, layer_index=int) -> Container: + def get_layer(self, layer_index: int) -> Container: + """Returns a layer of actors given an index""" return self.raw.actors[layer_index] def resolve_actor_reference(self, ref: dict) -> Container: @@ -209,12 +210,16 @@ def resolve_actor_reference(self, ref: dict) -> Container: layer = int(ref.get("layer", "default")) return self.raw.actors[layer][ref["actor"]] - def get_actor(self, layer_index=int, actor_name=str) -> Container: + def get_actor(self, layer_index: int, actor_name: str) -> Container: + """Returns an actor given a layer index and actor name""" + if layer_index < 0 or layer_index > 17: + raise ValueError(f"Invalid layer: {layer_index}! Layer indices range from 0-17!") return self.raw.actors[layer_index][actor_name] def copy_actor( self, coords: list[float], template_actor: Container, new_name: str, layer_index: int, offset: tuple = (0, 0, 0) ) -> Container: + """Copies an actor to a new position""" new_actor = copy.deepcopy(template_actor) self.raw.actors[layer_index][new_name] = new_actor for i in range(2): @@ -223,8 +228,12 @@ def copy_actor( return new_actor def remove_entity(self, reference: dict) -> None: + """Deletes an actor given a layer index and actor name""" layer = reference["layer"] actor_name = reference["actor"] + if actor_name not in self.get_layer(layer): + raise KeyError(f"Unable to remove entity '{actor_name}!' Entity does not exist!") + self.raw.actors[layer].pop(actor_name) self.remove_actor_from_all_groups(actor_name) diff --git a/tests/formats/test_bmsld.py b/tests/formats/test_bmsld.py index 7430f243..2f3d3407 100644 --- a/tests/formats/test_bmsld.py +++ b/tests/formats/test_bmsld.py @@ -100,3 +100,31 @@ def test_remove_actor_from_all_groups(surface_bmsld: Bmsld): surface_bmsld.remove_actor_from_all_groups("Moheek_026") groups = surface_bmsld.all_actor_group_names_for_actor("Moheek_026") assert len(groups) == 0 + +def test_get_layer(surface_bmsld: Bmsld): + layer = surface_bmsld.get_layer(17) + assert len(layer) == 1 + +def test_get_actor(surface_bmsld: Bmsld): + actor = surface_bmsld.get_actor(9, "LE_Item_001") + actor["type"] = "powerup_plasmabeam" + actor["position"][0] = -6000.0 + assert surface_bmsld.get_layer(9)["LE_Item_001"]["type"] == "powerup_plasmabeam" + assert surface_bmsld.get_layer(9)["LE_Item_001"]["position"][0] == -6000.0 + + with pytest.raises(ValueError, match="Invalid layer: 18! Layer indices range from 0-17!"): + surface_bmsld.get_actor(18, "FakeActor") + +def test_copy_actor(surface_bmsld: Bmsld): + actor = surface_bmsld.get_actor(9, "LE_Item_001") + surface_bmsld.copy_actor([1000.0, 340.0, 0.0], actor, "CopiedActor", 9) + surface_bmsld.add_actor_to_entity_groups("collision_camera_000", "CopiedActor") + assert surface_bmsld.is_actor_in_group("eg_SubArea_collision_camera_000", "CopiedActor") is True + +def test_remove_entity(surface_bmsld: Bmsld): + actor = "SP_Moheekwall_B_006" + surface_bmsld.remove_entity({"layer": 4, "actor": actor}) + assert surface_bmsld.is_actor_in_group("eg_SubArea_collision_camera_000", actor) is False + + with pytest.raises(KeyError, match="Unable to remove entity 'Kraid!' Entity does not exist!"): + surface_bmsld.remove_entity({"layer": 4, "actor": "Kraid"}) From fc96cd77d6d3ede767141f9e6ece787ae42457cb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 26 Dec 2024 23:20:31 +0000 Subject: [PATCH 08/38] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/formats/test_bmsld.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/formats/test_bmsld.py b/tests/formats/test_bmsld.py index 2f3d3407..6cf1a4d0 100644 --- a/tests/formats/test_bmsld.py +++ b/tests/formats/test_bmsld.py @@ -101,10 +101,12 @@ def test_remove_actor_from_all_groups(surface_bmsld: Bmsld): groups = surface_bmsld.all_actor_group_names_for_actor("Moheek_026") assert len(groups) == 0 + def test_get_layer(surface_bmsld: Bmsld): layer = surface_bmsld.get_layer(17) assert len(layer) == 1 + def test_get_actor(surface_bmsld: Bmsld): actor = surface_bmsld.get_actor(9, "LE_Item_001") actor["type"] = "powerup_plasmabeam" @@ -115,12 +117,14 @@ def test_get_actor(surface_bmsld: Bmsld): with pytest.raises(ValueError, match="Invalid layer: 18! Layer indices range from 0-17!"): surface_bmsld.get_actor(18, "FakeActor") + def test_copy_actor(surface_bmsld: Bmsld): actor = surface_bmsld.get_actor(9, "LE_Item_001") surface_bmsld.copy_actor([1000.0, 340.0, 0.0], actor, "CopiedActor", 9) surface_bmsld.add_actor_to_entity_groups("collision_camera_000", "CopiedActor") assert surface_bmsld.is_actor_in_group("eg_SubArea_collision_camera_000", "CopiedActor") is True + def test_remove_entity(surface_bmsld: Bmsld): actor = "SP_Moheekwall_B_006" surface_bmsld.remove_entity({"layer": 4, "actor": actor}) From a8a613ea3c3821f1672299d2f8aa67dd37c41714 Mon Sep 17 00:00:00 2001 From: dyceron Date: Thu, 26 Dec 2024 21:49:08 -0500 Subject: [PATCH 09/38] More field renames --- .../formats/bmsld.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index bcd7f377..0477c119 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -68,20 +68,14 @@ BMSLD = Struct( "_magic" / Const(b"MSLD"), "version" / VersionAdapter("1.20.0"), - "unk1" / Int32ul, - "unk2" / Int32ul, - "unk3" / Int32ul, - "unk4" / Int32ul, + "unk1" / CVector3D, + "unk2" / Float, "objects_a" / make_vector( Struct( "name" / StrId, - "unk1" / Hex(Int32ul), - "unk2" / Hex(Int32ul), - "unk3" / Hex(Int32ul), - "unk4" / Hex(Int32ul), - "unk5" / Hex(Int32ul), - "unk6" / Hex(Int32ul), + "position" / CVector3D, + "rotation" / CVector3D, ) ), "enemy_paths" From d84a5939639ef246be168f0ac030f70529540820 Mon Sep 17 00:00:00 2001 From: dyceron Date: Thu, 26 Dec 2024 22:09:18 -0500 Subject: [PATCH 10/38] More functions --- .../formats/bmsld.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index 0477c119..89d05416 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -231,3 +231,23 @@ def remove_entity(self, reference: dict) -> None: self.raw.actors[layer].pop(actor_name) self.remove_actor_from_all_groups(actor_name) + + def get_logic_shape(self, logic_shape_idx: int) -> Container: + """Returns a logic shape given an index""" + return self.raw["logic_shapes"][logic_shape_idx] + + ArgumentValue = int | float | str | bool + + def set_argument(self, layer_idx: int, actor_name: str, component_idx: int, argument_idx: int, value: ArgumentValue): + """ + Modify the value of an argument for an actor's component field + + param layer_idx: the layer the actor is in, numbered 0-17 + param actor_name: the actor to be modified + param component_idx: the index for the list of components of the actor + param argument_idx: the index of argument for the the component + param value: the value of the specified argument. can be an integer, float, string, or bool + + """ + actor = self.get_actor(layer_idx, actor_name) + actor["components"][component_idx]["arguments"][argument_idx]["value"] = value From c3a6a6eeda09aa3b00c2098519a7c335d35e79c4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 27 Dec 2024 03:09:25 +0000 Subject: [PATCH 11/38] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/mercury_engine_data_structures/formats/bmsld.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index 89d05416..d6f73c08 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -234,11 +234,13 @@ def remove_entity(self, reference: dict) -> None: def get_logic_shape(self, logic_shape_idx: int) -> Container: """Returns a logic shape given an index""" - return self.raw["logic_shapes"][logic_shape_idx] + return self.raw["logic_shapes"][logic_shape_idx] ArgumentValue = int | float | str | bool - def set_argument(self, layer_idx: int, actor_name: str, component_idx: int, argument_idx: int, value: ArgumentValue): + def set_argument( + self, layer_idx: int, actor_name: str, component_idx: int, argument_idx: int, value: ArgumentValue + ): """ Modify the value of an argument for an actor's component field From bd2a25acfe7cb276ea83cefd07927d0cc78699fe Mon Sep 17 00:00:00 2001 From: dyceron Date: Thu, 26 Dec 2024 22:29:08 -0500 Subject: [PATCH 12/38] Fix `get_logic_shape` using wrong data type --- src/mercury_engine_data_structures/formats/bmsld.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index d6f73c08..ee4a9104 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -232,9 +232,9 @@ def remove_entity(self, reference: dict) -> None: self.raw.actors[layer].pop(actor_name) self.remove_actor_from_all_groups(actor_name) - def get_logic_shape(self, logic_shape_idx: int) -> Container: - """Returns a logic shape given an index""" - return self.raw["logic_shapes"][logic_shape_idx] + def get_logic_shape(self, logic_shape: str) -> Container: + """Returns a logic shape by name""" + return self.raw["logic_shapes"][logic_shape] ArgumentValue = int | float | str | bool From 2d85d39d82aef1073a714004a273001083f81200 Mon Sep 17 00:00:00 2001 From: dyceron Date: Fri, 27 Dec 2024 20:50:42 -0500 Subject: [PATCH 13/38] Function + test cleanup --- .../formats/bmsld.py | 29 ++++++++----------- tests/formats/test_bmsld.py | 16 ++++++---- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index ee4a9104..ace0f336 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -197,19 +197,25 @@ def insert_into_entity_group(self, sub_area: Container, name_to_add: str) -> Non def get_layer(self, layer_index: int) -> Container: """Returns a layer of actors given an index""" + if layer_index < 0 or layer_index > 17: + raise KeyError(f"Invalid layer: {layer_index}! Layer indices range from 0-17!") return self.raw.actors[layer_index] - def resolve_actor_reference(self, ref: dict) -> Container: - # FIXME: There is no "default" as layer in SR - layer = int(ref.get("layer", "default")) - return self.raw.actors[layer][ref["actor"]] + def _check_if_actor_exists(self, layer_index: int, actor_name: str) -> None: + if actor_name not in self.get_layer(layer_index): + raise KeyError(f"No actor named '{actor_name}' found in Layer {layer_index}!") def get_actor(self, layer_index: int, actor_name: str) -> Container: """Returns an actor given a layer index and actor name""" - if layer_index < 0 or layer_index > 17: - raise ValueError(f"Invalid layer: {layer_index}! Layer indices range from 0-17!") + self._check_if_actor_exists(layer_index, actor_name) return self.raw.actors[layer_index][actor_name] + def remove_actor(self, layer_index: int, actor_name: str) -> None: + """Deletes an actor given a layer index and actor name""" + self._check_if_actor_exists(layer_index, actor_name) + self.get_layer(layer_index).pop(actor_name) + self.remove_actor_from_all_groups(actor_name) + def copy_actor( self, coords: list[float], template_actor: Container, new_name: str, layer_index: int, offset: tuple = (0, 0, 0) ) -> Container: @@ -221,17 +227,6 @@ def copy_actor( return new_actor - def remove_entity(self, reference: dict) -> None: - """Deletes an actor given a layer index and actor name""" - layer = reference["layer"] - actor_name = reference["actor"] - - if actor_name not in self.get_layer(layer): - raise KeyError(f"Unable to remove entity '{actor_name}!' Entity does not exist!") - - self.raw.actors[layer].pop(actor_name) - self.remove_actor_from_all_groups(actor_name) - def get_logic_shape(self, logic_shape: str) -> Container: """Returns a logic shape by name""" return self.raw["logic_shapes"][logic_shape] diff --git a/tests/formats/test_bmsld.py b/tests/formats/test_bmsld.py index 6cf1a4d0..96875486 100644 --- a/tests/formats/test_bmsld.py +++ b/tests/formats/test_bmsld.py @@ -106,16 +106,20 @@ def test_get_layer(surface_bmsld: Bmsld): layer = surface_bmsld.get_layer(17) assert len(layer) == 1 + with pytest.raises(KeyError, match="Invalid layer: 18! Layer indices range from 0-17!"): + surface_bmsld.get_layer(18) + def test_get_actor(surface_bmsld: Bmsld): actor = surface_bmsld.get_actor(9, "LE_Item_001") + assert actor is not None actor["type"] = "powerup_plasmabeam" actor["position"][0] = -6000.0 assert surface_bmsld.get_layer(9)["LE_Item_001"]["type"] == "powerup_plasmabeam" assert surface_bmsld.get_layer(9)["LE_Item_001"]["position"][0] == -6000.0 - with pytest.raises(ValueError, match="Invalid layer: 18! Layer indices range from 0-17!"): - surface_bmsld.get_actor(18, "FakeActor") + with pytest.raises(KeyError, match="No actor named 'FakeActor' found in Layer 9!"): + surface_bmsld.get_actor(9, "FakeActor") def test_copy_actor(surface_bmsld: Bmsld): @@ -125,10 +129,10 @@ def test_copy_actor(surface_bmsld: Bmsld): assert surface_bmsld.is_actor_in_group("eg_SubArea_collision_camera_000", "CopiedActor") is True -def test_remove_entity(surface_bmsld: Bmsld): +def test_remove_actor(surface_bmsld: Bmsld): actor = "SP_Moheekwall_B_006" - surface_bmsld.remove_entity({"layer": 4, "actor": actor}) + surface_bmsld.remove_actor(4, actor) assert surface_bmsld.is_actor_in_group("eg_SubArea_collision_camera_000", actor) is False - with pytest.raises(KeyError, match="Unable to remove entity 'Kraid!' Entity does not exist!"): - surface_bmsld.remove_entity({"layer": 4, "actor": "Kraid"}) + with pytest.raises(KeyError): + surface_bmsld.remove_actor(4, "SP_Kraid") From 5d6045d0c641f6efd3364e8d8751122af47b66e1 Mon Sep 17 00:00:00 2001 From: dyceron Date: Fri, 27 Dec 2024 22:02:56 -0500 Subject: [PATCH 14/38] Add ActorLayer enum and update tests --- .../formats/bmsld.py | 57 +++++++++++-------- tests/formats/test_bmsld.py | 21 +++---- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index ace0f336..dc25023f 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -2,6 +2,7 @@ import copy import logging +from enum import IntEnum from typing import TYPE_CHECKING import construct @@ -65,6 +66,18 @@ "actors" / make_vector(Struct("name" / StrId)), ) +class ActorLayer(IntEnum): + TRIGGER = 0 + ENV_TRIGGER = 2 + SPAWNGROUP = 3 + SPAWNPOINT = 4 + STARTPOINT = 5 + PASSIVE = 9 + PLATFORM = 10 + DOOR = 15 + CHOZO_SEAL = 16 + HIDDEN_POWERUP = 17 + BMSLD = Struct( "_magic" / Const(b"MSLD"), "version" / VersionAdapter("1.20.0"), @@ -105,7 +118,7 @@ "unk14" / Hex(Int32ul), ) ), - "actors" / make_dict(ProperActor)[18], + "actor_layers" / make_dict(ProperActor)[18], "sub_areas" / make_vector( Struct( @@ -124,7 +137,7 @@ def construct_class(cls, target_game: Game) -> Construct: return BMSLD def all_actors(self) -> Iterator[tuple[int, str, construct.Container]]: - for layer in self.raw.actors: + for layer in self.raw.actor_layers: for actor_name, actor in layer.items(): yield layer, actor_name, actor @@ -195,33 +208,31 @@ def insert_into_entity_group(self, sub_area: Container, name_to_add: str) -> Non sub_area.objects.append(name_to_add) sub_area.objects.sort(key=crc32) - def get_layer(self, layer_index: int) -> Container: - """Returns a layer of actors given an index""" - if layer_index < 0 or layer_index > 17: - raise KeyError(f"Invalid layer: {layer_index}! Layer indices range from 0-17!") - return self.raw.actors[layer_index] - - def _check_if_actor_exists(self, layer_index: int, actor_name: str) -> None: - if actor_name not in self.get_layer(layer_index): - raise KeyError(f"No actor named '{actor_name}' found in Layer {layer_index}!") - - def get_actor(self, layer_index: int, actor_name: str) -> Container: - """Returns an actor given a layer index and actor name""" - self._check_if_actor_exists(layer_index, actor_name) - return self.raw.actors[layer_index][actor_name] - - def remove_actor(self, layer_index: int, actor_name: str) -> None: - """Deletes an actor given a layer index and actor name""" - self._check_if_actor_exists(layer_index, actor_name) - self.get_layer(layer_index).pop(actor_name) + def get_layer(self, layer: ActorLayer) -> Container: + """Returns a layer of actors using an enum""" + return self.raw.actor_layers[layer] + + def _check_if_actor_exists(self, layer: ActorLayer, actor_name: str) -> None: + if actor_name not in self.get_layer(layer): + raise KeyError(f"No actor named '{actor_name}' found in '{layer}!'") + + def get_actor(self, layer: ActorLayer, actor_name: str) -> Container: + """Returns an actor given a layer using an enum and an actor name""" + self._check_if_actor_exists(layer, actor_name) + return self.raw.actor_layers[layer][actor_name] + + def remove_actor(self, layer: ActorLayer, actor_name: str) -> None: + """Deletes an actor given a layer using an enum and an actor name""" + self._check_if_actor_exists(layer, actor_name) + self.get_layer(layer).pop(actor_name) self.remove_actor_from_all_groups(actor_name) def copy_actor( - self, coords: list[float], template_actor: Container, new_name: str, layer_index: int, offset: tuple = (0, 0, 0) + self, coords: list[float], template_actor: Container, new_name: str, layer: ActorLayer, offset: tuple = (0, 0, 0) ) -> Container: """Copies an actor to a new position""" new_actor = copy.deepcopy(template_actor) - self.raw.actors[layer_index][new_name] = new_actor + self.raw.actor_layers[layer][new_name] = new_actor for i in range(2): new_actor["position"][i] = coords[i] + offset[i] diff --git a/tests/formats/test_bmsld.py b/tests/formats/test_bmsld.py index 96875486..0774df76 100644 --- a/tests/formats/test_bmsld.py +++ b/tests/formats/test_bmsld.py @@ -4,7 +4,7 @@ from tests.test_lib import parse_build_compare_editor from mercury_engine_data_structures import samus_returns_data -from mercury_engine_data_structures.formats.bmsld import Bmsld +from mercury_engine_data_structures.formats.bmsld import ActorLayer, Bmsld sr_missing = [ "maps/levels/c10_samus/s901_alpha/s901_alpha.bmsld", @@ -103,27 +103,24 @@ def test_remove_actor_from_all_groups(surface_bmsld: Bmsld): def test_get_layer(surface_bmsld: Bmsld): - layer = surface_bmsld.get_layer(17) + layer = surface_bmsld.get_layer(ActorLayer.HIDDEN_POWERUP) assert len(layer) == 1 - with pytest.raises(KeyError, match="Invalid layer: 18! Layer indices range from 0-17!"): - surface_bmsld.get_layer(18) - def test_get_actor(surface_bmsld: Bmsld): - actor = surface_bmsld.get_actor(9, "LE_Item_001") + actor = surface_bmsld.get_actor(ActorLayer.PASSIVE, "LE_Item_001") assert actor is not None actor["type"] = "powerup_plasmabeam" actor["position"][0] = -6000.0 - assert surface_bmsld.get_layer(9)["LE_Item_001"]["type"] == "powerup_plasmabeam" - assert surface_bmsld.get_layer(9)["LE_Item_001"]["position"][0] == -6000.0 + assert surface_bmsld.get_layer(ActorLayer.PASSIVE)["LE_Item_001"]["type"] == "powerup_plasmabeam" + assert surface_bmsld.get_layer(ActorLayer.PASSIVE)["LE_Item_001"]["position"][0] == -6000.0 - with pytest.raises(KeyError, match="No actor named 'FakeActor' found in Layer 9!"): - surface_bmsld.get_actor(9, "FakeActor") + with pytest.raises(KeyError, match="No actor named 'FakeActor' found in 'ActorLayer.Passive!'"): + surface_bmsld.get_actor(ActorLayer.PASSIVE, "FakeActor") def test_copy_actor(surface_bmsld: Bmsld): - actor = surface_bmsld.get_actor(9, "LE_Item_001") + actor = surface_bmsld.get_actor(ActorLayer.PASSIVE, "LE_Item_001") surface_bmsld.copy_actor([1000.0, 340.0, 0.0], actor, "CopiedActor", 9) surface_bmsld.add_actor_to_entity_groups("collision_camera_000", "CopiedActor") assert surface_bmsld.is_actor_in_group("eg_SubArea_collision_camera_000", "CopiedActor") is True @@ -131,7 +128,7 @@ def test_copy_actor(surface_bmsld: Bmsld): def test_remove_actor(surface_bmsld: Bmsld): actor = "SP_Moheekwall_B_006" - surface_bmsld.remove_actor(4, actor) + surface_bmsld.remove_actor(ActorLayer.SPAWNPOINT, actor) assert surface_bmsld.is_actor_in_group("eg_SubArea_collision_camera_000", actor) is False with pytest.raises(KeyError): From aa6c28c02f8b7d1edc8b85fe3d3a79a21b6373a4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 28 Dec 2024 03:03:03 +0000 Subject: [PATCH 15/38] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/mercury_engine_data_structures/formats/bmsld.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index dc25023f..effaad58 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -66,6 +66,7 @@ "actors" / make_vector(Struct("name" / StrId)), ) + class ActorLayer(IntEnum): TRIGGER = 0 ENV_TRIGGER = 2 @@ -78,6 +79,7 @@ class ActorLayer(IntEnum): CHOZO_SEAL = 16 HIDDEN_POWERUP = 17 + BMSLD = Struct( "_magic" / Const(b"MSLD"), "version" / VersionAdapter("1.20.0"), @@ -228,7 +230,12 @@ def remove_actor(self, layer: ActorLayer, actor_name: str) -> None: self.remove_actor_from_all_groups(actor_name) def copy_actor( - self, coords: list[float], template_actor: Container, new_name: str, layer: ActorLayer, offset: tuple = (0, 0, 0) + self, + coords: list[float], + template_actor: Container, + new_name: str, + layer: ActorLayer, + offset: tuple = (0, 0, 0), ) -> Container: """Copies an actor to a new position""" new_actor = copy.deepcopy(template_actor) From ee604c5c37f7ac389e886ae3762674ad507d7813 Mon Sep 17 00:00:00 2001 From: dyceron Date: Fri, 27 Dec 2024 23:33:51 -0500 Subject: [PATCH 16/38] Add comments to struct and minor tweaks --- .../formats/bmsld.py | 43 +++++++++++++------ tests/formats/test_bmsld.py | 17 +++++--- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index effaad58..ace8fc10 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -85,7 +85,8 @@ class ActorLayer(IntEnum): "version" / VersionAdapter("1.20.0"), "unk1" / CVector3D, "unk2" / Float, - "objects_a" + # locations where Samus gets repositioned after a cutscene/collecting dna, etc + "landmarks" / make_vector( Struct( "name" / StrId, @@ -93,6 +94,7 @@ class ActorLayer(IntEnum): "rotation" / CVector3D, ) ), + # paths that enemies follow (could be bounds?) "enemy_paths" / make_vector( Struct( @@ -101,9 +103,12 @@ class ActorLayer(IntEnum): "coordinates" / make_vector(CVector3D), ) ), + # areas of influence for enemies "logic_shapes" / make_dict(CollisionObject), + # areas for spawngroups "spawn_groups" / make_dict(CollisionObject), - "bosses" + # boss camera data + "boss_cameras" / make_vector( Struct( "name" / StrId, @@ -115,12 +120,14 @@ class ActorLayer(IntEnum): "unk06" / Hex(Int32ul), "unk07" / Hex(Int32ul), "unk08" / Hex(Int32ul), - "position?" / CVector3D, + "position" / CVector3D, "type" / StrId, "unk14" / Hex(Int32ul), ) ), + # layers for actors "actor_layers" / make_dict(ProperActor)[18], + # collision_cameras and groups "sub_areas" / make_vector( Struct( @@ -128,7 +135,8 @@ class ActorLayer(IntEnum): "objects" / make_vector(StrId), ) ), - "extra_sub_area" / construct.Optional(make_vector(ExtraActors)), + # only used in s000_mainmenu, s010_cockpit, s020_credits + "extra_data" / construct.Optional(make_vector(ExtraActors)), construct.Terminated, ).compile() @@ -211,7 +219,7 @@ def insert_into_entity_group(self, sub_area: Container, name_to_add: str) -> Non sub_area.objects.sort(key=crc32) def get_layer(self, layer: ActorLayer) -> Container: - """Returns a layer of actors using an enum""" + """Returns a layer of actors""" return self.raw.actor_layers[layer] def _check_if_actor_exists(self, layer: ActorLayer, actor_name: str) -> None: @@ -219,12 +227,12 @@ def _check_if_actor_exists(self, layer: ActorLayer, actor_name: str) -> None: raise KeyError(f"No actor named '{actor_name}' found in '{layer}!'") def get_actor(self, layer: ActorLayer, actor_name: str) -> Container: - """Returns an actor given a layer using an enum and an actor name""" + """Returns an actor given a layer and an actor name""" self._check_if_actor_exists(layer, actor_name) return self.raw.actor_layers[layer][actor_name] def remove_actor(self, layer: ActorLayer, actor_name: str) -> None: - """Deletes an actor given a layer using an enum and an actor name""" + """Deletes an actor given a layer and an actor name""" self._check_if_actor_exists(layer, actor_name) self.get_layer(layer).pop(actor_name) self.remove_actor_from_all_groups(actor_name) @@ -237,7 +245,16 @@ def copy_actor( layer: ActorLayer, offset: tuple = (0, 0, 0), ) -> Container: - """Copies an actor to a new position""" + """ + Copies an actor to a new position + + param coords: the x, y, z position for the copied actor + param template_actor: the actor being copied + param new_name: the name for the copied actor + param layer: the layer the copied actor will be added to + param offset: adds an additional offset the copied actor's coordinates + + """ new_actor = copy.deepcopy(template_actor) self.raw.actor_layers[layer][new_name] = new_actor for i in range(2): @@ -248,21 +265,21 @@ def copy_actor( def get_logic_shape(self, logic_shape: str) -> Container: """Returns a logic shape by name""" return self.raw["logic_shapes"][logic_shape] - ArgumentValue = int | float | str | bool def set_argument( - self, layer_idx: int, actor_name: str, component_idx: int, argument_idx: int, value: ArgumentValue - ): + self, layer: ActorLayer, actor_name: str, component_idx: int, argument_idx: int, value: ArgumentValue + ) -> None: """ Modify the value of an argument for an actor's component field - param layer_idx: the layer the actor is in, numbered 0-17 + param layer: the layer the actor exists in param actor_name: the actor to be modified param component_idx: the index for the list of components of the actor param argument_idx: the index of argument for the the component param value: the value of the specified argument. can be an integer, float, string, or bool """ - actor = self.get_actor(layer_idx, actor_name) + actor = self.get_actor(layer, actor_name) actor["components"][component_idx]["arguments"][argument_idx]["value"] = value + # return CollisionEntry(self.raw["logic_shapes"][logic_shape]) diff --git a/tests/formats/test_bmsld.py b/tests/formats/test_bmsld.py index 0774df76..407189ab 100644 --- a/tests/formats/test_bmsld.py +++ b/tests/formats/test_bmsld.py @@ -108,20 +108,23 @@ def test_get_layer(surface_bmsld: Bmsld): def test_get_actor(surface_bmsld: Bmsld): - actor = surface_bmsld.get_actor(ActorLayer.PASSIVE, "LE_Item_001") + layer = ActorLayer.PASSIVE + actor = surface_bmsld.get_actor(layer, "LE_Item_001") assert actor is not None + actor["type"] = "powerup_plasmabeam" actor["position"][0] = -6000.0 - assert surface_bmsld.get_layer(ActorLayer.PASSIVE)["LE_Item_001"]["type"] == "powerup_plasmabeam" - assert surface_bmsld.get_layer(ActorLayer.PASSIVE)["LE_Item_001"]["position"][0] == -6000.0 + actor_by_layer = surface_bmsld.get_layer(layer)["LE_Item_001"] + assert actor_by_layer["type"] == "powerup_plasmabeam" + assert actor_by_layer["position"][0] == -6000.0 - with pytest.raises(KeyError, match="No actor named 'FakeActor' found in 'ActorLayer.Passive!'"): - surface_bmsld.get_actor(ActorLayer.PASSIVE, "FakeActor") + with pytest.raises(KeyError): + surface_bmsld.get_actor(layer, "FakeActor") def test_copy_actor(surface_bmsld: Bmsld): actor = surface_bmsld.get_actor(ActorLayer.PASSIVE, "LE_Item_001") - surface_bmsld.copy_actor([1000.0, 340.0, 0.0], actor, "CopiedActor", 9) + surface_bmsld.copy_actor([1000.0, 340.0, 0.0], actor, "CopiedActor", ActorLayer.PASSIVE) surface_bmsld.add_actor_to_entity_groups("collision_camera_000", "CopiedActor") assert surface_bmsld.is_actor_in_group("eg_SubArea_collision_camera_000", "CopiedActor") is True @@ -132,4 +135,4 @@ def test_remove_actor(surface_bmsld: Bmsld): assert surface_bmsld.is_actor_in_group("eg_SubArea_collision_camera_000", actor) is False with pytest.raises(KeyError): - surface_bmsld.remove_actor(4, "SP_Kraid") + surface_bmsld.remove_actor(ActorLayer.SPAWNPOINT, "SP_Kraid") From 2d644d3c32041c1120d9f44a7846f988addae9e0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 28 Dec 2024 04:34:02 +0000 Subject: [PATCH 17/38] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/mercury_engine_data_structures/formats/bmsld.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index ace8fc10..65a02a20 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -265,6 +265,7 @@ def copy_actor( def get_logic_shape(self, logic_shape: str) -> Container: """Returns a logic shape by name""" return self.raw["logic_shapes"][logic_shape] + ArgumentValue = int | float | str | bool def set_argument( @@ -282,4 +283,5 @@ def set_argument( """ actor = self.get_actor(layer, actor_name) actor["components"][component_idx]["arguments"][argument_idx]["value"] = value + # return CollisionEntry(self.raw["logic_shapes"][logic_shape]) From ae37c04019c6b8fba9550977ceec06e26e84cd51 Mon Sep 17 00:00:00 2001 From: dyceron Date: Fri, 27 Dec 2024 23:49:07 -0500 Subject: [PATCH 18/38] Remove unused part --- src/mercury_engine_data_structures/formats/bmsld.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index 65a02a20..b41d10d8 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -283,5 +283,3 @@ def set_argument( """ actor = self.get_actor(layer, actor_name) actor["components"][component_idx]["arguments"][argument_idx]["value"] = value - - # return CollisionEntry(self.raw["logic_shapes"][logic_shape]) From c3a230205fdaf104ded6b314115b901858e60d67 Mon Sep 17 00:00:00 2001 From: dyceron Date: Sun, 29 Dec 2024 23:51:41 -0500 Subject: [PATCH 19/38] Add classes for setting components and arguments (thanks dunc!) --- .../formats/bmsld.py | 111 ++++++++++++------ 1 file changed, 78 insertions(+), 33 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index b41d10d8..33bd8329 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING import construct -from construct import Const, Construct, Container, Flag, Hex, Int32ul, Struct, Switch +from construct import Const, Construct, Container, Flag, Hex, Int32ul, ListContainer, Struct, Switch from mercury_engine_data_structures.base_resource import BaseResource from mercury_engine_data_structures.common_types import CVector3D, Float, StrId, VersionAdapter, make_dict, make_vector @@ -67,19 +67,6 @@ ) -class ActorLayer(IntEnum): - TRIGGER = 0 - ENV_TRIGGER = 2 - SPAWNGROUP = 3 - SPAWNPOINT = 4 - STARTPOINT = 5 - PASSIVE = 9 - PLATFORM = 10 - DOOR = 15 - CHOZO_SEAL = 16 - HIDDEN_POWERUP = 17 - - BMSLD = Struct( "_magic" / Const(b"MSLD"), "version" / VersionAdapter("1.20.0"), @@ -141,6 +128,82 @@ class ActorLayer(IntEnum): ).compile() +class ActorLayer(IntEnum): + TRIGGER = 0 + ENV_TRIGGER = 2 + SPAWNGROUP = 3 + SPAWNPOINT = 4 + STARTPOINT = 5 + PASSIVE = 9 + PLATFORM = 10 + DOOR = 15 + CHOZO_SEAL = 16 + HIDDEN_POWERUP = 17 + + +class Vec3: + def __init__(self, raw: ListContainer) -> None: + self.raw = raw + + @property + def x(self) -> float: + return self.raw[0] + @x.setter + def x(self, value: float) -> None: + self.raw[0] = value + + @property + def y(self) -> float: + return self.raw[1] + @y.setter + def y(self, value: float) -> None: + self.raw[1] = value + + @property + def z(self) -> float: + return self.raw[2] + @z.setter + def z(self, value: float) -> None: + self.raw[2] = value + + +class BmsldActor: + def __init__(self, raw: Container) -> None: + self._raw = raw + + @property + def actor_type(self) -> str: + return self._raw.type + @actor_type.setter + def actor_type(self, value: str) -> None: + self._raw.type = value + + @property + def position(self) -> Vec3: + return Vec3(self._raw.position) + @position.setter + def position(self, value: Vec3) -> None: + self._raw.position = value.raw + + @property + def rotation(self) -> Vec3: + return Vec3(self._raw.rotation) + @rotation.setter + def rotation(self, value: Vec3) -> None: + self._raw.rotation = value.raw + + def get_component(self, component_idx: int = 0) -> Container: + return ComponentFunction(self._raw.components[component_idx]) + + +class ComponentFunction: + def __init__(self, raw: Container) -> None: + self._raw = raw + + def set_argument(self, argument_idx: int, value: int | float | str | bool) -> None: + self._raw.arguments[argument_idx].value = value + + class Bmsld(BaseResource): @classmethod def construct_class(cls, target_game: Game) -> Construct: @@ -229,7 +292,7 @@ def _check_if_actor_exists(self, layer: ActorLayer, actor_name: str) -> None: def get_actor(self, layer: ActorLayer, actor_name: str) -> Container: """Returns an actor given a layer and an actor name""" self._check_if_actor_exists(layer, actor_name) - return self.raw.actor_layers[layer][actor_name] + return BmsldActor(self.raw.actor_layers[layer][actor_name]) def remove_actor(self, layer: ActorLayer, actor_name: str) -> None: """Deletes an actor given a layer and an actor name""" @@ -265,21 +328,3 @@ def copy_actor( def get_logic_shape(self, logic_shape: str) -> Container: """Returns a logic shape by name""" return self.raw["logic_shapes"][logic_shape] - - ArgumentValue = int | float | str | bool - - def set_argument( - self, layer: ActorLayer, actor_name: str, component_idx: int, argument_idx: int, value: ArgumentValue - ) -> None: - """ - Modify the value of an argument for an actor's component field - - param layer: the layer the actor exists in - param actor_name: the actor to be modified - param component_idx: the index for the list of components of the actor - param argument_idx: the index of argument for the the component - param value: the value of the specified argument. can be an integer, float, string, or bool - - """ - actor = self.get_actor(layer, actor_name) - actor["components"][component_idx]["arguments"][argument_idx]["value"] = value From 316f804d5e17b92c39f4ec843d722f672f0461ff Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 04:51:47 +0000 Subject: [PATCH 20/38] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/mercury_engine_data_structures/formats/bmsld.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index 33bd8329..323d07f8 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -148,6 +148,7 @@ def __init__(self, raw: ListContainer) -> None: @property def x(self) -> float: return self.raw[0] + @x.setter def x(self, value: float) -> None: self.raw[0] = value @@ -155,6 +156,7 @@ def x(self, value: float) -> None: @property def y(self) -> float: return self.raw[1] + @y.setter def y(self, value: float) -> None: self.raw[1] = value @@ -162,6 +164,7 @@ def y(self, value: float) -> None: @property def z(self) -> float: return self.raw[2] + @z.setter def z(self, value: float) -> None: self.raw[2] = value @@ -174,6 +177,7 @@ def __init__(self, raw: Container) -> None: @property def actor_type(self) -> str: return self._raw.type + @actor_type.setter def actor_type(self, value: str) -> None: self._raw.type = value @@ -181,6 +185,7 @@ def actor_type(self, value: str) -> None: @property def position(self) -> Vec3: return Vec3(self._raw.position) + @position.setter def position(self, value: Vec3) -> None: self._raw.position = value.raw @@ -188,6 +193,7 @@ def position(self, value: Vec3) -> None: @property def rotation(self) -> Vec3: return Vec3(self._raw.rotation) + @rotation.setter def rotation(self, value: Vec3) -> None: self._raw.rotation = value.raw From 598a8e56dab57c6b3f5f00d02a6a5e76b8f18edc Mon Sep 17 00:00:00 2001 From: dyceron Date: Mon, 30 Dec 2024 12:00:36 -0500 Subject: [PATCH 21/38] Big review --- .../common_types.py | 30 ++++++++++- .../formats/bmsld.py | 50 +++++-------------- 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/mercury_engine_data_structures/common_types.py b/src/mercury_engine_data_structures/common_types.py index ac2ab9e4..793a4848 100644 --- a/src/mercury_engine_data_structures/common_types.py +++ b/src/mercury_engine_data_structures/common_types.py @@ -6,7 +6,7 @@ import typing import construct -from construct import Adapter +from construct import Adapter, ListContainer from mercury_engine_data_structures.construct_extensions.strings import ( CStringRobust, @@ -520,3 +520,31 @@ def make_enum(values: list[str] | dict[str, int], *, add_invalid: bool = True): if add_invalid: mapping["Invalid"] = 0x7FFFFFFF return construct.Enum(construct.Int32ul, **mapping) + +class Vec3: + def __init__(self, raw: ListContainer) -> None: + self.raw = raw + + @property + def x(self) -> float: + return self.raw[0] + + @x.setter + def x(self, value: float) -> None: + self.raw[0] = value + + @property + def y(self) -> float: + return self.raw[1] + + @y.setter + def y(self, value: float) -> None: + self.raw[1] = value + + @property + def z(self) -> float: + return self.raw[2] + + @z.setter + def z(self, value: float) -> None: + self.raw[2] = value diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index 323d07f8..c869d702 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -9,7 +9,7 @@ from construct import Const, Construct, Container, Flag, Hex, Int32ul, ListContainer, Struct, Switch from mercury_engine_data_structures.base_resource import BaseResource -from mercury_engine_data_structures.common_types import CVector3D, Float, StrId, VersionAdapter, make_dict, make_vector +from mercury_engine_data_structures.common_types import CVector3D, Float, StrId, VersionAdapter, Vec3, make_dict, make_vector from mercury_engine_data_structures.construct_extensions.misc import ErrorWithMessage from mercury_engine_data_structures.construct_extensions.strings import StaticPaddedString from mercury_engine_data_structures.crc import crc32 @@ -41,7 +41,7 @@ "type" / StrId, "position" / CVector3D, "rotation" / CVector3D, - "components" + "component_functions" / make_vector( Struct( "component_type" / StrId, @@ -142,34 +142,6 @@ class ActorLayer(IntEnum): class Vec3: - def __init__(self, raw: ListContainer) -> None: - self.raw = raw - - @property - def x(self) -> float: - return self.raw[0] - - @x.setter - def x(self, value: float) -> None: - self.raw[0] = value - - @property - def y(self) -> float: - return self.raw[1] - - @y.setter - def y(self, value: float) -> None: - self.raw[1] = value - - @property - def z(self) -> float: - return self.raw[2] - - @z.setter - def z(self, value: float) -> None: - self.raw[2] = value - - class BmsldActor: def __init__(self, raw: Container) -> None: self._raw = raw @@ -201,12 +173,15 @@ def rotation(self, value: Vec3) -> None: def get_component(self, component_idx: int = 0) -> Container: return ComponentFunction(self._raw.components[component_idx]) +ArgumentType = int | float | str | bool class ComponentFunction: def __init__(self, raw: Container) -> None: self._raw = raw - def set_argument(self, argument_idx: int, value: int | float | str | bool) -> None: + def set_argument(self, argument_idx: int, value: ArgumentType) -> None: + if not isinstance(value, ArgumentType): + raise TypeError(f"Invalid type {type(value)} for field {value}") self._raw.arguments[argument_idx].value = value @@ -295,7 +270,7 @@ def _check_if_actor_exists(self, layer: ActorLayer, actor_name: str) -> None: if actor_name not in self.get_layer(layer): raise KeyError(f"No actor named '{actor_name}' found in '{layer}!'") - def get_actor(self, layer: ActorLayer, actor_name: str) -> Container: + def get_actor(self, layer: ActorLayer, actor_name: str) -> BmsldActor: """Returns an actor given a layer and an actor name""" self._check_if_actor_exists(layer, actor_name) return BmsldActor(self.raw.actor_layers[layer][actor_name]) @@ -308,8 +283,8 @@ def remove_actor(self, layer: ActorLayer, actor_name: str) -> None: def copy_actor( self, - coords: list[float], - template_actor: Container, + position: list[float], + template_actor: BmsldActor, new_name: str, layer: ActorLayer, offset: tuple = (0, 0, 0), @@ -317,17 +292,16 @@ def copy_actor( """ Copies an actor to a new position - param coords: the x, y, z position for the copied actor + param position: the x, y, z position for the copied actor param template_actor: the actor being copied param new_name: the name for the copied actor param layer: the layer the copied actor will be added to param offset: adds an additional offset the copied actor's coordinates """ - new_actor = copy.deepcopy(template_actor) + new_actor = BmsldActor(copy.deepcopy(template_actor)) self.raw.actor_layers[layer][new_name] = new_actor - for i in range(2): - new_actor["position"][i] = coords[i] + offset[i] + new_actor.position = Vec3([p + o for p, o in zip(position, offset)]) return new_actor From 1054131fe455558872ab5e1becfeda34879e5795 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 17:00:43 +0000 Subject: [PATCH 22/38] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/mercury_engine_data_structures/common_types.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mercury_engine_data_structures/common_types.py b/src/mercury_engine_data_structures/common_types.py index 793a4848..aa78d073 100644 --- a/src/mercury_engine_data_structures/common_types.py +++ b/src/mercury_engine_data_structures/common_types.py @@ -521,6 +521,7 @@ def make_enum(values: list[str] | dict[str, int], *, add_invalid: bool = True): mapping["Invalid"] = 0x7FFFFFFF return construct.Enum(construct.Int32ul, **mapping) + class Vec3: def __init__(self, raw: ListContainer) -> None: self.raw = raw From 5fb5f73c99722659e3b9ab62bd8108d121e398c6 Mon Sep 17 00:00:00 2001 From: dyceron Date: Mon, 30 Dec 2024 12:02:27 -0500 Subject: [PATCH 23/38] Remove stray lines --- src/mercury_engine_data_structures/formats/bmsld.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index c869d702..d31f9380 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING import construct -from construct import Const, Construct, Container, Flag, Hex, Int32ul, ListContainer, Struct, Switch +from construct import Const, Construct, Container, Flag, Hex, Int32ul, Struct, Switch from mercury_engine_data_structures.base_resource import BaseResource from mercury_engine_data_structures.common_types import CVector3D, Float, StrId, VersionAdapter, Vec3, make_dict, make_vector @@ -141,7 +141,6 @@ class ActorLayer(IntEnum): HIDDEN_POWERUP = 17 -class Vec3: class BmsldActor: def __init__(self, raw: Container) -> None: self._raw = raw From 8bfe687a897f1937f4461305df893f9710706c66 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 17:02:36 +0000 Subject: [PATCH 24/38] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/mercury_engine_data_structures/formats/bmsld.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index d31f9380..4559a3da 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -9,7 +9,15 @@ from construct import Const, Construct, Container, Flag, Hex, Int32ul, Struct, Switch from mercury_engine_data_structures.base_resource import BaseResource -from mercury_engine_data_structures.common_types import CVector3D, Float, StrId, VersionAdapter, Vec3, make_dict, make_vector +from mercury_engine_data_structures.common_types import ( + CVector3D, + Float, + StrId, + Vec3, + VersionAdapter, + make_dict, + make_vector, +) from mercury_engine_data_structures.construct_extensions.misc import ErrorWithMessage from mercury_engine_data_structures.construct_extensions.strings import StaticPaddedString from mercury_engine_data_structures.crc import crc32 @@ -172,8 +180,10 @@ def rotation(self, value: Vec3) -> None: def get_component(self, component_idx: int = 0) -> Container: return ComponentFunction(self._raw.components[component_idx]) + ArgumentType = int | float | str | bool + class ComponentFunction: def __init__(self, raw: Container) -> None: self._raw = raw From 956295f7521b95932d0402ad629efe8a3783f521 Mon Sep 17 00:00:00 2001 From: dyceron Date: Mon, 30 Dec 2024 12:04:44 -0500 Subject: [PATCH 25/38] Fix component_function --- src/mercury_engine_data_structures/formats/bmsld.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index 4559a3da..3b4b23cf 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -177,8 +177,8 @@ def rotation(self) -> Vec3: def rotation(self, value: Vec3) -> None: self._raw.rotation = value.raw - def get_component(self, component_idx: int = 0) -> Container: - return ComponentFunction(self._raw.components[component_idx]) + def get_component_function(self, component_idx: int = 0) -> Container: + return ComponentFunction(self._raw.component_functions[component_idx]) ArgumentType = int | float | str | bool From 872dd04576b035d60488e68fa5dba06e1e6997c3 Mon Sep 17 00:00:00 2001 From: dyceron Date: Mon, 30 Dec 2024 12:54:20 -0500 Subject: [PATCH 26/38] More concerns --- .../common_types.py | 4 +-- .../formats/bmsld.py | 26 ++++++++++++------- tests/formats/test_bmsld.py | 4 +-- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/mercury_engine_data_structures/common_types.py b/src/mercury_engine_data_structures/common_types.py index aa78d073..90631849 100644 --- a/src/mercury_engine_data_structures/common_types.py +++ b/src/mercury_engine_data_structures/common_types.py @@ -6,7 +6,7 @@ import typing import construct -from construct import Adapter, ListContainer +from construct import Adapter from mercury_engine_data_structures.construct_extensions.strings import ( CStringRobust, @@ -523,7 +523,7 @@ def make_enum(values: list[str] | dict[str, int], *, add_invalid: bool = True): class Vec3: - def __init__(self, raw: ListContainer) -> None: + def __init__(self, raw: list[float]) -> None: self.raw = raw @property diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index 3b4b23cf..b0d907b4 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING import construct -from construct import Const, Construct, Container, Flag, Hex, Int32ul, Struct, Switch +from construct import Const, Construct, Container, Flag, Hex, Int32ul, ListContainer, Struct, Switch from mercury_engine_data_structures.base_resource import BaseResource from mercury_engine_data_structures.common_types import ( @@ -183,15 +183,23 @@ def get_component_function(self, component_idx: int = 0) -> Container: ArgumentType = int | float | str | bool +ARGUMENT_TYPES = { + "s": str, + "f": float, + "b": bool, + "i": int, +} class ComponentFunction: def __init__(self, raw: Container) -> None: self._raw = raw def set_argument(self, argument_idx: int, value: ArgumentType) -> None: - if not isinstance(value, ArgumentType): - raise TypeError(f"Invalid type {type(value)} for field {value}") - self._raw.arguments[argument_idx].value = value + argument = self._raw.arguments[argument_idx] + expected_type = ARGUMENT_TYPES[argument.type] + if not isinstance(value, expected_type): + raise TypeError(f"Invalid argument type: expected {expected_type}, got {type(value)} ({value})") + argument.value = value class Bmsld(BaseResource): @@ -271,12 +279,12 @@ def insert_into_entity_group(self, sub_area: Container, name_to_add: str) -> Non sub_area.objects.append(name_to_add) sub_area.objects.sort(key=crc32) - def get_layer(self, layer: ActorLayer) -> Container: + def _get_layer(self, layer: ActorLayer) -> ListContainer: """Returns a layer of actors""" return self.raw.actor_layers[layer] def _check_if_actor_exists(self, layer: ActorLayer, actor_name: str) -> None: - if actor_name not in self.get_layer(layer): + if actor_name not in self._get_layer(layer): raise KeyError(f"No actor named '{actor_name}' found in '{layer}!'") def get_actor(self, layer: ActorLayer, actor_name: str) -> BmsldActor: @@ -287,7 +295,7 @@ def get_actor(self, layer: ActorLayer, actor_name: str) -> BmsldActor: def remove_actor(self, layer: ActorLayer, actor_name: str) -> None: """Deletes an actor given a layer and an actor name""" self._check_if_actor_exists(layer, actor_name) - self.get_layer(layer).pop(actor_name) + self._get_layer(layer).pop(actor_name) self.remove_actor_from_all_groups(actor_name) def copy_actor( @@ -297,7 +305,7 @@ def copy_actor( new_name: str, layer: ActorLayer, offset: tuple = (0, 0, 0), - ) -> Container: + ) -> BmsldActor: """ Copies an actor to a new position @@ -309,7 +317,7 @@ def copy_actor( """ new_actor = BmsldActor(copy.deepcopy(template_actor)) - self.raw.actor_layers[layer][new_name] = new_actor + self.raw.actor_layers[layer][new_name] = new_actor._raw new_actor.position = Vec3([p + o for p, o in zip(position, offset)]) return new_actor diff --git a/tests/formats/test_bmsld.py b/tests/formats/test_bmsld.py index 407189ab..75f102d5 100644 --- a/tests/formats/test_bmsld.py +++ b/tests/formats/test_bmsld.py @@ -103,7 +103,7 @@ def test_remove_actor_from_all_groups(surface_bmsld: Bmsld): def test_get_layer(surface_bmsld: Bmsld): - layer = surface_bmsld.get_layer(ActorLayer.HIDDEN_POWERUP) + layer = surface_bmsld._get_layer(ActorLayer.HIDDEN_POWERUP) assert len(layer) == 1 @@ -114,7 +114,7 @@ def test_get_actor(surface_bmsld: Bmsld): actor["type"] = "powerup_plasmabeam" actor["position"][0] = -6000.0 - actor_by_layer = surface_bmsld.get_layer(layer)["LE_Item_001"] + actor_by_layer = surface_bmsld._get_layer(layer)["LE_Item_001"] assert actor_by_layer["type"] == "powerup_plasmabeam" assert actor_by_layer["position"][0] == -6000.0 From 077f1a99346c8af5743a468c84a55a9a0fdd1e0b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 17:54:40 +0000 Subject: [PATCH 27/38] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/mercury_engine_data_structures/formats/bmsld.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index b0d907b4..66665779 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -190,6 +190,7 @@ def get_component_function(self, component_idx: int = 0) -> Container: "i": int, } + class ComponentFunction: def __init__(self, raw: Container) -> None: self._raw = raw From a938038acf88b3a46f497b9c9aff2b48791fa1a0 Mon Sep 17 00:00:00 2001 From: dyceron Date: Sat, 4 Jan 2025 16:39:20 -0500 Subject: [PATCH 28/38] Remove extra class in `common_types.py` --- .../common_types.py | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/src/mercury_engine_data_structures/common_types.py b/src/mercury_engine_data_structures/common_types.py index 90631849..ac2ab9e4 100644 --- a/src/mercury_engine_data_structures/common_types.py +++ b/src/mercury_engine_data_structures/common_types.py @@ -520,32 +520,3 @@ def make_enum(values: list[str] | dict[str, int], *, add_invalid: bool = True): if add_invalid: mapping["Invalid"] = 0x7FFFFFFF return construct.Enum(construct.Int32ul, **mapping) - - -class Vec3: - def __init__(self, raw: list[float]) -> None: - self.raw = raw - - @property - def x(self) -> float: - return self.raw[0] - - @x.setter - def x(self, value: float) -> None: - self.raw[0] = value - - @property - def y(self) -> float: - return self.raw[1] - - @y.setter - def y(self, value: float) -> None: - self.raw[1] = value - - @property - def z(self) -> float: - return self.raw[2] - - @z.setter - def z(self, value: float) -> None: - self.raw[2] = value From ab51f699a1db9c5f30b8fdd65da33be1804809a7 Mon Sep 17 00:00:00 2001 From: dyceron Date: Mon, 6 Jan 2025 21:43:40 -0500 Subject: [PATCH 29/38] More updates and dunc's reviews --- .../formats/bmsld.py | 75 +++++++++---------- tests/formats/test_bmsld.py | 17 ++++- 2 files changed, 51 insertions(+), 41 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index 66665779..c2dd7f68 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING import construct -from construct import Const, Construct, Container, Flag, Hex, Int32ul, ListContainer, Struct, Switch +from construct import Const, Construct, Container, Flag, Hex, Int32ul, ListContainer, Sequence, Struct, Switch from mercury_engine_data_structures.base_resource import BaseResource from mercury_engine_data_structures.common_types import ( @@ -21,7 +21,7 @@ from mercury_engine_data_structures.construct_extensions.misc import ErrorWithMessage from mercury_engine_data_structures.construct_extensions.strings import StaticPaddedString from mercury_engine_data_structures.crc import crc32 -from mercury_engine_data_structures.formats.collision import collision_formats +from mercury_engine_data_structures.formats.collision import CollisionEntry, collision_formats if TYPE_CHECKING: from collections.abc import Iterator @@ -70,7 +70,6 @@ ) ExtraActors = Struct( - "group" / StrId, "actors" / make_vector(Struct("name" / StrId)), ) @@ -123,15 +122,9 @@ # layers for actors "actor_layers" / make_dict(ProperActor)[18], # collision_cameras and groups - "sub_areas" - / make_vector( - Struct( - "name" / StrId, - "objects" / make_vector(StrId), - ) - ), + "sub_areas" / make_dict(make_vector(StrId)), # only used in s000_mainmenu, s010_cockpit, s020_credits - "extra_data" / construct.Optional(make_vector(ExtraActors)), + "extra_data" / construct.Optional(make_dict(ExtraActors)), construct.Terminated, ).compile() @@ -163,21 +156,21 @@ def actor_type(self, value: str) -> None: @property def position(self) -> Vec3: - return Vec3(self._raw.position) + return self._raw.position @position.setter def position(self, value: Vec3) -> None: - self._raw.position = value.raw + self._raw.position = value @property def rotation(self) -> Vec3: - return Vec3(self._raw.rotation) + return self._raw.rotation @rotation.setter def rotation(self, value: Vec3) -> None: - self._raw.rotation = value.raw + self._raw.rotation = value - def get_component_function(self, component_idx: int = 0) -> Container: + def get_component_function(self, component_idx: int = 0) -> ComponentFunction: return ComponentFunction(self._raw.component_functions[component_idx]) @@ -195,6 +188,15 @@ class ComponentFunction: def __init__(self, raw: Container) -> None: self._raw = raw + def __repr__(self) -> str: + arguments = [ + '"{arg.value}"' if isinstance(arg.value, str) + else arg.value + for arg in self._raw.arguments + ] + arg_repr = ", ".join(arguments) + return f"{self._raw.component_type}.{self._raw.command}({arg_repr})" + def set_argument(self, argument_idx: int, value: ArgumentType) -> None: argument = self._raw.arguments[argument_idx] expected_type = ARGUMENT_TYPES[argument.type] @@ -208,40 +210,36 @@ class Bmsld(BaseResource): def construct_class(cls, target_game: Game) -> Construct: return BMSLD - def all_actors(self) -> Iterator[tuple[int, str, construct.Container]]: + def all_actors(self) -> Iterator[tuple[ActorLayer, str, BmsldActor]]: for layer in self.raw.actor_layers: for actor_name, actor in layer.items(): yield layer, actor_name, actor - def all_actor_groups(self) -> Iterator[tuple[str, Container]]: - for sub_area in self.raw.sub_areas: - yield sub_area.name, sub_area + @property + def actor_groups(self) -> dict[str, list[str]]: + return self.raw.sub_areas + + @actor_groups.setter + def actor_groups(self, value: dict[str, list[str]]) -> None: + self.raw.sub_areas = value def is_actor_in_group(self, group_name: str, actor_name: str) -> bool: - generator = (area for area in self.raw.sub_areas if area.name == group_name) - for area in generator: - return actor_name in area.objects - return False + return actor_name in self.actor_groups[group_name] def get_actor_group(self, group_name: str) -> Container: group = next( - (sub_area for sub_area_name, sub_area in self.all_actor_groups() if sub_area_name == group_name), None + (sub_area for sub_area in self.actor_groups if sub_area == group_name), None ) if group is None: raise KeyError(f"No group found with name for {group_name}") return group def all_actor_group_names_for_actor(self, actor_name: str) -> list[str]: - return [ - actor_group_name - for actor_group_name, actor_group in self.all_actor_groups() - if actor_name in actor_group.objects - ] + return [group_name for group_name, group in self.actor_groups.items() if actor_name in group] def remove_actor_from_group(self, group_name: str, actor_name: str): logger.debug("Remove actor %s from group %s", actor_name, group_name) - group = self.get_actor_group(group_name) - group.objects.remove(actor_name) + self.actor_groups[group_name].remove(actor_name) def remove_actor_from_all_groups(self, actor_name: str): group_names = self.all_actor_group_names_for_actor(actor_name) @@ -267,18 +265,19 @@ def compare_func(first: str, second: str) -> bool: return first == f"eg_SubArea_{second}" collision_camera_groups = [ - group for group_name, group in self.all_actor_groups() if compare_func(group_name, collision_camera_name) + group for group in self.actor_groups if compare_func(group, collision_camera_name) ] if len(collision_camera_groups) == 0: raise Exception(f"No entity group found for {collision_camera_name}") for group in collision_camera_groups: - logger.debug("Add actor %s to group %s", actor_name, group.name) + logger.debug("Add actor %s to group %s", actor_name, group) self.insert_into_entity_group(group, actor_name) def insert_into_entity_group(self, sub_area: Container, name_to_add: str) -> None: # MSR requires to have the names in the sub area list sorted by their crc32 value - sub_area.objects.append(name_to_add) - sub_area.objects.sort(key=crc32) + entity_group = self.actor_groups[sub_area] + entity_group.append(name_to_add) + entity_group.sort(key=crc32) def _get_layer(self, layer: ActorLayer) -> ListContainer: """Returns a layer of actors""" @@ -323,6 +322,6 @@ def copy_actor( return new_actor - def get_logic_shape(self, logic_shape: str) -> Container: + def get_logic_shape(self, logic_shape: str) -> CollisionEntry: """Returns a logic shape by name""" - return self.raw["logic_shapes"][logic_shape] + return CollisionEntry(self.raw["logic_shapes"][logic_shape]) diff --git a/tests/formats/test_bmsld.py b/tests/formats/test_bmsld.py index 75f102d5..3517c733 100644 --- a/tests/formats/test_bmsld.py +++ b/tests/formats/test_bmsld.py @@ -34,7 +34,7 @@ def test_bmsld(samus_returns_tree, bmsld_path): def test_all_actor_groups(surface_bmsld: Bmsld): - all_groups = surface_bmsld.all_actor_groups() + all_groups = surface_bmsld.actor_groups assert len(list(all_groups)) == 32 @@ -112,8 +112,8 @@ def test_get_actor(surface_bmsld: Bmsld): actor = surface_bmsld.get_actor(layer, "LE_Item_001") assert actor is not None - actor["type"] = "powerup_plasmabeam" - actor["position"][0] = -6000.0 + actor.actor_type = "powerup_plasmabeam" + actor.position.x = -6000.0 actor_by_layer = surface_bmsld._get_layer(layer)["LE_Item_001"] assert actor_by_layer["type"] == "powerup_plasmabeam" assert actor_by_layer["position"][0] == -6000.0 @@ -136,3 +136,14 @@ def test_remove_actor(surface_bmsld: Bmsld): with pytest.raises(KeyError): surface_bmsld.remove_actor(ActorLayer.SPAWNPOINT, "SP_Kraid") + + +def test_get_logic_shape(surface_bmsld: Bmsld): + logic_shape = surface_bmsld.get_logic_shape("LS_Spikes_001") + assert logic_shape is not None + + poly = logic_shape.get_poly(0) + assert poly["num_points"] == 4 + + point = logic_shape.get_point(0, 0) + assert point["x"] != point["y"] From b3ad2c2c470cf868b1a73cf3da2726a164aaf8e0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Jan 2025 02:43:48 +0000 Subject: [PATCH 30/38] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../formats/bmsld.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index c2dd7f68..42f3318a 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING import construct -from construct import Const, Construct, Container, Flag, Hex, Int32ul, ListContainer, Sequence, Struct, Switch +from construct import Const, Construct, Container, Flag, Hex, Int32ul, ListContainer, Struct, Switch from mercury_engine_data_structures.base_resource import BaseResource from mercury_engine_data_structures.common_types import ( @@ -189,11 +189,7 @@ def __init__(self, raw: Container) -> None: self._raw = raw def __repr__(self) -> str: - arguments = [ - '"{arg.value}"' if isinstance(arg.value, str) - else arg.value - for arg in self._raw.arguments - ] + arguments = ['"{arg.value}"' if isinstance(arg.value, str) else arg.value for arg in self._raw.arguments] arg_repr = ", ".join(arguments) return f"{self._raw.component_type}.{self._raw.command}({arg_repr})" @@ -227,9 +223,7 @@ def is_actor_in_group(self, group_name: str, actor_name: str) -> bool: return actor_name in self.actor_groups[group_name] def get_actor_group(self, group_name: str) -> Container: - group = next( - (sub_area for sub_area in self.actor_groups if sub_area == group_name), None - ) + group = next((sub_area for sub_area in self.actor_groups if sub_area == group_name), None) if group is None: raise KeyError(f"No group found with name for {group_name}") return group @@ -264,9 +258,7 @@ def compare_func(first: str, second: str) -> bool: else: return first == f"eg_SubArea_{second}" - collision_camera_groups = [ - group for group in self.actor_groups if compare_func(group, collision_camera_name) - ] + collision_camera_groups = [group for group in self.actor_groups if compare_func(group, collision_camera_name)] if len(collision_camera_groups) == 0: raise Exception(f"No entity group found for {collision_camera_name}") for group in collision_camera_groups: From 8340304cdc8c70c7df288cac65ce5a27a5b7fa0c Mon Sep 17 00:00:00 2001 From: dyceron Date: Mon, 6 Jan 2025 22:42:52 -0500 Subject: [PATCH 31/38] add repr test --- src/mercury_engine_data_structures/formats/bmsld.py | 2 +- tests/formats/test_bmsld.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index 42f3318a..cb337473 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -189,7 +189,7 @@ def __init__(self, raw: Container) -> None: self._raw = raw def __repr__(self) -> str: - arguments = ['"{arg.value}"' if isinstance(arg.value, str) else arg.value for arg in self._raw.arguments] + arguments = [repr(arg.value) for arg in self._raw.arguments] arg_repr = ", ".join(arguments) return f"{self._raw.component_type}.{self._raw.command}({arg_repr})" diff --git a/tests/formats/test_bmsld.py b/tests/formats/test_bmsld.py index 3517c733..d0075076 100644 --- a/tests/formats/test_bmsld.py +++ b/tests/formats/test_bmsld.py @@ -147,3 +147,11 @@ def test_get_logic_shape(surface_bmsld: Bmsld): point = logic_shape.get_point(0, 0) assert point["x"] != point["y"] + + +def test_repr(surface_bmsld: Bmsld): + actor = surface_bmsld.get_actor( ActorLayer.STARTPOINT, "StartPoint0").get_component_function() + assert ( + repr(actor) + == "STARTPOINT.SetScenarioParams('CurrentScenario.OnShipStartPointTeleport', '', True, False, False)" + ) From ac4cbbe58af86b1a2c29afcde19745a3d24d1213 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Jan 2025 03:43:17 +0000 Subject: [PATCH 32/38] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/formats/test_bmsld.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/formats/test_bmsld.py b/tests/formats/test_bmsld.py index d0075076..f3ffabb9 100644 --- a/tests/formats/test_bmsld.py +++ b/tests/formats/test_bmsld.py @@ -150,7 +150,7 @@ def test_get_logic_shape(surface_bmsld: Bmsld): def test_repr(surface_bmsld: Bmsld): - actor = surface_bmsld.get_actor( ActorLayer.STARTPOINT, "StartPoint0").get_component_function() + actor = surface_bmsld.get_actor(ActorLayer.STARTPOINT, "StartPoint0").get_component_function() assert ( repr(actor) == "STARTPOINT.SetScenarioParams('CurrentScenario.OnShipStartPointTeleport', '', True, False, False)" From b1cd44fc1e5327cc07b9a6335c27b431fa9667b0 Mon Sep 17 00:00:00 2001 From: dyceron Date: Mon, 6 Jan 2025 22:46:49 -0500 Subject: [PATCH 33/38] Simplfy `extra_data` --- src/mercury_engine_data_structures/formats/bmsld.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index cb337473..e55f5c7d 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -69,10 +69,6 @@ ), ) -ExtraActors = Struct( - "actors" / make_vector(Struct("name" / StrId)), -) - BMSLD = Struct( "_magic" / Const(b"MSLD"), @@ -124,7 +120,7 @@ # collision_cameras and groups "sub_areas" / make_dict(make_vector(StrId)), # only used in s000_mainmenu, s010_cockpit, s020_credits - "extra_data" / construct.Optional(make_dict(ExtraActors)), + "extra_data" / construct.Optional(make_dict(make_vector(StrId))), construct.Terminated, ).compile() From f70317377ea9a0fc3b0f90bd68a2b7c5c3b1bf5f Mon Sep 17 00:00:00 2001 From: dyceron Date: Mon, 6 Jan 2025 22:56:28 -0500 Subject: [PATCH 34/38] This is probably wrong --- src/mercury_engine_data_structures/formats/bmsld.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index e55f5c7d..fac14af2 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -205,6 +205,8 @@ def construct_class(cls, target_game: Game) -> Construct: def all_actors(self) -> Iterator[tuple[ActorLayer, str, BmsldActor]]: for layer in self.raw.actor_layers: for actor_name, actor in layer.items(): + layer = ActorLayer + actor = BmsldActor yield layer, actor_name, actor @property From 3df8133e849a329f4def71c234d2c4fc146cec20 Mon Sep 17 00:00:00 2001 From: dyceron Date: Wed, 8 Jan 2025 19:38:45 -0500 Subject: [PATCH 35/38] Ok this is probably correct --- src/mercury_engine_data_structures/formats/bmsld.py | 4 +--- tests/formats/test_bmsld.py | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index fac14af2..a99ba900 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -205,9 +205,7 @@ def construct_class(cls, target_game: Game) -> Construct: def all_actors(self) -> Iterator[tuple[ActorLayer, str, BmsldActor]]: for layer in self.raw.actor_layers: for actor_name, actor in layer.items(): - layer = ActorLayer - actor = BmsldActor - yield layer, actor_name, actor + yield ActorLayer, BmsldActor(actor) @property def actor_groups(self) -> dict[str, list[str]]: diff --git a/tests/formats/test_bmsld.py b/tests/formats/test_bmsld.py index f3ffabb9..90983506 100644 --- a/tests/formats/test_bmsld.py +++ b/tests/formats/test_bmsld.py @@ -65,6 +65,8 @@ def test_get_actor_group(surface_bmsld: Bmsld): def test_all_actors(surface_bmsld: Bmsld): all_actors = list(surface_bmsld.all_actors()) assert len(all_actors) == 232 + # Number of active layers should be 10 + assert len(ActorLayer) == 10 def test_all_actor_group_names_for_actor(surface_bmsld: Bmsld): From 349dd1f3e198f6c03c19e863b60e62fce96e4690 Mon Sep 17 00:00:00 2001 From: dyceron Date: Tue, 14 Jan 2025 20:52:29 -0500 Subject: [PATCH 36/38] Rename fields --- src/mercury_engine_data_structures/formats/bmsld.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index a99ba900..ea5fdc90 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -118,9 +118,9 @@ # layers for actors "actor_layers" / make_dict(ProperActor)[18], # collision_cameras and groups - "sub_areas" / make_dict(make_vector(StrId)), + "actor_groups" / make_dict(make_vector(StrId)), # only used in s000_mainmenu, s010_cockpit, s020_credits - "extra_data" / construct.Optional(make_dict(make_vector(StrId))), + "extra_actor_groups" / construct.Optional(make_dict(make_vector(StrId))), construct.Terminated, ).compile() @@ -209,11 +209,11 @@ def all_actors(self) -> Iterator[tuple[ActorLayer, str, BmsldActor]]: @property def actor_groups(self) -> dict[str, list[str]]: - return self.raw.sub_areas + return self.raw.actor_groups @actor_groups.setter def actor_groups(self, value: dict[str, list[str]]) -> None: - self.raw.sub_areas = value + self.raw.actor_groups = value def is_actor_in_group(self, group_name: str, actor_name: str) -> bool: return actor_name in self.actor_groups[group_name] From a6733d8a523ad97e38d13faa30f66e3df3fca618 Mon Sep 17 00:00:00 2001 From: dyceron Date: Tue, 14 Jan 2025 21:45:49 -0500 Subject: [PATCH 37/38] Start fixing typing --- .../formats/bmsld.py | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index ea5fdc90..e2374cc1 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -3,7 +3,7 @@ import copy import logging from enum import IntEnum -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, TypeAlias import construct from construct import Const, Construct, Container, Flag, Hex, Int32ul, ListContainer, Struct, Switch @@ -125,6 +125,11 @@ ).compile() +ActorName: TypeAlias = str +ActorGroupName: TypeAlias = str +ActorGroup: TypeAlias = dict[str, list[str]] + + class ActorLayer(IntEnum): TRIGGER = 0 ENV_TRIGGER = 2 @@ -202,41 +207,43 @@ class Bmsld(BaseResource): def construct_class(cls, target_game: Game) -> Construct: return BMSLD - def all_actors(self) -> Iterator[tuple[ActorLayer, str, BmsldActor]]: + def all_actors(self) -> Iterator[tuple[ActorLayer, ActorName, BmsldActor]]: for layer in self.raw.actor_layers: for actor_name, actor in layer.items(): yield ActorLayer, BmsldActor(actor) @property - def actor_groups(self) -> dict[str, list[str]]: + def actor_groups(self) -> ActorGroup: return self.raw.actor_groups @actor_groups.setter - def actor_groups(self, value: dict[str, list[str]]) -> None: + def actor_groups(self, value: ActorGroup) -> None: self.raw.actor_groups = value - def is_actor_in_group(self, group_name: str, actor_name: str) -> bool: + def is_actor_in_group(self, group_name: ActorGroupName, actor_name: ActorName) -> bool: return actor_name in self.actor_groups[group_name] - def get_actor_group(self, group_name: str) -> Container: + def get_actor_group(self, group_name: ActorGroupName) -> Container: group = next((sub_area for sub_area in self.actor_groups if sub_area == group_name), None) if group is None: raise KeyError(f"No group found with name for {group_name}") return group - def all_actor_group_names_for_actor(self, actor_name: str) -> list[str]: + def all_actor_group_names_for_actor(self, actor_name: ActorName) -> list[str]: return [group_name for group_name, group in self.actor_groups.items() if actor_name in group] - def remove_actor_from_group(self, group_name: str, actor_name: str): + def remove_actor_from_group(self, group_name: ActorGroupName, actor_name: ActorName) -> None: logger.debug("Remove actor %s from group %s", actor_name, group_name) self.actor_groups[group_name].remove(actor_name) - def remove_actor_from_all_groups(self, actor_name: str): + def remove_actor_from_all_groups(self, actor_name: ActorName) -> None: group_names = self.all_actor_group_names_for_actor(actor_name) for group_name in group_names: self.remove_actor_from_group(group_name, actor_name) - def add_actor_to_entity_groups(self, collision_camera_name: str, actor_name: str, all_groups: bool = False): + def add_actor_to_entity_groups( + self, collision_camera_name: ActorGroupName, actor_name: ActorName, all_groups: bool = False + ) -> None: """ Adds an actor to either all entity groups or one entity group, which follow the name pattern of eg_SubArea_NAME. In case an actor needs to be added to an entity group not following this name pattern @@ -248,7 +255,7 @@ def add_actor_to_entity_groups(self, collision_camera_name: str, actor_name: str pattern or just to one entity group matching the name pattern exactly """ - def compare_func(first: str, second: str) -> bool: + def compare_func(first: ActorGroupName, second: ActorGroupName) -> bool: if all_groups: return first.startswith(f"eg_SubArea_{second}") else: @@ -261,26 +268,26 @@ def compare_func(first: str, second: str) -> bool: logger.debug("Add actor %s to group %s", actor_name, group) self.insert_into_entity_group(group, actor_name) - def insert_into_entity_group(self, sub_area: Container, name_to_add: str) -> None: + def insert_into_entity_group(self, sub_area: Container, actor_name: ActorName) -> None: # MSR requires to have the names in the sub area list sorted by their crc32 value entity_group = self.actor_groups[sub_area] - entity_group.append(name_to_add) + entity_group.append(actor_name) entity_group.sort(key=crc32) - def _get_layer(self, layer: ActorLayer) -> ListContainer: + def _get_layer(self, layer: ActorLayer) -> list[dict]: """Returns a layer of actors""" return self.raw.actor_layers[layer] - def _check_if_actor_exists(self, layer: ActorLayer, actor_name: str) -> None: + def _check_if_actor_exists(self, layer: ActorLayer, actor_name: ActorName) -> None: if actor_name not in self._get_layer(layer): raise KeyError(f"No actor named '{actor_name}' found in '{layer}!'") - def get_actor(self, layer: ActorLayer, actor_name: str) -> BmsldActor: + def get_actor(self, layer: ActorLayer, actor_name: ActorName) -> BmsldActor: """Returns an actor given a layer and an actor name""" self._check_if_actor_exists(layer, actor_name) return BmsldActor(self.raw.actor_layers[layer][actor_name]) - def remove_actor(self, layer: ActorLayer, actor_name: str) -> None: + def remove_actor(self, layer: ActorLayer, actor_name: ActorName) -> None: """Deletes an actor given a layer and an actor name""" self._check_if_actor_exists(layer, actor_name) self._get_layer(layer).pop(actor_name) @@ -290,7 +297,7 @@ def copy_actor( self, position: list[float], template_actor: BmsldActor, - new_name: str, + new_name: ActorName, layer: ActorLayer, offset: tuple = (0, 0, 0), ) -> BmsldActor: From 2a2cac9daa21a4f978751318719502cea5f422d3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 02:45:56 +0000 Subject: [PATCH 38/38] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/mercury_engine_data_structures/formats/bmsld.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mercury_engine_data_structures/formats/bmsld.py b/src/mercury_engine_data_structures/formats/bmsld.py index e2374cc1..9a032992 100644 --- a/src/mercury_engine_data_structures/formats/bmsld.py +++ b/src/mercury_engine_data_structures/formats/bmsld.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, TypeAlias import construct -from construct import Const, Construct, Container, Flag, Hex, Int32ul, ListContainer, Struct, Switch +from construct import Const, Construct, Container, Flag, Hex, Int32ul, Struct, Switch from mercury_engine_data_structures.base_resource import BaseResource from mercury_engine_data_structures.common_types import (