diff --git a/src/mercury_engine_data_structures/formats/bmtun.py b/src/mercury_engine_data_structures/formats/bmtun.py index 4dc1912..14667c8 100644 --- a/src/mercury_engine_data_structures/formats/bmtun.py +++ b/src/mercury_engine_data_structures/formats/bmtun.py @@ -48,3 +48,20 @@ class Bmtun(BaseResource): @functools.lru_cache def construct_class(cls, target_game: Game) -> Construct: return BMTUN + + 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 class name: {class_name}!") + if tunable_name not in classes[class_name].tunables: + raise KeyError(f"Unknown tunable name for {class_name}: {tunable_name}!") + + 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: 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 8092b4d..fecc086 100644 --- a/tests/formats/test_bmtun.py +++ b/tests/formats/test_bmtun.py @@ -10,3 +10,28 @@ @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") == 299.0 + + with pytest.raises(KeyError): + bmtun.get_tunable("CTunableAbilityScanningPulse", "iRange") + + +def test_set_tunable(bmtun): + bmtun.set_tunable("Amiibo|CTunableReserveTanks", "fLifeTankSize", 199.0) + assert bmtun.get_tunable("Amiibo|CTunableReserveTanks", "fLifeTankSize") == 199.0 + + bmtun.set_tunable("CTunableMissile", "sDamageSource", "BOMB") + assert bmtun.get_tunable("CTunableMissile", "sDamageSource") == "BOMB" + + 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)