diff --git a/src/config/models.py b/src/config/models.py index 86db4ad5..7ed94455 100644 --- a/src/config/models.py +++ b/src/config/models.py @@ -457,7 +457,7 @@ def min_max_tier_in_range(cls, v: int) -> int: class UniqueModel(BaseModel): model_config = ConfigDict(extra="forbid") - aspect: AspectUniqueFilterModel = None + aspect: AspectUniqueFilterModel = None # Aspect needs to stay on top so the model is written how people expect affix: list[AffixFilterModel] = [] itemType: list[ItemType] = [] profileAlias: str = "" diff --git a/src/item/filter.py b/src/item/filter.py index 8b9865ab..315f60e3 100644 --- a/src/item/filter.py +++ b/src/item/filter.py @@ -204,8 +204,19 @@ def _check_unique_item(self, item: Item) -> _FilterResult: matched_full_name = f"{filter_item.profileAlias}.{item.aspect.name}" res.matched.append(_MatchedFilter(matched_full_name, did_match_aspect=True)) res.all_unique_filters_are_aspects = all_filters_are_aspect - if not res.keep and item.rarity == ItemRarity.Mythic: # Always keep mythics no matter what + + # Always keep mythics no matter what + # If all filters are for aspects specifically and none apply to this item, we default to handle_uniques config + if not res.keep and ( + item.rarity == ItemRarity.Mythic + or ( + res.all_unique_filters_are_aspects + and not res.unique_aspect_in_profile + and IniConfigLoader().general.handle_uniques != UnfilteredUniquesType.junk + ) + ): res.keep = True + return res def _did_files_change(self) -> bool: diff --git a/src/loot_filter.py b/src/loot_filter.py index 905dc9b5..2397bba2 100644 --- a/src/loot_filter.py +++ b/src/loot_filter.py @@ -120,15 +120,13 @@ def check_items(inv: InventoryBase, force_refresh: ItemRefreshType): # property if item_descr.rarity == ItemRarity.Unique: if not res.keep: + _mark_as_junk() + elif res.keep: if res.all_unique_filters_are_aspects and not res.unique_aspect_in_profile: if IniConfigLoader().general.handle_uniques == UnfilteredUniquesType.favorite: _mark_as_favorite() - elif IniConfigLoader().general.handle_uniques == UnfilteredUniquesType.junk: - _mark_as_junk() - else: - _mark_as_junk() - elif res.keep and IniConfigLoader().general.mark_as_favorite: - _mark_as_favorite() + elif IniConfigLoader().general.mark_as_favorite: + _mark_as_favorite() else: if not res.keep: _mark_as_junk() diff --git a/src/scripts/vision_mode.py b/src/scripts/vision_mode.py index 3b9d9756..62ab833e 100644 --- a/src/scripts/vision_mode.py +++ b/src/scripts/vision_mode.py @@ -10,7 +10,7 @@ import src.logger from src.cam import Cam from src.config.loader import IniConfigLoader -from src.config.models import HandleRaresType, UnfilteredUniquesType +from src.config.models import HandleRaresType from src.config.ui import ResManager from src.item.data.item_type import ItemType from src.item.data.rarity import ItemRarity @@ -243,15 +243,7 @@ def vision_mode(): if item_descr is not None: res = Filter().should_keep(item_descr) - if not res.keep: - if ( - item_descr.rarity == ItemRarity.Unique - and res.all_unique_filters_are_aspects - and not res.unique_aspect_in_profile - ): - match = IniConfigLoader().general.handle_uniques != UnfilteredUniquesType.junk - else: - match = False + match = res.keep # Adapt colors based on config if match: diff --git a/tests/item/filter/data/filters.py b/tests/item/filter/data/filters.py index 01ed83d9..4ad5d0a2 100644 --- a/tests/item/filter/data/filters.py +++ b/tests/item/filter/data/filters.py @@ -151,6 +151,21 @@ ], ) +always_keep_mythics = ProfileModel( + name="keep_mythics", + Uniques=[ + UniqueModel(minPower=900), + ], +) + +aspect_only_unique_filters = ProfileModel( + name="aspect_only", + Uniques=[ + UniqueModel(aspect=AspectUniqueFilterModel(name="tibaults_will"), minPower=900), + UniqueModel(aspect=AspectUniqueFilterModel(name="black_river"), minPower=900, profileAlias="alias_test"), + ], +) + sigil = ProfileModel( name="test", Sigils=SigilFilterModel( diff --git a/tests/item/filter/data/uniques.py b/tests/item/filter/data/uniques.py index 2d070d79..1fe7218f 100644 --- a/tests/item/filter/data/uniques.py +++ b/tests/item/filter/data/uniques.py @@ -10,6 +10,18 @@ def __init__(self, rarity=ItemRarity.Unique, item_type=ItemType.Shield, power=91 super().__init__(rarity=rarity, item_type=item_type, power=power, **kwargs) +aspect_only_mythic_tests = [ + ("matches filter", True, ["aspect_only.tibaults_will"], TestUnique(aspect=Aspect(name="tibaults_will"), power=925)), + ("does not match filter", False, [], TestUnique(aspect=Aspect(name="tibaults_will"), power=800)), + ("matches with alias", True, ["alias_test.black_river"], TestUnique(aspect=Aspect(name="black_river"), power=925)), + ("no aspect applies", True, [], TestUnique(aspect=Aspect("crown_of_lucion"))), +] + +simple_mythics = [ + ("matches filter", True, TestUnique(aspect=Aspect(name="black_river"), power=925, rarity=ItemRarity.Mythic)), + ("does not match but should keep", True, TestUnique(aspect=Aspect(name="black_river"), power=800, rarity=ItemRarity.Mythic)), +] + uniques = [ ("item power too low", [], TestUnique(power=800)), ("wrong type", [], TestUnique(item_type=ItemType.Helm, aspect=Aspect(name="deathless_visage", value=1862))), diff --git a/tests/item/filter/filter_test.py b/tests/item/filter/filter_test.py index 6391e679..a4545c25 100644 --- a/tests/item/filter/filter_test.py +++ b/tests/item/filter/filter_test.py @@ -8,7 +8,7 @@ from src.item.models import Item from tests.item.filter.data.affixes import affixes from tests.item.filter.data.sigils import sigil_jalal, sigil_priority, sigils -from tests.item.filter.data.uniques import uniques +from tests.item.filter.data.uniques import aspect_only_mythic_tests, simple_mythics, uniques def _create_mocked_filter(mocker: MockerFixture) -> Filter: @@ -57,3 +57,23 @@ def test_uniques(name: str, result: list[str], item: Item, mocker: MockerFixture test_filter = _create_mocked_filter(mocker) test_filter.unique_filters = {filters.unique.name: filters.unique.Uniques} assert natsorted([match.profile for match in test_filter.should_keep(item).matched]) == natsorted(result) + + +@pytest.mark.parametrize(("name", "result", "item"), natsorted(simple_mythics), ids=[name for name, _, _ in natsorted(simple_mythics)]) +def test_mythic_always_kept(name: str, result: bool, item: Item, mocker: MockerFixture): + test_filter = _create_mocked_filter(mocker) + test_filter.unique_filters = {filters.always_keep_mythics.name: filters.always_keep_mythics.Uniques} + assert test_filter.should_keep(item).keep == result + + +@pytest.mark.parametrize( + ("name", "should_keep", "matched", "item"), + natsorted(aspect_only_mythic_tests), + ids=[name for name, _, _, _ in natsorted(aspect_only_mythic_tests)], +) +def test_unfiltered_unique_is_kept(name: str, should_keep: bool, matched: list[str], item: Item, mocker: MockerFixture): + test_filter = _create_mocked_filter(mocker) + test_filter.unique_filters = {filters.aspect_only_unique_filters.name: filters.aspect_only_unique_filters.Uniques} + test_filter_result = test_filter.should_keep(item) + assert natsorted([match.profile for match in test_filter_result.matched]) == natsorted(matched) + assert test_filter_result.keep == should_keep