From 08340456d5f646ef67bca5090f1d7216fdb84e8b Mon Sep 17 00:00:00 2001 From: dyceron Date: Sun, 22 Dec 2024 09:48:42 -0500 Subject: [PATCH 1/5] BMTUN: Add functions for getting/setting tunables --- .../formats/bmtun.py | 15 ++++++++++++++- tests/formats/test_bmtun.py | 17 +++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/mercury_engine_data_structures/formats/bmtun.py b/src/mercury_engine_data_structures/formats/bmtun.py index 4dc1912b..3652bf2b 100644 --- a/src/mercury_engine_data_structures/formats/bmtun.py +++ b/src/mercury_engine_data_structures/formats/bmtun.py @@ -1,12 +1,13 @@ from __future__ import annotations import functools -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any import construct from construct.core import ( Const, Construct, + Container, Flag, Int32sl, Struct, @@ -48,3 +49,15 @@ class Bmtun(BaseResource): @functools.lru_cache def construct_class(cls, target_game: Game) -> Construct: return BMTUN + + def get_tunable(self, tunable: str, param: str) -> Container: + classes = self.raw.classes + if tunable not in classes: + raise ValueError(f"Unknown tunable: {tunable}!") + if param not in classes[tunable].tunables: + raise ValueError(f"Unknown tunable param: {param}!") + return self.raw.classes[tunable].tunables[param] + + def set_tunable(self, tunable_name: str, param: str, value: Any) -> None: + tunable = self.get_tunable(tunable_name, param) + tunable.value = value diff --git a/tests/formats/test_bmtun.py b/tests/formats/test_bmtun.py index 8092b4d2..d318f90e 100644 --- a/tests/formats/test_bmtun.py +++ b/tests/formats/test_bmtun.py @@ -10,3 +10,20 @@ @pytest.mark.parametrize("bmtun_path", samus_returns_data.all_files_ending_with(".bmtun")) def test_bmtun(samus_returns_tree, bmtun_path): parse_build_compare_editor(Bmtun, samus_returns_tree, bmtun_path) + + +@pytest.fixture() +def bmtun(samus_returns_tree) -> Bmtun: + return samus_returns_tree.get_parsed_asset("system/tunables/tunables.bmtun", type_hint=Bmtun) + + +def test_get_tunable(bmtun): + assert bmtun.get_tunable("Amiibo|CTunableReserveTanks", "fLifeTankSize").value == 299.0 + + +def test_set_tunable(bmtun): + bmtun.set_tunable("Amiibo|CTunableReserveTanks", "fLifeTankSize", 199.0) + assert bmtun.get_tunable("Amiibo|CTunableReserveTanks", "fLifeTankSize") == 199.0 + + with pytest.raises(ValueError): + bmtun.set_tunable("Amiibo|CTunableReserveTanks", "fSETankSize", True) From 6c951819707658d1663bc5f5a23f7cefe9d4a529 Mon Sep 17 00:00:00 2001 From: dyceron Date: Sun, 22 Dec 2024 16:33:15 -0500 Subject: [PATCH 2/5] Address reviews --- .../formats/bmtun.py | 26 ++++++++++--------- tests/formats/test_bmtun.py | 6 ++--- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmtun.py b/src/mercury_engine_data_structures/formats/bmtun.py index 3652bf2b..5d601682 100644 --- a/src/mercury_engine_data_structures/formats/bmtun.py +++ b/src/mercury_engine_data_structures/formats/bmtun.py @@ -1,13 +1,12 @@ from __future__ import annotations import functools -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING import construct from construct.core import ( Const, Construct, - Container, Flag, Int32sl, Struct, @@ -50,14 +49,17 @@ class Bmtun(BaseResource): def construct_class(cls, target_game: Game) -> Construct: return BMTUN - def get_tunable(self, tunable: str, param: str) -> Container: + def _check_tunable_exists(self, class_name: str, tunable_name: str) -> None: classes = self.raw.classes - if tunable not in classes: - raise ValueError(f"Unknown tunable: {tunable}!") - if param not in classes[tunable].tunables: - raise ValueError(f"Unknown tunable param: {param}!") - return self.raw.classes[tunable].tunables[param] - - def set_tunable(self, tunable_name: str, param: str, value: Any) -> None: - tunable = self.get_tunable(tunable_name, param) - tunable.value = value + if class_name not in classes: + raise KeyError(f"Unknown tunable: {class_name}!") + if tunable_name not in classes[class_name].tunables: + raise KeyError(f"Unknown tunable param: {tunable_name}!") + + def get_tunable(self, class_name: str, tunable_name: str) -> None: + self._check_tunable_exists(class_name, tunable_name) + return self.raw.classes[class_name].tunables[tunable_name].value + + def set_tunable(self, class_name: str, tunable_name: str, value: str | float | bool | int | list[float]) -> None: + self._check_tunable_exists(class_name, tunable_name) + self.raw.classes[class_name].tunables[tunable_name].value = value diff --git a/tests/formats/test_bmtun.py b/tests/formats/test_bmtun.py index d318f90e..12753998 100644 --- a/tests/formats/test_bmtun.py +++ b/tests/formats/test_bmtun.py @@ -18,12 +18,12 @@ def bmtun(samus_returns_tree) -> Bmtun: def test_get_tunable(bmtun): - assert bmtun.get_tunable("Amiibo|CTunableReserveTanks", "fLifeTankSize").value == 299.0 + assert bmtun.get_tunable("Amiibo|CTunableReserveTanks", "fLifeTankSize") == 299.0 def test_set_tunable(bmtun): bmtun.set_tunable("Amiibo|CTunableReserveTanks", "fLifeTankSize", 199.0) assert bmtun.get_tunable("Amiibo|CTunableReserveTanks", "fLifeTankSize") == 199.0 - with pytest.raises(ValueError): - bmtun.set_tunable("Amiibo|CTunableReserveTanks", "fSETankSize", True) + bmtun.set_tunable("CTunableMissile", "sDamageSource", "BOMB") + assert bmtun.get_tunable("CTunableMissile", "sDamageSource") == "BOMB" From a201cd73894fa20d3b2d534e110766dcc5c839fb Mon Sep 17 00:00:00 2001 From: dyceron Date: Tue, 24 Dec 2024 09:15:01 -0500 Subject: [PATCH 3/5] Review and add more testing --- src/mercury_engine_data_structures/formats/bmtun.py | 6 ++++-- tests/formats/test_bmtun.py | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmtun.py b/src/mercury_engine_data_structures/formats/bmtun.py index 5d601682..14606f20 100644 --- a/src/mercury_engine_data_structures/formats/bmtun.py +++ b/src/mercury_engine_data_structures/formats/bmtun.py @@ -56,10 +56,12 @@ def _check_tunable_exists(self, class_name: str, tunable_name: str) -> None: if tunable_name not in classes[class_name].tunables: raise KeyError(f"Unknown tunable param: {tunable_name}!") - def get_tunable(self, class_name: str, tunable_name: str) -> None: + Tunable = str | float | bool | int | list[float] + + def get_tunable(self, class_name: str, tunable_name: str) -> Tunable: self._check_tunable_exists(class_name, tunable_name) return self.raw.classes[class_name].tunables[tunable_name].value - def set_tunable(self, class_name: str, tunable_name: str, value: str | float | bool | int | list[float]) -> None: + def set_tunable(self, class_name: str, tunable_name: str, value: Tunable) -> None: self._check_tunable_exists(class_name, tunable_name) self.raw.classes[class_name].tunables[tunable_name].value = value diff --git a/tests/formats/test_bmtun.py b/tests/formats/test_bmtun.py index 12753998..87ca1dd5 100644 --- a/tests/formats/test_bmtun.py +++ b/tests/formats/test_bmtun.py @@ -20,6 +20,9 @@ def bmtun(samus_returns_tree) -> Bmtun: def test_get_tunable(bmtun): assert bmtun.get_tunable("Amiibo|CTunableReserveTanks", "fLifeTankSize") == 299.0 + with pytest.raises(KeyError): + bmtun.get_tunable("CTunableAbilityScanningPulse", "iRange") + def test_set_tunable(bmtun): bmtun.set_tunable("Amiibo|CTunableReserveTanks", "fLifeTankSize", 199.0) @@ -27,3 +30,6 @@ def test_set_tunable(bmtun): bmtun.set_tunable("CTunableMissile", "sDamageSource", "BOMB") assert bmtun.get_tunable("CTunableMissile", "sDamageSource") == "BOMB" + + with pytest.raises(KeyError): + bmtun.set_tunable("Amiibo|CTunableReserveTanks", "fPBTankSize", 10.0) From 8f523e08814abc906587553e849d642178b3ed1d Mon Sep 17 00:00:00 2001 From: dyceron Date: Tue, 24 Dec 2024 09:16:04 -0500 Subject: [PATCH 4/5] Rename KeyError messages --- src/mercury_engine_data_structures/formats/bmtun.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmtun.py b/src/mercury_engine_data_structures/formats/bmtun.py index 14606f20..ee422ed9 100644 --- a/src/mercury_engine_data_structures/formats/bmtun.py +++ b/src/mercury_engine_data_structures/formats/bmtun.py @@ -52,9 +52,9 @@ def construct_class(cls, target_game: Game) -> Construct: def _check_tunable_exists(self, class_name: str, tunable_name: str) -> None: classes = self.raw.classes if class_name not in classes: - raise KeyError(f"Unknown tunable: {class_name}!") + raise KeyError(f"Unknown class name: {class_name}!") if tunable_name not in classes[class_name].tunables: - raise KeyError(f"Unknown tunable param: {tunable_name}!") + raise KeyError(f"Unknown tunable name: {tunable_name}!") Tunable = str | float | bool | int | list[float] From 30b0974e7e5d0a819e484f6dbb4aac4b39bec62f Mon Sep 17 00:00:00 2001 From: dyceron Date: Tue, 24 Dec 2024 17:43:05 -0500 Subject: [PATCH 5/5] Improve testing --- src/mercury_engine_data_structures/formats/bmtun.py | 2 +- tests/formats/test_bmtun.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/mercury_engine_data_structures/formats/bmtun.py b/src/mercury_engine_data_structures/formats/bmtun.py index ee422ed9..14667c8f 100644 --- a/src/mercury_engine_data_structures/formats/bmtun.py +++ b/src/mercury_engine_data_structures/formats/bmtun.py @@ -54,7 +54,7 @@ def _check_tunable_exists(self, class_name: str, tunable_name: str) -> None: if class_name not in classes: raise KeyError(f"Unknown class name: {class_name}!") if tunable_name not in classes[class_name].tunables: - raise KeyError(f"Unknown tunable name: {tunable_name}!") + raise KeyError(f"Unknown tunable name for {class_name}: {tunable_name}!") Tunable = str | float | bool | int | list[float] diff --git a/tests/formats/test_bmtun.py b/tests/formats/test_bmtun.py index 87ca1dd5..fecc086a 100644 --- a/tests/formats/test_bmtun.py +++ b/tests/formats/test_bmtun.py @@ -31,5 +31,7 @@ def test_set_tunable(bmtun): bmtun.set_tunable("CTunableMissile", "sDamageSource", "BOMB") assert bmtun.get_tunable("CTunableMissile", "sDamageSource") == "BOMB" - with pytest.raises(KeyError): - bmtun.set_tunable("Amiibo|CTunableReserveTanks", "fPBTankSize", 10.0) + with pytest.raises(KeyError, match="Unknown tunable name for Amiibo|CTunableReserveTanks: FakeTunable!"): + bmtun.set_tunable("Amiibo|CTunableReserveTanks", "FakeTunable", 10.0) + with pytest.raises(KeyError, match="Unknown class name: FakeClass!"): + bmtun.set_tunable("FakeClass", "FakeTunable", 10.0)