From 76494b56691be6fb006f3c0ede09fda22aebd3ec Mon Sep 17 00:00:00 2001 From: duncathan Date: Thu, 22 Jun 2023 19:17:36 -0600 Subject: [PATCH 1/7] rename script API classes AreaWrapper -> Area, ScriptLayerHelper -> ScriptLayer, ScriptInstanceHelper -> ScriptInstance --- retro_data_structures/formats/mlvl.py | 28 ++++++------- retro_data_structures/formats/mrea.py | 28 ++++++------- retro_data_structures/formats/scan.py | 8 ++-- retro_data_structures/formats/script_layer.py | 40 +++++++++---------- .../formats/script_object.py | 16 ++++---- test/formats/test_mrea.py | 6 +-- 6 files changed, 63 insertions(+), 63 deletions(-) diff --git a/retro_data_structures/formats/mlvl.py b/retro_data_structures/formats/mlvl.py index 4600251..4fe71f8 100644 --- a/retro_data_structures/formats/mlvl.py +++ b/retro_data_structures/formats/mlvl.py @@ -42,8 +42,8 @@ from retro_data_structures.formats.guid import GUID from retro_data_structures.formats.mrea import Mrea from retro_data_structures.formats.savw import Savw -from retro_data_structures.formats.script_layer import ScriptLayerHelper, new_layer -from retro_data_structures.formats.script_object import InstanceId, ScriptInstanceHelper +from retro_data_structures.formats.script_layer import ScriptLayer, new_layer +from retro_data_structures.formats.script_object import InstanceId, ScriptInstance from retro_data_structures.formats.strg import Strg from retro_data_structures.game_check import Game @@ -352,7 +352,7 @@ def create(version: int, asset_id): } -class AreaWrapper: +class Area: _flags: Container _layer_names: ListContainer _index: int @@ -410,18 +410,18 @@ def mrea_asset_id(self) -> int: return self._raw.area_mrea_id @property - def layers(self) -> Iterator[ScriptLayerHelper]: + def layers(self) -> Iterator[ScriptLayer]: for layer in self.mrea.script_layers: yield layer.with_parent(self) @property - def generated_objects_layer(self) -> ScriptLayerHelper: + def generated_objects_layer(self) -> ScriptLayer: return self.mrea.generated_objects_layer - def get_layer(self, name: str) -> ScriptLayerHelper: + def get_layer(self, name: str) -> ScriptLayer: return next(layer for layer in self.layers if layer.name == name) - def add_layer(self, name: str, active: bool = True) -> ScriptLayerHelper: + def add_layer(self, name: str, active: bool = True) -> ScriptLayer: index = len(self._layer_names) self._layer_names.append(name) self._flags.append(active) @@ -434,7 +434,7 @@ def next_instance_id(self) -> int: ids = [instance.id.instance for layer in self.layers for instance in layer.instances] return next(i for i in count() if i not in ids) - def get_instance(self, instance_id: int | InstanceId) -> ScriptInstanceHelper | None: + def get_instance(self, instance_id: typing.Union[int, InstanceId]) -> typing.Optional[ScriptInstance]: if not isinstance(instance_id, InstanceId): instance_id = InstanceId(instance_id) @@ -444,10 +444,10 @@ def get_instance(self, instance_id: int | InstanceId) -> ScriptInstanceHelper | return None - def get_instance_by_name(self, name: str) -> ScriptInstanceHelper: + def get_instance_by_name(self, name: str) -> ScriptInstance: return self.mrea.get_instance_by_name(name) - def _raw_connect_to(self, source_dock_number: int, target_area: AreaWrapper, target_dock_number: int): + def _raw_connect_to(self, source_dock_number: int, target_area: Area, target_dock_number: int): source_dock = self._raw.docks[source_dock_number] assert len(source_dock.connecting_dock) == 1, "Only docks with one connection supported" source_dock.connecting_dock[0].area_index = target_area._index @@ -460,7 +460,7 @@ def _raw_connect_to(self, source_dock_number: int, target_area: AreaWrapper, tar attached_area_index.append(c.area_index) self._raw.attached_area_index = construct.ListContainer(attached_area_index) - def connect_dock_to(self, source_dock_number: int, target_area: AreaWrapper, target_dock_number: int): + def connect_dock_to(self, source_dock_number: int, target_area: Area, target_dock_number: int): self._raw_connect_to(source_dock_number, target_area, target_dock_number) target_area._raw_connect_to(target_dock_number, self, source_dock_number) @@ -586,14 +586,14 @@ def __repr__(self) -> str: return super().__repr__() @property - def areas(self) -> Iterator[AreaWrapper]: + def areas(self) -> Iterator[Area]: offsets = self._raw.area_layer_name_offset names = self._raw.layer_names for i, area in enumerate(self._raw.areas): area_layer_names = names[offsets[i]:] if i == len(self._raw.areas) - 1 else names[offsets[i]:offsets[i+1]] - yield AreaWrapper(area, self.asset_manager, self._raw.area_layer_flags[i], area_layer_names, i, self) + yield Area(area, self.asset_manager, self._raw.area_layer_flags[i], area_layer_names, i, self) - def get_area(self, asset_id: NameOrAssetId) -> AreaWrapper: + def get_area(self, asset_id: NameOrAssetId) -> Area: return next(area for area in self.areas if area.mrea_asset_id == self.asset_manager._resolve_asset_id(asset_id)) _name_strg_cached: Strg = None diff --git a/retro_data_structures/formats/mrea.py b/retro_data_structures/formats/mrea.py index 787541c..c8c90f8 100644 --- a/retro_data_structures/formats/mrea.py +++ b/retro_data_structures/formats/mrea.py @@ -30,8 +30,8 @@ from retro_data_structures.formats.area_collision import AreaCollision from retro_data_structures.formats.arot import AROT from retro_data_structures.formats.lights import Lights -from retro_data_structures.formats.script_layer import SCGN, SCLY, ScriptLayerHelper -from retro_data_structures.formats.script_object import InstanceId, ScriptInstanceHelper +from retro_data_structures.formats.script_layer import SCGN, SCLY, ScriptLayer +from retro_data_structures.formats.script_object import InstanceId, ScriptInstance from retro_data_structures.formats.visi import VISI from retro_data_structures.formats.world_geometry import lazy_world_geometry from retro_data_structures.game_check import AssetIdCorrect, Game @@ -423,7 +423,7 @@ def _build(self, obj: Container, stream, context, path): class Mrea(BaseResource): - _script_layer_helpers: dict[int, ScriptLayerHelper] | None = None + _script_layer_helpers: dict[int, ScriptLayer] | None = None @classmethod def resource_type(cls) -> AssetType: @@ -476,20 +476,20 @@ def build(self) -> bytes: return super().build() @property - def script_layers(self) -> Iterator[ScriptLayerHelper]: + def script_layers(self) -> Iterator[ScriptLayer]: self._ensure_decoded_section("script_layers_section", lazy_load=self.target_game != Game.PRIME) if self.target_game == Game.PRIME: section = self._raw.sections.script_layers_section[0] for i, layer in enumerate(section.layers): - yield ScriptLayerHelper(layer, i, self.target_game) + yield ScriptLayer(layer, i, self.target_game) else: if self._script_layer_helpers is None: self._script_layer_helpers = {} for i, section in enumerate(self._raw.sections.script_layers_section): if i not in self._script_layer_helpers: - self._script_layer_helpers[i] = ScriptLayerHelper( + self._script_layer_helpers[i] = ScriptLayer( _CATEGORY_ENCODINGS["script_layers_section"].parse( section, target_game=self.target_game ), @@ -499,12 +499,12 @@ def script_layers(self) -> Iterator[ScriptLayerHelper]: yield from self._script_layer_helpers.values() - _generated_objects_layer: ScriptLayerHelper | None = None + _generated_objects_layer: ScriptLayer | None = None @property - def generated_objects_layer(self) -> ScriptLayerHelper: + def generated_objects_layer(self) -> ScriptLayer: assert self.target_game >= Game.ECHOES if self._generated_objects_layer is None: - self._generated_objects_layer = ScriptLayerHelper( + self._generated_objects_layer = ScriptLayer( self.get_section("generated_script_objects_section")[0], None, self.target_game @@ -512,18 +512,18 @@ def generated_objects_layer(self) -> ScriptLayerHelper: return self._generated_objects_layer @property - def all_instances(self) -> Iterator[ScriptInstanceHelper]: + def all_instances(self) -> Iterator[ScriptInstance]: for layer in self.script_layers: yield from layer.instances - def get_instance(self, instance_id: int) -> ScriptInstanceHelper | None: + def get_instance(self, instance_id: int) -> ScriptInstance | None: for layer in self.script_layers: if (instance := layer.get_instance(instance_id)) is not None: return instance if (instance := self.generated_objects_layer.get_instance(instance_id)) is not None: return instance - def get_instance_by_name(self, name: str) -> ScriptInstanceHelper: + def get_instance_by_name(self, name: str) -> ScriptInstance: for layer in self.script_layers: if (instance := layer.get_instance_by_name(name, raise_if_missing=False)) is not None: return instance @@ -531,12 +531,12 @@ def get_instance_by_name(self, name: str) -> ScriptInstanceHelper: return instance raise KeyError(name) - def remove_instance(self, instance: int | InstanceId | str | ScriptInstanceHelper): + def remove_instance(self, instance: int | InstanceId | str | ScriptInstance): if isinstance(instance, str): instance = self.get_instance(instance) elif isinstance(instance, int): instance = InstanceId(instance) - if isinstance(instance, ScriptInstanceHelper): + if isinstance(instance, ScriptInstance): instance = instance.id layers = list(self.script_layers) diff --git a/retro_data_structures/formats/scan.py b/retro_data_structures/formats/scan.py index 665e49f..f79508c 100644 --- a/retro_data_structures/formats/scan.py +++ b/retro_data_structures/formats/scan.py @@ -14,7 +14,7 @@ from retro_data_structures.common_types import AssetId32, FourCC from retro_data_structures.formats import dgrp from retro_data_structures.formats.dgrp import DGRP -from retro_data_structures.formats.script_object import ConstructScriptInstance, ScriptInstanceHelper +from retro_data_structures.formats.script_object import ConstructScriptInstance, ScriptInstance from retro_data_structures.game_check import Game ScanImage = Struct( @@ -85,12 +85,12 @@ def dependencies_for(self) -> typing.Iterator[Dependency]: for it in self._internal_dependencies_for(): yield Dependency(it.type, it.id, True) - _scannable_object_info: ScriptInstanceHelper | None = None + _scannable_object_info: ScriptInstance | None = None @property - def scannable_object_info(self) -> ScriptInstanceHelper: + def scannable_object_info(self) -> ScriptInstance: assert self.target_game != Game.PRIME if self._scannable_object_info is None: - self._scannable_object_info = ScriptInstanceHelper(self._raw.scannable_object_info, self.target_game, + self._scannable_object_info = ScriptInstance(self._raw.scannable_object_info, self.target_game, on_modify=self.rebuild_dependencies) return self._scannable_object_info diff --git a/retro_data_structures/formats/script_layer.py b/retro_data_structures/formats/script_layer.py index 5b36db4..68249ec 100644 --- a/retro_data_structures/formats/script_layer.py +++ b/retro_data_structures/formats/script_layer.py @@ -25,13 +25,13 @@ from retro_data_structures.base_resource import Dependency from retro_data_structures.common_types import FourCC from retro_data_structures.construct_extensions.misc import Skip -from retro_data_structures.formats.script_object import ConstructScriptInstance, InstanceId, ScriptInstanceHelper +from retro_data_structures.formats.script_object import ConstructScriptInstance, InstanceId, ScriptInstance from retro_data_structures.game_check import Game from retro_data_structures.properties import BaseObjectType if typing.TYPE_CHECKING: from retro_data_structures.asset_manager import AssetManager - from retro_data_structures.formats.mlvl import AreaWrapper + from retro_data_structures.formats.mlvl import Area ScriptLayerPrime = Struct( "magic" / Const("SCLY", FourCC), @@ -54,7 +54,7 @@ ) -def ScriptLayer(identifier): +def ConstructScriptLayer(identifier): return Struct( "magic" / Const(identifier, FourCC), "unknown" / Int8ub, @@ -76,12 +76,12 @@ def new_layer(index: int | None, target_game: Game) -> Container: }) -SCLY = IfThenElse(game_check.current_game_at_least(game_check.Game.ECHOES), ScriptLayer("SCLY"), ScriptLayerPrime) -SCGN = ScriptLayer("SCGN") +SCLY = IfThenElse(game_check.current_game_at_least(game_check.Game.ECHOES), ConstructScriptLayer("SCLY"), ScriptLayerPrime) +SCGN = ConstructScriptLayer("SCGN") -class ScriptLayerHelper: - _parent_area: AreaWrapper | None = None +class ScriptLayer: + _parent_area: Area | None = None _index: int _modified: bool = False @@ -95,7 +95,7 @@ def __repr__(self) -> str: return f"{self.name} ({'Active' if self.active else 'Inactive'})" return super().__repr__() - def with_parent(self, parent: AreaWrapper) -> ScriptLayerHelper: + def with_parent(self, parent: Area) -> ScriptLayer: self._parent_area = parent return self @@ -106,22 +106,22 @@ def index(self): @property def instances(self): for instance in self._raw.script_instances: - yield ScriptInstanceHelper(instance, self.target_game, on_modify=self.mark_modified) + yield ScriptInstance(instance, self.target_game, on_modify=self.mark_modified) - def get_instance(self, instance_id: int) -> ScriptInstanceHelper: + def get_instance(self, instance_id: int) -> ScriptInstance | None: for instance in self.instances: if instance.id_matches(instance_id): return instance return None - def get_instance_by_name(self, name: str, *, raise_if_missing: bool = True) -> ScriptInstanceHelper: + def get_instance_by_name(self, name: str, *, raise_if_missing: bool = True) -> ScriptInstance: for instance in self.instances: if instance.name == name: return instance if raise_if_missing: raise KeyError(name) - def _internal_add_instance(self, instance: ScriptInstanceHelper): + def _internal_add_instance(self, instance: ScriptInstance): if self.get_instance(instance.id) is not None: raise RuntimeError(f"Instance with id {instance.id} already exists.") @@ -129,23 +129,23 @@ def _internal_add_instance(self, instance: ScriptInstanceHelper): self._raw.script_instances.append(instance._raw) return self.get_instance(instance.id) - def add_instance(self, instance_type: str, name: str | None = None) -> ScriptInstanceHelper: - instance = ScriptInstanceHelper.new_instance(self.target_game, instance_type, self) + def add_instance(self, instance_type: str, name: str | None = None) -> ScriptInstance: + instance = ScriptInstance.new_instance(self.target_game, instance_type, self) if name is not None: instance.name = name return self._internal_add_instance(instance) - def add_instance_with(self, object_properties: BaseObjectType) -> ScriptInstanceHelper: - instance = ScriptInstanceHelper.new_from_properties(object_properties, self) + def add_instance_with(self, object_properties: BaseObjectType) -> ScriptInstance: + instance = ScriptInstance.new_from_properties(object_properties, self) return self._internal_add_instance(instance) - def add_memory_relay(self, name: str | None = None) -> ScriptInstanceHelper: + def add_memory_relay(self, name: str | None = None) -> ScriptInstance: relay = self.add_instance("MRLY", name) savw = self._parent_area._parent_mlvl.savw savw.raw.memory_relays.append({"instance_id": relay.id}) return relay - def add_existing_instance(self, instance: ScriptInstanceHelper) -> ScriptInstanceHelper: + def add_existing_instance(self, instance: ScriptInstance) -> ScriptInstance: if instance.id.area != self._parent_area.id: new_id = InstanceId.new(self._index, self._parent_area.id, self._parent_area.next_instance_id) else: @@ -154,10 +154,10 @@ def add_existing_instance(self, instance: ScriptInstanceHelper) -> ScriptInstanc instance.id = new_id return self._internal_add_instance(instance) - def remove_instance(self, instance: int | str | ScriptInstanceHelper): + def remove_instance(self, instance: int | str | ScriptInstance): if isinstance(instance, str): instance = self.get_instance_by_name(instance) - if isinstance(instance, ScriptInstanceHelper): + if isinstance(instance, ScriptInstance): instance = instance.id matching_instances = [ diff --git a/retro_data_structures/formats/script_object.py b/retro_data_structures/formats/script_object.py index 863346f..b118e20 100644 --- a/retro_data_structures/formats/script_object.py +++ b/retro_data_structures/formats/script_object.py @@ -34,7 +34,7 @@ if TYPE_CHECKING: from retro_data_structures.asset_manager import AssetManager - from retro_data_structures.formats.script_layer import ScriptLayerHelper + from retro_data_structures.formats.script_layer import ScriptLayer from retro_data_structures.properties.base_property import BaseObjectType PropertyType = typing.TypeVar("PropertyType", bound=BaseObjectType) @@ -214,7 +214,7 @@ def _try_quick_get_name(data: bytes) -> str | None: return None -class ScriptInstanceHelper: +class ScriptInstance: _raw: ScriptInstanceRaw target_game: Game @@ -227,10 +227,10 @@ def __repr__(self): return f"" def __eq__(self, other): - return isinstance(other, ScriptInstanceHelper) and self._raw == other._raw + return isinstance(other, ScriptInstance) and self._raw == other._raw @classmethod - def new_instance(cls, target_game: Game, instance_type: str, layer: ScriptLayerHelper) -> ScriptInstanceHelper: + def new_instance(cls, target_game: Game, instance_type: str, layer: ScriptLayer) -> ScriptInstance: property_type = properties.get_game_object(target_game, instance_type) raw = ScriptInstanceRaw( @@ -242,7 +242,7 @@ def new_instance(cls, target_game: Game, instance_type: str, layer: ScriptLayerH return cls(raw, target_game, on_modify=layer.mark_modified) @classmethod - def new_from_properties(cls, object_properties: BaseObjectType, layer: ScriptLayerHelper) -> ScriptInstanceHelper: + def new_from_properties(cls, object_properties: BaseObjectType, layer: ScriptLayer) -> ScriptInstance: raw = ScriptInstanceRaw( type=object_properties.object_type(), id=layer.new_instance_id(), @@ -332,7 +332,7 @@ def connections(self, value: typing.Iterable[Connection]): self._raw.connections = tuple(value) self.on_modify() - def add_connection(self, state: str | State, message: str | Message, target: ScriptInstanceHelper): + def add_connection(self, state: str | State, message: str | Message, target: ScriptInstance): correct_state = enum_helper.STATE_PER_GAME[self.target_game] correct_message = enum_helper.MESSAGE_PER_GAME[self.target_game] @@ -345,8 +345,8 @@ def add_connection(self, state: str | State, message: str | Message, target: Scr def remove_connection(self, connection: Connection): self.connections = [c for c in self.connections if c is not connection] - def remove_connections(self, target: Union[int, ScriptInstanceHelper]): - if isinstance(target, ScriptInstanceHelper): + def remove_connections(self, target: Union[int, ScriptInstance]): + if isinstance(target, ScriptInstance): target = target.id self.connections = [c for c in self.connections if c.target != target] diff --git a/test/formats/test_mrea.py b/test/formats/test_mrea.py index a4d14f2..f6d3fb5 100644 --- a/test/formats/test_mrea.py +++ b/test/formats/test_mrea.py @@ -5,7 +5,7 @@ from retro_data_structures.base_resource import AssetId from retro_data_structures.formats import Mlvl from retro_data_structures.formats.mrea import Mrea -from retro_data_structures.formats.script_object import ScriptInstanceHelper +from retro_data_structures.formats.script_object import ScriptInstance @pytest.mark.xfail @@ -26,13 +26,13 @@ def test_compare_p2(prime2_asset_manager, mrea_asset_id: AssetId): decoded = Mrea.parse(resource.data, target_game=prime2_asset_manager.target_game) for instance in decoded.all_instances: - assert isinstance(instance, ScriptInstanceHelper) + assert isinstance(instance, ScriptInstance) encoded = decoded.build() decoded2 = Mrea.parse(encoded, target_game=prime2_asset_manager.target_game) for instance in decoded2.all_instances: - assert isinstance(instance, ScriptInstanceHelper) + assert isinstance(instance, ScriptInstance) assert test_lib.purge_hidden(decoded2.raw) == test_lib.purge_hidden(decoded.raw) From b515e6347c4f90d3ca2f2627421910632bf3eedb Mon Sep 17 00:00:00 2001 From: duncathan Date: Thu, 22 Jun 2023 19:18:04 -0600 Subject: [PATCH 2/7] add tests for desired script API --- test/formats/test_mrea.py | 12 --- test/formats/test_script_api.py | 119 +++++++++++++++++++++++++++++ test/formats/test_script_object.py | 28 ------- 3 files changed, 119 insertions(+), 40 deletions(-) create mode 100644 test/formats/test_script_api.py delete mode 100644 test/formats/test_script_object.py diff --git a/test/formats/test_mrea.py b/test/formats/test_mrea.py index f6d3fb5..1362b2f 100644 --- a/test/formats/test_mrea.py +++ b/test/formats/test_mrea.py @@ -35,15 +35,3 @@ def test_compare_p2(prime2_asset_manager, mrea_asset_id: AssetId): assert isinstance(instance, ScriptInstance) assert test_lib.purge_hidden(decoded2.raw) == test_lib.purge_hidden(decoded.raw) - - -def test_add_instance(prime2_asset_manager): - from retro_data_structures.enums import echoes - from retro_data_structures.properties.echoes.objects.SpecialFunction import SpecialFunction - - mlvl = prime2_asset_manager.get_parsed_asset(0x42b935e4, type_hint=Mlvl) - area = mlvl.get_area(0x5DFA984F) - area.get_layer("Default").add_instance_with(SpecialFunction( - function=echoes.Function.Darkworld, - )) - assert area.mrea.build() is not None diff --git a/test/formats/test_script_api.py b/test/formats/test_script_api.py new file mode 100644 index 0000000..122d5dc --- /dev/null +++ b/test/formats/test_script_api.py @@ -0,0 +1,119 @@ +import pytest + +import retro_data_structures.enums.echoes as _echoes_enums +import retro_data_structures.enums.prime as _prime_enums +from retro_data_structures.formats import script_object +from retro_data_structures.formats.mlvl import Mlvl, Area +from retro_data_structures.formats.script_object import InstanceId + + +@pytest.mark.parametrize(["layer", "area", "instance", "expected"], [ + (0, 0, 0, 0x00000000), + (1, 0, 0, 0x04000000), + (0, 1, 0, 0x00010000), + (0, 0, 1, 0x00000001), + (5, 2, 1, 0x14020001), +]) +def test_instance_id_new(layer, area, instance, expected): + assert InstanceId.new(layer, area, instance) == expected + + +@pytest.mark.parametrize(["correct_type", "value", "expected"], [ + (_prime_enums.State, _prime_enums.State.Exited, _prime_enums.State.Exited), + (_prime_enums.State, _echoes_enums.State.Exited, _prime_enums.State.Exited), + (_echoes_enums.State, _prime_enums.State.Exited, _echoes_enums.State.Exited), + (_echoes_enums.State, 'EXIT', _echoes_enums.State.Exited), + (_echoes_enums.State, _echoes_enums.State.Exited, _echoes_enums.State.Exited), +]) +def test_resolve_to_enum(correct_type, value, expected): + assert script_object._resolve_to_enum(correct_type, value) == expected + + +@pytest.fixture +def prime2_mlvl(prime2_asset_manager) -> Mlvl: + # Agon Wastes + return prime2_asset_manager.get_parsed_asset(0x42b935e4) + +@pytest.fixture +def prime2_area(prime2_mlvl: Mlvl) -> Area: + # Storage C + return prime2_mlvl.get_area(0x5DFA984F) + + +# Area +@pytest.mark.xfail(reason="This feature had never been tested and does not work") +@pytest.mark.parametrize("active", (False, True)) +def test_add_layer(prime2_area: Area, active: bool): + layer = prime2_area.add_layer("Test", active) + assert layer.active == active + assert layer.index == 2 + +def test_get_instance(prime2_area: Area): + idx, name = 0x0045006B, "Pickup Object" + inst = prime2_area.get_instance(idx) + assert inst.name == name + + inst = prime2_area.get_instance(name) + assert inst.id == idx + +def test_remove_instance(prime2_area: Area): + old_len = len(list(prime2_area.all_instances)) + prime2_area.remove_instance("Pickup Object") + assert len(list(prime2_area.all_instances)) == old_len - 1 + + +# Script Layer +def test_add_instance(prime2_area: Area): + from retro_data_structures.enums import echoes + from retro_data_structures.properties.echoes.objects.SpecialFunction import SpecialFunction + + inst = prime2_area.get_layer("Default").add_instance_with(SpecialFunction( + function=echoes.Function.Darkworld, + )) + assert inst.type == SpecialFunction + assert prime2_area.mrea.build() is not None + +def test_add_memory_relay(prime2_area: Area): + relay = prime2_area.get_layer("Default").add_memory_relay("Test") + save = prime2_area._parent_mlvl.savw + + assert any(state["instance_id"] == relay.id for state in save.raw.memory_relays) + +@pytest.mark.parametrize("name", ("Test1", "Test2")) +@pytest.mark.parametrize("active", (False, True)) +def test_edit_layer(prime2_area: Area, name: str, active: bool): + default = prime2_area.get_layer("Default") + + default.name = name + default.active = active + + assert default.name == name + assert default.active == active + + +# Script Object +def test_edit_properties(prime2_area: Area): + from retro_data_structures.properties.echoes.objects.Pickup import Pickup + + inst = prime2_area.get_instance("Pickup Object") + + inst.name = "Test" + assert inst.name == "Test" + + with inst.edit_properties(Pickup) as pickup: + pickup.amount = 2 + + +def test_edit_connections(prime2_area: Area): + from retro_data_structures.enums.echoes import Message, State + + pickup = prime2_area.get_instance("Pickup Object") + relay = prime2_area.get_instance("Post Pickup") + + original_connections = pickup.connections + + pickup.remove_connections(relay) + assert len(pickup.connections) == len(original_connections) - 1 + + pickup.add_connection(State.Arrived, Message.SetToZero, relay) + assert set(pickup.connections) == set(original_connections) diff --git a/test/formats/test_script_object.py b/test/formats/test_script_object.py deleted file mode 100644 index 9ba24d5..0000000 --- a/test/formats/test_script_object.py +++ /dev/null @@ -1,28 +0,0 @@ -import pytest - -import retro_data_structures.enums.echoes as _echoes_enums -import retro_data_structures.enums.prime as _prime_enums -from retro_data_structures.formats import script_object -from retro_data_structures.formats.script_object import InstanceId - - -@pytest.mark.parametrize(["layer", "area", "instance", "expected"], [ - (0, 0, 0, 0x00000000), - (1, 0, 0, 0x04000000), - (0, 1, 0, 0x00010000), - (0, 0, 1, 0x00000001), - (5, 2, 1, 0x14020001), -]) -def test_instance_id_new(layer, area, instance, expected): - assert InstanceId.new(layer, area, instance) == expected - - -@pytest.mark.parametrize(["correct_type", "value", "expected"], [ - (_prime_enums.State, _prime_enums.State.Exited, _prime_enums.State.Exited), - (_prime_enums.State, _echoes_enums.State.Exited, _prime_enums.State.Exited), - (_echoes_enums.State, _prime_enums.State.Exited, _echoes_enums.State.Exited), - (_echoes_enums.State, 'EXIT', _echoes_enums.State.Exited), - (_echoes_enums.State, _echoes_enums.State.Exited, _echoes_enums.State.Exited), -]) -def test_resolve_to_enum(correct_type, value, expected): - assert script_object._resolve_to_enum(correct_type, value) == expected From e3e62291108a9ceb6fdbdc2408d0f98998abeea8 Mon Sep 17 00:00:00 2001 From: duncathan Date: Thu, 22 Jun 2023 22:35:21 -0600 Subject: [PATCH 3/7] move to instanceref system --- retro_data_structures/formats/mlvl.py | 4 +- retro_data_structures/formats/mrea.py | 45 +++++++++---------- retro_data_structures/formats/script_layer.py | 44 +++++++++--------- .../formats/script_object.py | 37 +++++++++------ test/formats/test_script_api.py | 18 ++++---- 5 files changed, 79 insertions(+), 69 deletions(-) diff --git a/retro_data_structures/formats/mlvl.py b/retro_data_structures/formats/mlvl.py index 4fe71f8..1eb9753 100644 --- a/retro_data_structures/formats/mlvl.py +++ b/retro_data_structures/formats/mlvl.py @@ -440,12 +440,12 @@ def get_instance(self, instance_id: typing.Union[int, InstanceId]) -> typing.Opt for layer in self.layers: if instance_id.layer == layer.index: - return layer.get_instance(instance_id) + return layer._get_instance_by_id(instance_id) return None def get_instance_by_name(self, name: str) -> ScriptInstance: - return self.mrea.get_instance_by_name(name) + return self.mrea._get_instance_by_name(name) def _raw_connect_to(self, source_dock_number: int, target_area: Area, target_dock_number: int): source_dock = self._raw.docks[source_dock_number] diff --git a/retro_data_structures/formats/mrea.py b/retro_data_structures/formats/mrea.py index c8c90f8..6d74819 100644 --- a/retro_data_structures/formats/mrea.py +++ b/retro_data_structures/formats/mrea.py @@ -31,7 +31,7 @@ from retro_data_structures.formats.arot import AROT from retro_data_structures.formats.lights import Lights from retro_data_structures.formats.script_layer import SCGN, SCLY, ScriptLayer -from retro_data_structures.formats.script_object import InstanceId, ScriptInstance +from retro_data_structures.formats.script_object import InstanceId, InstanceIdRef, InstanceRef, ScriptInstance, resolve_instance_id_ref from retro_data_structures.formats.visi import VISI from retro_data_structures.formats.world_geometry import lazy_world_geometry from retro_data_structures.game_check import AssetIdCorrect, Game @@ -511,33 +511,30 @@ def generated_objects_layer(self) -> ScriptLayer: ) return self._generated_objects_layer + @property + def all_layers(self) -> Iterator[ScriptLayer]: + yield from self.script_layers + yield self.generated_objects_layer + @property def all_instances(self) -> Iterator[ScriptInstance]: for layer in self.script_layers: yield from layer.instances - def get_instance(self, instance_id: int) -> ScriptInstance | None: - for layer in self.script_layers: - if (instance := layer.get_instance(instance_id)) is not None: - return instance - if (instance := self.generated_objects_layer.get_instance(instance_id)) is not None: - return instance - - def get_instance_by_name(self, name: str) -> ScriptInstance: - for layer in self.script_layers: - if (instance := layer.get_instance_by_name(name, raise_if_missing=False)) is not None: - return instance - if (instance := self.generated_objects_layer.get_instance_by_name(name, raise_if_missing=False)) is not None: - return instance - raise KeyError(name) + def get_instance(self, ref: InstanceRef) -> ScriptInstance: + for layer in self.all_layers: + try: + return layer.get_instance(ref) + except KeyError: + pass + raise KeyError(ref) def remove_instance(self, instance: int | InstanceId | str | ScriptInstance): - if isinstance(instance, str): - instance = self.get_instance(instance) - elif isinstance(instance, int): - instance = InstanceId(instance) - if isinstance(instance, ScriptInstance): - instance = instance.id - - layers = list(self.script_layers) - layers[instance.layer].remove_instance(instance) + for layer in self.all_layers: + try: + layer.remove_instance(instance) + except KeyError: + pass + else: + return + raise KeyError(instance) diff --git a/retro_data_structures/formats/script_layer.py b/retro_data_structures/formats/script_layer.py index 68249ec..6f6030a 100644 --- a/retro_data_structures/formats/script_layer.py +++ b/retro_data_structures/formats/script_layer.py @@ -25,7 +25,7 @@ from retro_data_structures.base_resource import Dependency from retro_data_structures.common_types import FourCC from retro_data_structures.construct_extensions.misc import Skip -from retro_data_structures.formats.script_object import ConstructScriptInstance, InstanceId, ScriptInstance +from retro_data_structures.formats.script_object import ConstructScriptInstance, InstanceId, InstanceIdRef, InstanceRef, ScriptInstance, resolve_instance_id_ref from retro_data_structures.game_check import Game from retro_data_structures.properties import BaseObjectType @@ -108,26 +108,38 @@ def instances(self): for instance in self._raw.script_instances: yield ScriptInstance(instance, self.target_game, on_modify=self.mark_modified) - def get_instance(self, instance_id: int) -> ScriptInstance | None: + def has_instance(self, ref: InstanceRef) -> bool: + try: + self.get_instance(ref) + except KeyError: + return False + return True + + def get_instance(self, ref: InstanceRef) -> ScriptInstance: + if isinstance(ref, str): + return self._get_instance_by_name(ref) + return self._get_instance_by_id(ref) + + def _get_instance_by_id(self, instance_id: InstanceIdRef) -> ScriptInstance: + instance_id = resolve_instance_id_ref(instance_id) for instance in self.instances: if instance.id_matches(instance_id): return instance - return None + raise KeyError(instance_id) - def get_instance_by_name(self, name: str, *, raise_if_missing: bool = True) -> ScriptInstance: + def _get_instance_by_name(self, name: str) -> ScriptInstance: for instance in self.instances: if instance.name == name: return instance - if raise_if_missing: - raise KeyError(name) + raise KeyError(name) def _internal_add_instance(self, instance: ScriptInstance): - if self.get_instance(instance.id) is not None: + if self.has_instance(instance): raise RuntimeError(f"Instance with id {instance.id} already exists.") self._modified = True self._raw.script_instances.append(instance._raw) - return self.get_instance(instance.id) + return self._get_instance_by_id(instance.id) def add_instance(self, instance_type: str, name: str | None = None) -> ScriptInstance: instance = ScriptInstance.new_instance(self.target_game, instance_type, self) @@ -145,20 +157,10 @@ def add_memory_relay(self, name: str | None = None) -> ScriptInstance: savw.raw.memory_relays.append({"instance_id": relay.id}) return relay - def add_existing_instance(self, instance: ScriptInstance) -> ScriptInstance: - if instance.id.area != self._parent_area.id: - new_id = InstanceId.new(self._index, self._parent_area.id, self._parent_area.next_instance_id) - else: - new_id = InstanceId.new(self._index, instance.id.area, instance.id.instance) - - instance.id = new_id - return self._internal_add_instance(instance) - - def remove_instance(self, instance: int | str | ScriptInstance): + def remove_instance(self, instance: InstanceRef): if isinstance(instance, str): - instance = self.get_instance_by_name(instance) - if isinstance(instance, ScriptInstance): - instance = instance.id + instance = self._get_instance_by_name(instance) + instance = resolve_instance_id_ref(instance) matching_instances = [ i for i in self._raw.script_instances diff --git a/retro_data_structures/formats/script_object.py b/retro_data_structures/formats/script_object.py index b118e20..c96dc88 100644 --- a/retro_data_structures/formats/script_object.py +++ b/retro_data_structures/formats/script_object.py @@ -77,7 +77,7 @@ def instance(self) -> int: return self & 0xffff -@dataclasses.dataclass() +@dataclasses.dataclass(frozen=True) class Connection: state: State message: Message @@ -268,11 +268,9 @@ def id(self, value): self._raw.id = InstanceId(value) self.on_modify() - def id_matches(self, id: int | InstanceId) -> bool: - if not isinstance(id, InstanceId): - id = InstanceId(id) - - return self.id.area == id.area and self.id.instance == id.instance + def id_matches(self, other: InstanceIdRef) -> bool: + other = resolve_instance_id_ref(other) + return self.id.area == other.area and self.id.instance == other.instance @property def name(self) -> str | None: @@ -332,24 +330,37 @@ def connections(self, value: typing.Iterable[Connection]): self._raw.connections = tuple(value) self.on_modify() - def add_connection(self, state: str | State, message: str | Message, target: ScriptInstance): + def add_connection(self, state: str | State, message: str | Message, target: InstanceIdRef): correct_state = enum_helper.STATE_PER_GAME[self.target_game] correct_message = enum_helper.MESSAGE_PER_GAME[self.target_game] + target = resolve_instance_id_ref(target) + self.connections = self.connections + (Connection( state=_resolve_to_enum(correct_state, state), message=_resolve_to_enum(correct_message, message), - target=target.id + target=target ),) def remove_connection(self, connection: Connection): - self.connections = [c for c in self.connections if c is not connection] - - def remove_connections(self, target: Union[int, ScriptInstance]): - if isinstance(target, ScriptInstance): - target = target.id + self.connections = [c for c in self.connections if c != connection] + def remove_connections_from(self, target: InstanceIdRef): + target = resolve_instance_id_ref(target) self.connections = [c for c in self.connections if c.target != target] def mlvl_dependencies_for(self, asset_manager: AssetManager) -> Iterator[Dependency]: yield from self.get_properties().dependencies_for(asset_manager) + + +InstanceIdRef = InstanceId | int | ScriptInstance +InstanceRef = InstanceIdRef | str + +def resolve_instance_id_ref(inst: InstanceIdRef) -> InstanceId: + if isinstance(inst, InstanceId): + return inst + if isinstance(inst, ScriptInstance): + return inst.id + if isinstance(inst, int): + return InstanceId(inst) + raise TypeError(f"Invalid type: Expected InstanceIdRef, got {type(inst)}") diff --git a/test/formats/test_script_api.py b/test/formats/test_script_api.py index 122d5dc..2fc8d5a 100644 --- a/test/formats/test_script_api.py +++ b/test/formats/test_script_api.py @@ -50,16 +50,16 @@ def test_add_layer(prime2_area: Area, active: bool): def test_get_instance(prime2_area: Area): idx, name = 0x0045006B, "Pickup Object" - inst = prime2_area.get_instance(idx) + inst = prime2_area.mrea.get_instance(idx) assert inst.name == name - inst = prime2_area.get_instance(name) + inst = prime2_area.mrea.get_instance(name) assert inst.id == idx def test_remove_instance(prime2_area: Area): - old_len = len(list(prime2_area.all_instances)) - prime2_area.remove_instance("Pickup Object") - assert len(list(prime2_area.all_instances)) == old_len - 1 + old_len = len(list(prime2_area.mrea.all_instances)) + prime2_area.mrea.remove_instance("Pickup Object") + assert len(list(prime2_area.mrea.all_instances)) == old_len - 1 # Script Layer @@ -95,7 +95,7 @@ def test_edit_layer(prime2_area: Area, name: str, active: bool): def test_edit_properties(prime2_area: Area): from retro_data_structures.properties.echoes.objects.Pickup import Pickup - inst = prime2_area.get_instance("Pickup Object") + inst = prime2_area.mrea.get_instance("Pickup Object") inst.name = "Test" assert inst.name == "Test" @@ -107,12 +107,12 @@ def test_edit_properties(prime2_area: Area): def test_edit_connections(prime2_area: Area): from retro_data_structures.enums.echoes import Message, State - pickup = prime2_area.get_instance("Pickup Object") - relay = prime2_area.get_instance("Post Pickup") + pickup = prime2_area.mrea.get_instance("Pickup Object") + relay = prime2_area.mrea.get_instance("Post Pickup") original_connections = pickup.connections - pickup.remove_connections(relay) + pickup.remove_connections_from(relay) assert len(pickup.connections) == len(original_connections) - 1 pickup.add_connection(State.Arrived, Message.SetToZero, relay) From 7e312c8fb7f9a6e7e779e50ec327d640eceabd9b Mon Sep 17 00:00:00 2001 From: duncathan Date: Thu, 22 Jun 2023 23:44:06 -0600 Subject: [PATCH 4/7] move most of Mrea to Area; move Area to mrea.py --- retro_data_structures/formats/mlvl.py | 301 +---------------- retro_data_structures/formats/mrea.py | 307 +++++++++++++++++- retro_data_structures/formats/script_layer.py | 6 +- .../formats/script_object.py | 8 +- test/formats/test_mrea.py | 9 +- test/formats/test_script_api.py | 16 +- 6 files changed, 324 insertions(+), 323 deletions(-) diff --git a/retro_data_structures/formats/mlvl.py b/retro_data_structures/formats/mlvl.py index 1eb9753..352d2c9 100644 --- a/retro_data_structures/formats/mlvl.py +++ b/retro_data_structures/formats/mlvl.py @@ -40,7 +40,7 @@ from retro_data_structures.formats import Mapw from retro_data_structures.formats.cmdl import dependencies_for_material_set from retro_data_structures.formats.guid import GUID -from retro_data_structures.formats.mrea import Mrea +from retro_data_structures.formats.mrea import Area, Mrea from retro_data_structures.formats.savw import Savw from retro_data_structures.formats.script_layer import ScriptLayer, new_layer from retro_data_structures.formats.script_object import InstanceId, ScriptInstance @@ -263,305 +263,6 @@ def create(version: int, asset_id): ) -_hardcoded_dependencies: dict[int, dict[str, list[Dependency]]] = { - 0xD7C3B839: { - # Sanctum - "Default": [Dependency("TXTR", 0xd5b9e5d1)], - "Emperor Ing Stage 1": [Dependency("TXTR", 0x52c7d438)], - "Emperor Ing Stage 3": [Dependency("TXTR", 0xd5b9e5d1)], - "Emperor Ing Stage 1 Intro Cine": [Dependency("TXTR", 0x52c7d438)], - "Emperor Ing Stage 3 Death Cine": [Dependency("TXTR", 0xd5b9e5d1)], - }, - 0xA92F00B3: { - # Hive Temple - "CliffsideBoss": [ - Dependency("TXTR", 0x24149e16), - Dependency("TXTR", 0xbdb8a88a), - Dependency("FSM2", 0x3d31822b), - ] - }, - 0xC0113CE8: { - # Dynamo Works - "3rd Pass": [Dependency("RULE", 0x393ca543)] - }, - 0x5571E89E: { - # Hall of Combat Mastery - "2nd Pass Enemies": [Dependency("RULE", 0x393ca543)] - }, - 0x7B94B06B: { - # Hive Portal Chamber - "1st Pass": [Dependency("RULE", 0x393ca543)], - "2nd Pass": [Dependency("RULE", 0x393ca543)] - }, - 0xF8DBC03D: { - # Hive Reactor - "2nd Pass": [Dependency("RULE", 0x393ca543)] - }, - 0xB666B655: { - # Reactor Access - "2nd Pass": [Dependency("RULE", 0x393ca543)] - }, - 0xE79AAFAE: { - # Transport A Access - "2nd Pass": [Dependency("RULE", 0x393ca543)] - }, - 0xFEB7BD27: { - # Transport B Access - "Default": [Dependency("RULE", 0x393ca543)] - }, - 0x89D246FD: { - # Portal Access - "Default": [Dependency("RULE", 0x393ca543)] - }, - 0x0253782D: { - # Dark Forgotten Bridge - "Default": [Dependency("RULE", 0x393ca543)] - }, - 0x09DECF21: { - # Forgotten Bridge - "Default": [Dependency("RULE", 0x393ca543)] - }, - 0x629790F4: { - # Sacrificial Chamber - "1st Pass": [Dependency("RULE", 0x393ca543)] - }, - 0xBBE4B3AE: { - # Dungeon - "Default": [Dependency("TXTR", 0xe252e7f6)] - }, - 0x2BCD44A7: { - # Portal Terminal - "Default": [Dependency("TXTR", 0xb6fa5023)] - }, - 0xC68B5B51: { - # Transport to Sanctuary Fortress - "!!non_layer!!": [Dependency("TXTR", 0x75a219a8)] - }, - 0x625A2692: { - # Temple Transport Access - "!!non_layer!!": [Dependency("TXTR", 0x581c56ea)] - }, - 0x96F4CA1E: { - # Minigyro Chamber - "Default": [Dependency("TXTR", 0xac080dfb)] - }, - 0x5BBF334F: { - # Staging Area - "!!non_layer!!": [Dependency("TXTR", 0x738feb19)] - } -} - - -class Area: - _flags: Container - _layer_names: ListContainer - _index: int - - _mrea: Mrea = None - _strg: Strg = None - - # FIXME: since the whole Mlvl is now being passed, this function can have the other arguments removed - def __init__(self, raw: Container, asset_manager: AssetManager, flags: Container, names: Container, - index: int, parent_mlvl: Mlvl): - self._raw = raw - self.asset_manager = asset_manager - self._flags = flags - self._layer_names = names - self._index = index - self._parent_mlvl = parent_mlvl - - @property - def id(self) -> int: - return self._raw.internal_area_id - - @property - def index(self) -> int: - return self._index - - @property - def name(self) -> str: - try: - return self.strg.strings[0] - except UnknownAssetId: - return "!!" + self.internal_name - - @name.setter - def name(self, value): - self.strg.set_string(0, value) - - @property - def internal_name(self) -> str: - return self._raw.get("internal_area_name", "Unknown") - - @property - def strg(self) -> Strg: - if self._strg is None: - self._strg = self.asset_manager.get_file(self._raw.area_name_id, type_hint=Strg) - return self._strg - - @property - def mrea(self) -> Mrea: - if self._mrea is None: - self._mrea = self.asset_manager.get_file(self.mrea_asset_id, type_hint=Mrea) - return self._mrea - - @property - def mrea_asset_id(self) -> int: - return self._raw.area_mrea_id - - @property - def layers(self) -> Iterator[ScriptLayer]: - for layer in self.mrea.script_layers: - yield layer.with_parent(self) - - @property - def generated_objects_layer(self) -> ScriptLayer: - return self.mrea.generated_objects_layer - - def get_layer(self, name: str) -> ScriptLayer: - return next(layer for layer in self.layers if layer.name == name) - - def add_layer(self, name: str, active: bool = True) -> ScriptLayer: - index = len(self._layer_names) - self._layer_names.append(name) - self._flags.append(active) - raw = new_layer(index, self.asset_manager.target_game) - self.mrea._raw.sections.script_layer_section.append(raw) - return self.get_layer(name) - - @property - def next_instance_id(self) -> int: - ids = [instance.id.instance for layer in self.layers for instance in layer.instances] - return next(i for i in count() if i not in ids) - - def get_instance(self, instance_id: typing.Union[int, InstanceId]) -> typing.Optional[ScriptInstance]: - if not isinstance(instance_id, InstanceId): - instance_id = InstanceId(instance_id) - - for layer in self.layers: - if instance_id.layer == layer.index: - return layer._get_instance_by_id(instance_id) - - return None - - def get_instance_by_name(self, name: str) -> ScriptInstance: - return self.mrea._get_instance_by_name(name) - - def _raw_connect_to(self, source_dock_number: int, target_area: Area, target_dock_number: int): - source_dock = self._raw.docks[source_dock_number] - assert len(source_dock.connecting_dock) == 1, "Only docks with one connection supported" - source_dock.connecting_dock[0].area_index = target_area._index - source_dock.connecting_dock[0].dock_index = target_dock_number - - attached_area_index = [] - for docks in self._raw.docks: - for c in docks.connecting_dock: - if c.area_index not in attached_area_index: - attached_area_index.append(c.area_index) - self._raw.attached_area_index = construct.ListContainer(attached_area_index) - - def connect_dock_to(self, source_dock_number: int, target_area: Area, target_dock_number: int): - self._raw_connect_to(source_dock_number, target_area, target_dock_number) - target_area._raw_connect_to(target_dock_number, self, source_dock_number) - - def build_non_layer_dependencies(self) -> typing.Iterator[Dependency]: - if self.asset_manager.target_game <= Game.ECHOES: - geometry_section = self.mrea.get_raw_section("geometry_section") - if geometry_section: - for asset_id in PrefixedArray(Int32ub, AssetId32).parse(geometry_section[0]): - yield from self.asset_manager.get_dependencies_for_asset(asset_id) - else: - geometry = self.mrea.get_geometry() - if geometry is not None: - yield from dependencies_for_material_set(geometry[0].materials, self.asset_manager) - - valid_asset = self.asset_manager.target_game.is_valid_asset_id - if valid_asset(portal_area := self.mrea.get_portal_area()): - yield Dependency("PTLA", portal_area) - if valid_asset(static_geometry_map := self.mrea.get_static_geometry_map()): - yield Dependency("EGMC", static_geometry_map) - if valid_asset(path := self.mrea.get_path()): - yield Dependency("PATH", path) - - def build_scgn_dependencies(self, layer_deps: list[list[Dependency]], only_modified: bool = False): - layer_deps = list(layer_deps) - - layers = list(self.layers) - for instance in self.generated_objects_layer.instances: - inst_layer = instance.id.layer - if not only_modified or layers[inst_layer].is_modified: - layer_deps[inst_layer].extend(instance.mlvl_dependencies_for(self.asset_manager)) - - return [list(dict.fromkeys(deps)) for deps in layer_deps] - - def build_mlvl_dependencies(self, only_modified: bool = False): - layer_deps = [ - list( - layer.build_mlvl_dependencies(self.asset_manager) - if (not only_modified) or layer.is_modified() else - layer.dependencies - ) for layer in self.layers - ] - - if only_modified: - # assume we never modify these - layer_deps.append(list(self.non_layer_dependencies)) - else: - non_layer_deps = list(self.build_non_layer_dependencies()) - if "!!non_layer!!" in _hardcoded_dependencies.get(self.mrea_asset_id, {}): - non_layer_deps.extend(_hardcoded_dependencies[self.mrea_asset_id]["!!non_layer!!"]) - layer_deps.append(non_layer_deps) - - - layer_deps = self.build_scgn_dependencies(layer_deps, only_modified) - - if self.mrea_asset_id in _hardcoded_dependencies: - for layer_name, missing in _hardcoded_dependencies[self.mrea_asset_id].items(): - if layer_name == "!!non_layer!!": - continue - - layer = self.get_layer(layer_name) - if only_modified and not layer.is_modified: - continue - - layer_deps[layer.index].extend(missing) - - layer_deps = [ - [dep for dep in layer if not dep.exclude_for_mlvl] - for layer in layer_deps - ] - - offset = 0 - offsets = [] - for layer_dep in layer_deps: - offsets.append(offset) - offset += len(layer_dep) - - fancy_deps: list[Dependency] = list(itertools.chain(*layer_deps)) - deps = [Container(asset_type=dep.type, asset_id=dep.id) for dep in fancy_deps] - self._raw.dependencies.dependencies = deps - self._raw.dependencies.offsets = offsets - - @property - def layer_dependencies(self) -> dict[str, list[Dependency]]: - return { - layer.name: list(layer.dependencies) - for layer in self.layers - } - - @property - def non_layer_dependencies(self) -> Iterator[Dependency]: - deps = self._raw.dependencies - global_deps = deps.dependencies[deps.offsets[len(self._layer_names)]:] - yield from [Dependency(dep.asset_type, dep.asset_id) for dep in global_deps] - - @property - def dependencies(self) -> dict[str, list[Dependency]]: - deps = self.layer_dependencies - deps["!!non_layer!!"] = list(self.non_layer_dependencies) - return deps - - class Mlvl(BaseResource): _mapw: Mapw = None _savw: Savw = None diff --git a/retro_data_structures/formats/mrea.py b/retro_data_structures/formats/mrea.py index 6d74819..6468c1d 100644 --- a/retro_data_structures/formats/mrea.py +++ b/retro_data_structures/formats/mrea.py @@ -1,8 +1,10 @@ """ Wiki: https://wiki.axiodl.com/w/MREA_(Metroid_Prime_2) """ +from __future__ import annotations import copy import io +import itertools import typing from collections.abc import Iterator from enum import IntEnum @@ -22,20 +24,27 @@ from retro_data_structures import game_check from retro_data_structures.base_resource import AssetId, AssetType, BaseResource, Dependency -from retro_data_structures.common_types import FourCC, Transform4f +from retro_data_structures.common_types import AssetId32, FourCC, Transform4f from retro_data_structures.compression import LZOCompressedBlock from retro_data_structures.construct_extensions.alignment import PrefixedWithPaddingBefore from retro_data_structures.construct_extensions.version import BeforeVersion, WithVersion from retro_data_structures.data_section import DataSection +from retro_data_structures.exceptions import UnknownAssetId from retro_data_structures.formats.area_collision import AreaCollision from retro_data_structures.formats.arot import AROT +from retro_data_structures.formats.cmdl import dependencies_for_material_set from retro_data_structures.formats.lights import Lights -from retro_data_structures.formats.script_layer import SCGN, SCLY, ScriptLayer -from retro_data_structures.formats.script_object import InstanceId, InstanceIdRef, InstanceRef, ScriptInstance, resolve_instance_id_ref +from retro_data_structures.formats.script_layer import SCGN, SCLY, ScriptLayer, new_layer +from retro_data_structures.formats.script_object import InstanceId, InstanceIdRef, InstanceRef, ScriptInstance, resolve_instance_id +from retro_data_structures.formats.strg import Strg from retro_data_structures.formats.visi import VISI from retro_data_structures.formats.world_geometry import lazy_world_geometry from retro_data_structures.game_check import AssetIdCorrect, Game +if typing.TYPE_CHECKING: + from retro_data_structures.asset_manager import AssetManager + from retro_data_structures.formats.mlvl import Mlvl + class MREAVersion(IntEnum): PrimeKioskDemo = 0xC @@ -511,14 +520,98 @@ def generated_objects_layer(self) -> ScriptLayer: ) return self._generated_objects_layer + +class Area: + _flags: Container + _layer_names: ListContainer + _index: int + + _mrea: Mrea = None + _strg: Strg = None + + # FIXME: since the whole Mlvl is now being passed, this function can have the other arguments removed + def __init__(self, raw: Container, asset_manager: AssetManager, flags: Container, names: Container, + index: int, parent_mlvl: Mlvl): + self._raw = raw + self.asset_manager = asset_manager + self._flags = flags + self._layer_names = names + self._index = index + self._parent_mlvl = parent_mlvl + + @property + def id(self) -> int: + return self._raw.internal_area_id + + @property + def index(self) -> int: + return self._index + + @property + def name(self) -> str: + try: + return self.strg.strings[0] + except UnknownAssetId: + return "!!" + self.internal_name + + @name.setter + def name(self, value): + self.strg.set_string(0, value) + + @property + def internal_name(self) -> str: + return self._raw.get("internal_area_name", "Unknown") + + @property + def strg(self) -> Strg: + if self._strg is None: + self._strg = self.asset_manager.get_file(self._raw.area_name_id, type_hint=Strg) + return self._strg + + @property + def mrea(self) -> Mrea: + if self._mrea is None: + self._mrea = self.asset_manager.get_file(self.mrea_asset_id, type_hint=Mrea) + return self._mrea + + @property + def mrea_asset_id(self) -> int: + return self._raw.area_mrea_id + + @property + def layers(self) -> Iterator[ScriptLayer]: + for layer in self.mrea.script_layers: + yield layer.with_parent(self) + + @property + def generated_objects_layer(self) -> ScriptLayer: + return self.mrea.generated_objects_layer + @property def all_layers(self) -> Iterator[ScriptLayer]: - yield from self.script_layers + yield from self.layers yield self.generated_objects_layer + def get_layer(self, name: str) -> ScriptLayer: + return next(layer for layer in self.layers if layer.name == name) + + def add_layer(self, name: str, active: bool = True) -> ScriptLayer: + # FIXME + index = len(self._layer_names) + self._layer_names.append(name) + self._flags.append(active) + raw = new_layer(index, self.asset_manager.target_game) + self.mrea._raw.sections.script_layer_section.append(raw) + return self.get_layer(name) + + @property + def next_instance_id(self) -> int: + ids = {instance.id.instance for instance in self.all_instances} + return next(i for i in itertools.count() if i not in ids) + @property def all_instances(self) -> Iterator[ScriptInstance]: - for layer in self.script_layers: + for layer in self.all_layers: yield from layer.instances def get_instance(self, ref: InstanceRef) -> ScriptInstance: @@ -529,7 +622,7 @@ def get_instance(self, ref: InstanceRef) -> ScriptInstance: pass raise KeyError(ref) - def remove_instance(self, instance: int | InstanceId | str | ScriptInstance): + def remove_instance(self, instance: InstanceRef): for layer in self.all_layers: try: layer.remove_instance(instance) @@ -538,3 +631,205 @@ def remove_instance(self, instance: int | InstanceId | str | ScriptInstance): else: return raise KeyError(instance) + + def _raw_connect_to(self, source_dock_number: int, target_area: Area, target_dock_number: int): + source_dock = self._raw.docks[source_dock_number] + assert len(source_dock.connecting_dock) == 1, "Only docks with one connection supported" + source_dock.connecting_dock[0].area_index = target_area._index + source_dock.connecting_dock[0].dock_index = target_dock_number + + attached_area_index = [] + for docks in self._raw.docks: + for c in docks.connecting_dock: + if c.area_index not in attached_area_index: + attached_area_index.append(c.area_index) + self._raw.attached_area_index = construct.ListContainer(attached_area_index) + + def connect_dock_to(self, source_dock_number: int, target_area: Area, target_dock_number: int): + self._raw_connect_to(source_dock_number, target_area, target_dock_number) + target_area._raw_connect_to(target_dock_number, self, source_dock_number) + + def build_non_layer_dependencies(self) -> typing.Iterator[Dependency]: + if self.asset_manager.target_game <= Game.ECHOES: + geometry_section = self.mrea.get_raw_section("geometry_section") + if geometry_section: + for asset_id in PrefixedArray(Int32ub, AssetId32).parse(geometry_section[0]): + yield from self.asset_manager.get_dependencies_for_asset(asset_id) + else: + geometry = self.mrea.get_geometry() + if geometry is not None: + yield from dependencies_for_material_set(geometry[0].materials, self.asset_manager) + + valid_asset = self.asset_manager.target_game.is_valid_asset_id + if valid_asset(portal_area := self.mrea.get_portal_area()): + yield Dependency("PTLA", portal_area) + if valid_asset(static_geometry_map := self.mrea.get_static_geometry_map()): + yield Dependency("EGMC", static_geometry_map) + if valid_asset(path := self.mrea.get_path()): + yield Dependency("PATH", path) + + def build_scgn_dependencies(self, layer_deps: list[list[Dependency]], only_modified: bool = False): + layer_deps = list(layer_deps) + + layers = list(self.layers) + for instance in self.generated_objects_layer.instances: + inst_layer = instance.id.layer + if not only_modified or layers[inst_layer].is_modified: + layer_deps[inst_layer].extend(instance.mlvl_dependencies_for(self.asset_manager)) + + return [list(dict.fromkeys(deps)) for deps in layer_deps] + + def build_mlvl_dependencies(self, only_modified: bool = False): + layer_deps = [ + list( + layer.build_mlvl_dependencies(self.asset_manager) + if (not only_modified) or layer.is_modified() else + layer.dependencies + ) for layer in self.layers + ] + + if only_modified: + # assume we never modify these + layer_deps.append(list(self.non_layer_dependencies)) + else: + non_layer_deps = list(self.build_non_layer_dependencies()) + if "!!non_layer!!" in _hardcoded_dependencies.get(self.mrea_asset_id, {}): + non_layer_deps.extend(_hardcoded_dependencies[self.mrea_asset_id]["!!non_layer!!"]) + layer_deps.append(non_layer_deps) + + + layer_deps = self.build_scgn_dependencies(layer_deps, only_modified) + + if self.mrea_asset_id in _hardcoded_dependencies: + for layer_name, missing in _hardcoded_dependencies[self.mrea_asset_id].items(): + if layer_name == "!!non_layer!!": + continue + + layer = self.get_layer(layer_name) + if only_modified and not layer.is_modified: + continue + + layer_deps[layer.index].extend(missing) + + layer_deps = [ + [dep for dep in layer if not dep.exclude_for_mlvl] + for layer in layer_deps + ] + + offset = 0 + offsets = [] + for layer_dep in layer_deps: + offsets.append(offset) + offset += len(layer_dep) + + fancy_deps: list[Dependency] = list(itertools.chain(*layer_deps)) + deps = [Container(asset_type=dep.type, asset_id=dep.id) for dep in fancy_deps] + self._raw.dependencies.dependencies = deps + self._raw.dependencies.offsets = offsets + + @property + def layer_dependencies(self) -> dict[str, list[Dependency]]: + return { + layer.name: list(layer.dependencies) + for layer in self.layers + } + + @property + def non_layer_dependencies(self) -> Iterator[Dependency]: + deps = self._raw.dependencies + global_deps = deps.dependencies[deps.offsets[len(self._layer_names)]:] + yield from [Dependency(dep.asset_type, dep.asset_id) for dep in global_deps] + + @property + def dependencies(self) -> dict[str, list[Dependency]]: + deps = self.layer_dependencies + deps["!!non_layer!!"] = list(self.non_layer_dependencies) + return deps + +_hardcoded_dependencies: dict[int, dict[str, list[Dependency]]] = { + 0xD7C3B839: { + # Sanctum + "Default": [Dependency("TXTR", 0xd5b9e5d1)], + "Emperor Ing Stage 1": [Dependency("TXTR", 0x52c7d438)], + "Emperor Ing Stage 3": [Dependency("TXTR", 0xd5b9e5d1)], + "Emperor Ing Stage 1 Intro Cine": [Dependency("TXTR", 0x52c7d438)], + "Emperor Ing Stage 3 Death Cine": [Dependency("TXTR", 0xd5b9e5d1)], + }, + 0xA92F00B3: { + # Hive Temple + "CliffsideBoss": [ + Dependency("TXTR", 0x24149e16), + Dependency("TXTR", 0xbdb8a88a), + Dependency("FSM2", 0x3d31822b), + ] + }, + 0xC0113CE8: { + # Dynamo Works + "3rd Pass": [Dependency("RULE", 0x393ca543)] + }, + 0x5571E89E: { + # Hall of Combat Mastery + "2nd Pass Enemies": [Dependency("RULE", 0x393ca543)] + }, + 0x7B94B06B: { + # Hive Portal Chamber + "1st Pass": [Dependency("RULE", 0x393ca543)], + "2nd Pass": [Dependency("RULE", 0x393ca543)] + }, + 0xF8DBC03D: { + # Hive Reactor + "2nd Pass": [Dependency("RULE", 0x393ca543)] + }, + 0xB666B655: { + # Reactor Access + "2nd Pass": [Dependency("RULE", 0x393ca543)] + }, + 0xE79AAFAE: { + # Transport A Access + "2nd Pass": [Dependency("RULE", 0x393ca543)] + }, + 0xFEB7BD27: { + # Transport B Access + "Default": [Dependency("RULE", 0x393ca543)] + }, + 0x89D246FD: { + # Portal Access + "Default": [Dependency("RULE", 0x393ca543)] + }, + 0x0253782D: { + # Dark Forgotten Bridge + "Default": [Dependency("RULE", 0x393ca543)] + }, + 0x09DECF21: { + # Forgotten Bridge + "Default": [Dependency("RULE", 0x393ca543)] + }, + 0x629790F4: { + # Sacrificial Chamber + "1st Pass": [Dependency("RULE", 0x393ca543)] + }, + 0xBBE4B3AE: { + # Dungeon + "Default": [Dependency("TXTR", 0xe252e7f6)] + }, + 0x2BCD44A7: { + # Portal Terminal + "Default": [Dependency("TXTR", 0xb6fa5023)] + }, + 0xC68B5B51: { + # Transport to Sanctuary Fortress + "!!non_layer!!": [Dependency("TXTR", 0x75a219a8)] + }, + 0x625A2692: { + # Temple Transport Access + "!!non_layer!!": [Dependency("TXTR", 0x581c56ea)] + }, + 0x96F4CA1E: { + # Minigyro Chamber + "Default": [Dependency("TXTR", 0xac080dfb)] + }, + 0x5BBF334F: { + # Staging Area + "!!non_layer!!": [Dependency("TXTR", 0x738feb19)] + } +} diff --git a/retro_data_structures/formats/script_layer.py b/retro_data_structures/formats/script_layer.py index 6f6030a..4e16e99 100644 --- a/retro_data_structures/formats/script_layer.py +++ b/retro_data_structures/formats/script_layer.py @@ -25,7 +25,7 @@ from retro_data_structures.base_resource import Dependency from retro_data_structures.common_types import FourCC from retro_data_structures.construct_extensions.misc import Skip -from retro_data_structures.formats.script_object import ConstructScriptInstance, InstanceId, InstanceIdRef, InstanceRef, ScriptInstance, resolve_instance_id_ref +from retro_data_structures.formats.script_object import ConstructScriptInstance, InstanceId, InstanceIdRef, InstanceRef, ScriptInstance, resolve_instance_id from retro_data_structures.game_check import Game from retro_data_structures.properties import BaseObjectType @@ -121,7 +121,7 @@ def get_instance(self, ref: InstanceRef) -> ScriptInstance: return self._get_instance_by_id(ref) def _get_instance_by_id(self, instance_id: InstanceIdRef) -> ScriptInstance: - instance_id = resolve_instance_id_ref(instance_id) + instance_id = resolve_instance_id(instance_id) for instance in self.instances: if instance.id_matches(instance_id): return instance @@ -160,7 +160,7 @@ def add_memory_relay(self, name: str | None = None) -> ScriptInstance: def remove_instance(self, instance: InstanceRef): if isinstance(instance, str): instance = self._get_instance_by_name(instance) - instance = resolve_instance_id_ref(instance) + instance = resolve_instance_id(instance) matching_instances = [ i for i in self._raw.script_instances diff --git a/retro_data_structures/formats/script_object.py b/retro_data_structures/formats/script_object.py index c96dc88..a06a12f 100644 --- a/retro_data_structures/formats/script_object.py +++ b/retro_data_structures/formats/script_object.py @@ -269,7 +269,7 @@ def id(self, value): self.on_modify() def id_matches(self, other: InstanceIdRef) -> bool: - other = resolve_instance_id_ref(other) + other = resolve_instance_id(other) return self.id.area == other.area and self.id.instance == other.instance @property @@ -334,7 +334,7 @@ def add_connection(self, state: str | State, message: str | Message, target: Ins correct_state = enum_helper.STATE_PER_GAME[self.target_game] correct_message = enum_helper.MESSAGE_PER_GAME[self.target_game] - target = resolve_instance_id_ref(target) + target = resolve_instance_id(target) self.connections = self.connections + (Connection( state=_resolve_to_enum(correct_state, state), @@ -346,7 +346,7 @@ def remove_connection(self, connection: Connection): self.connections = [c for c in self.connections if c != connection] def remove_connections_from(self, target: InstanceIdRef): - target = resolve_instance_id_ref(target) + target = resolve_instance_id(target) self.connections = [c for c in self.connections if c.target != target] def mlvl_dependencies_for(self, asset_manager: AssetManager) -> Iterator[Dependency]: @@ -356,7 +356,7 @@ def mlvl_dependencies_for(self, asset_manager: AssetManager) -> Iterator[Depende InstanceIdRef = InstanceId | int | ScriptInstance InstanceRef = InstanceIdRef | str -def resolve_instance_id_ref(inst: InstanceIdRef) -> InstanceId: +def resolve_instance_id(inst: InstanceIdRef) -> InstanceId: if isinstance(inst, InstanceId): return inst if isinstance(inst, ScriptInstance): diff --git a/test/formats/test_mrea.py b/test/formats/test_mrea.py index 1362b2f..57fa0ae 100644 --- a/test/formats/test_mrea.py +++ b/test/formats/test_mrea.py @@ -22,16 +22,21 @@ def test_compare_p1(prime1_asset_manager): def test_compare_p2(prime2_asset_manager, mrea_asset_id: AssetId): + def _all_instances(mrea: Mrea): + for layer in mrea.script_layers: + yield from layer.instances + yield from mrea.generated_objects_layer.instances + resource = prime2_asset_manager.get_raw_asset(mrea_asset_id) decoded = Mrea.parse(resource.data, target_game=prime2_asset_manager.target_game) - for instance in decoded.all_instances: + for instance in _all_instances(decoded): assert isinstance(instance, ScriptInstance) encoded = decoded.build() decoded2 = Mrea.parse(encoded, target_game=prime2_asset_manager.target_game) - for instance in decoded2.all_instances: + for instance in _all_instances(decoded2): assert isinstance(instance, ScriptInstance) assert test_lib.purge_hidden(decoded2.raw) == test_lib.purge_hidden(decoded.raw) diff --git a/test/formats/test_script_api.py b/test/formats/test_script_api.py index 2fc8d5a..bdde83b 100644 --- a/test/formats/test_script_api.py +++ b/test/formats/test_script_api.py @@ -50,16 +50,16 @@ def test_add_layer(prime2_area: Area, active: bool): def test_get_instance(prime2_area: Area): idx, name = 0x0045006B, "Pickup Object" - inst = prime2_area.mrea.get_instance(idx) + inst = prime2_area.get_instance(idx) assert inst.name == name - inst = prime2_area.mrea.get_instance(name) + inst = prime2_area.get_instance(name) assert inst.id == idx def test_remove_instance(prime2_area: Area): - old_len = len(list(prime2_area.mrea.all_instances)) - prime2_area.mrea.remove_instance("Pickup Object") - assert len(list(prime2_area.mrea.all_instances)) == old_len - 1 + old_len = len(list(prime2_area.all_instances)) + prime2_area.remove_instance("Pickup Object") + assert len(list(prime2_area.all_instances)) == old_len - 1 # Script Layer @@ -95,7 +95,7 @@ def test_edit_layer(prime2_area: Area, name: str, active: bool): def test_edit_properties(prime2_area: Area): from retro_data_structures.properties.echoes.objects.Pickup import Pickup - inst = prime2_area.mrea.get_instance("Pickup Object") + inst = prime2_area.get_instance("Pickup Object") inst.name = "Test" assert inst.name == "Test" @@ -107,8 +107,8 @@ def test_edit_properties(prime2_area: Area): def test_edit_connections(prime2_area: Area): from retro_data_structures.enums.echoes import Message, State - pickup = prime2_area.mrea.get_instance("Pickup Object") - relay = prime2_area.mrea.get_instance("Post Pickup") + pickup = prime2_area.get_instance("Pickup Object") + relay = prime2_area.get_instance("Post Pickup") original_connections = pickup.connections From a88cba024017f790e7dfac4a865cd143bec3a784 Mon Sep 17 00:00:00 2001 From: duncathan Date: Fri, 23 Jun 2023 00:20:13 -0600 Subject: [PATCH 5/7] i clawed my way out of hell and all i got was this lousy FIXME --- retro_data_structures/formats/mrea.py | 1 + 1 file changed, 1 insertion(+) diff --git a/retro_data_structures/formats/mrea.py b/retro_data_structures/formats/mrea.py index 6468c1d..c7cb2ac 100644 --- a/retro_data_structures/formats/mrea.py +++ b/retro_data_structures/formats/mrea.py @@ -530,6 +530,7 @@ class Area: _strg: Strg = None # FIXME: since the whole Mlvl is now being passed, this function can have the other arguments removed + # FIXME: also every time i've tried to do this it breaks mlvl dependencies. good luck! def __init__(self, raw: Container, asset_manager: AssetManager, flags: Container, names: Container, index: int, parent_mlvl: Mlvl): self._raw = raw From 3854d1c1a43a987ec7ac1c123fe45ac6cab1f5b0 Mon Sep 17 00:00:00 2001 From: duncathan Date: Fri, 23 Jun 2023 00:24:30 -0600 Subject: [PATCH 6/7] ruff don't i have a commit hook set up for this??? --- retro_data_structures/formats/mlvl.py | 9 ++------- retro_data_structures/formats/mrea.py | 6 +++++- retro_data_structures/formats/script_layer.py | 15 +++++++++++++-- retro_data_structures/formats/script_object.py | 1 - test/formats/test_mrea.py | 1 - test/formats/test_script_api.py | 2 +- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/retro_data_structures/formats/mlvl.py b/retro_data_structures/formats/mlvl.py index 352d2c9..88f8ec2 100644 --- a/retro_data_structures/formats/mlvl.py +++ b/retro_data_structures/formats/mlvl.py @@ -3,10 +3,8 @@ """ from __future__ import annotations -import itertools import typing from collections.abc import Iterator -from itertools import count import construct from construct import ( @@ -38,17 +36,14 @@ from retro_data_structures.construct_extensions.misc import PrefixedArrayWithExtra from retro_data_structures.exceptions import UnknownAssetId from retro_data_structures.formats import Mapw -from retro_data_structures.formats.cmdl import dependencies_for_material_set from retro_data_structures.formats.guid import GUID -from retro_data_structures.formats.mrea import Area, Mrea +from retro_data_structures.formats.mrea import Area from retro_data_structures.formats.savw import Savw -from retro_data_structures.formats.script_layer import ScriptLayer, new_layer -from retro_data_structures.formats.script_object import InstanceId, ScriptInstance from retro_data_structures.formats.strg import Strg from retro_data_structures.game_check import Game if typing.TYPE_CHECKING: - from retro_data_structures.asset_manager import AssetManager + pass MLVLConnectingDock = Struct( area_index=Int32ub, diff --git a/retro_data_structures/formats/mrea.py b/retro_data_structures/formats/mrea.py index c7cb2ac..948445f 100644 --- a/retro_data_structures/formats/mrea.py +++ b/retro_data_structures/formats/mrea.py @@ -2,6 +2,7 @@ Wiki: https://wiki.axiodl.com/w/MREA_(Metroid_Prime_2) """ from __future__ import annotations + import copy import io import itertools @@ -35,7 +36,10 @@ from retro_data_structures.formats.cmdl import dependencies_for_material_set from retro_data_structures.formats.lights import Lights from retro_data_structures.formats.script_layer import SCGN, SCLY, ScriptLayer, new_layer -from retro_data_structures.formats.script_object import InstanceId, InstanceIdRef, InstanceRef, ScriptInstance, resolve_instance_id +from retro_data_structures.formats.script_object import ( + InstanceRef, + ScriptInstance, +) from retro_data_structures.formats.strg import Strg from retro_data_structures.formats.visi import VISI from retro_data_structures.formats.world_geometry import lazy_world_geometry diff --git a/retro_data_structures/formats/script_layer.py b/retro_data_structures/formats/script_layer.py index 4e16e99..c294317 100644 --- a/retro_data_structures/formats/script_layer.py +++ b/retro_data_structures/formats/script_layer.py @@ -25,7 +25,14 @@ from retro_data_structures.base_resource import Dependency from retro_data_structures.common_types import FourCC from retro_data_structures.construct_extensions.misc import Skip -from retro_data_structures.formats.script_object import ConstructScriptInstance, InstanceId, InstanceIdRef, InstanceRef, ScriptInstance, resolve_instance_id +from retro_data_structures.formats.script_object import ( + ConstructScriptInstance, + InstanceId, + InstanceIdRef, + InstanceRef, + ScriptInstance, + resolve_instance_id, +) from retro_data_structures.game_check import Game from retro_data_structures.properties import BaseObjectType @@ -76,7 +83,11 @@ def new_layer(index: int | None, target_game: Game) -> Container: }) -SCLY = IfThenElse(game_check.current_game_at_least(game_check.Game.ECHOES), ConstructScriptLayer("SCLY"), ScriptLayerPrime) +SCLY = IfThenElse( + game_check.current_game_at_least(game_check.Game.ECHOES), + ConstructScriptLayer("SCLY"), + ScriptLayerPrime +) SCGN = ConstructScriptLayer("SCGN") diff --git a/retro_data_structures/formats/script_object.py b/retro_data_structures/formats/script_object.py index a06a12f..7249e0f 100644 --- a/retro_data_structures/formats/script_object.py +++ b/retro_data_structures/formats/script_object.py @@ -22,7 +22,6 @@ Int32ub, PrefixedArray, Struct, - Union, ) from retro_data_structures import game_check, properties diff --git a/test/formats/test_mrea.py b/test/formats/test_mrea.py index 57fa0ae..af21ec7 100644 --- a/test/formats/test_mrea.py +++ b/test/formats/test_mrea.py @@ -3,7 +3,6 @@ import pytest from retro_data_structures.base_resource import AssetId -from retro_data_structures.formats import Mlvl from retro_data_structures.formats.mrea import Mrea from retro_data_structures.formats.script_object import ScriptInstance diff --git a/test/formats/test_script_api.py b/test/formats/test_script_api.py index bdde83b..4a616e6 100644 --- a/test/formats/test_script_api.py +++ b/test/formats/test_script_api.py @@ -3,7 +3,7 @@ import retro_data_structures.enums.echoes as _echoes_enums import retro_data_structures.enums.prime as _prime_enums from retro_data_structures.formats import script_object -from retro_data_structures.formats.mlvl import Mlvl, Area +from retro_data_structures.formats.mlvl import Area, Mlvl from retro_data_structures.formats.script_object import InstanceId From ff1260adc3a8b1f221fed84a3903ae6cd0ab1e04 Mon Sep 17 00:00:00 2001 From: duncathan Date: Fri, 23 Jun 2023 00:29:57 -0600 Subject: [PATCH 7/7] remove that ancient get_property thing --- retro_data_structures/formats/script_object.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/retro_data_structures/formats/script_object.py b/retro_data_structures/formats/script_object.py index 7249e0f..5cbc780 100644 --- a/retro_data_structures/formats/script_object.py +++ b/retro_data_structures/formats/script_object.py @@ -308,12 +308,6 @@ def set_properties(self, data: BaseObjectType): self._raw.base_property = data.to_bytes() self.on_modify() - def get_property(self, chain: Iterator[str]): - prop = self.get_properties() - for name in chain: - prop = getattr(prop, name) - return prop - @contextlib.contextmanager def edit_properties(self, type_cls: type[PropertyType]): props = self.get_properties_as(type_cls)