From b79c168ec465f8b1030a92af80a2e738e7dec98d Mon Sep 17 00:00:00 2001 From: CJ Shrader Date: Sun, 26 Jan 2025 13:44:21 -0500 Subject: [PATCH 1/5] Ruff fixes --- README.md | 54 ++++++++++++------------- tests/conftest.py | 2 +- tests/gui/importer/test_d4builds.py | 2 +- tests/gui/importer/test_diablo_trade.py | 2 +- tests/gui/importer/test_maxroll.py | 2 +- tests/gui/importer/test_mobalytics.py | 2 +- tests/utils/image_operations_test.py | 4 +- 7 files changed, 34 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 277ee39d..09e168bf 100644 --- a/README.md +++ b/README.md @@ -104,38 +104,38 @@ The config folder in `C:/Users//.d4lf` contains: ### params.ini -| \[general\] | Description | +| [general] | Description | |---------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| profiles | A set of profiles separated by comma. d4lf will look for these yaml files in config/profiles and in C:/Users/WINDOWS_USER/.d4lf/profiles | -| browser | Which browser to use to get builds, please make sure you pick an installed browser: chrome, edge or firefox are currently supported. Note: Trade importing can only work with Chrome and ignores this setting. | -| check_chest_tabs | Which chest tabs will be checked and filtered for items in case chest is open when starting the filter. You need to buy all slots. Counting is done left to right. E.g. 1,2,4 will check tab 1, tab 2, tab 4 | -| full_dump | When using the import build feature, whether to use the full dump (e.g. contains all filter items) or not | -| handle_rares | - `filter`: Filter them based on your profiles
- `ignore`: Ignores all rares, vision mode shows them as blue and auto mode never junks or favorites them
- `junk`: Vision mode shows them always as red, auto mode always junks rares | -| handle_uniques | How to handle uniques that do not match any filter. This property does not apply to filtered uniques. All mythics are favorited regardless of filter.
- `favorite`: Mark the unique as favorite and vision mode will show it as green (default)
- `ignore`: Do nothing with the unique and vision mode will show it as green
- `junk`: Mark any uniques that don't match any filters as junk and show as red in vision mode | -| keep_aspects | - `all`: Keep all legendary items
- `upgrade`: Keep all legendary items that upgrade your codex of power
- `none`: Keep no legendary items based on aspect (they are still filtered!) | -| mark_as_favorite | Whether to favorite matched items or not. Defaults to true | -| minimum_overlay_font_size | The minimum font size for the vision overlay, specifically the green text that shows which filter(s) are matching. Note: For small profile names, the font may actually be larger than this size but will never go below this size. | -| move_to_inv_item_type
move_to_stash_item_type | Which types of items to move when using fast move functionality. Will only affect tabs defined in check_chest_tabs. You can select more than one option.
- `favorites`: Move favorites only
- `junk`: Move junk only
- `unmarked`: Only items not marked as favorite or junk
- `everything`: Move everything | -| run_vision_mode_on_startup | If the vision mode should automatically start when starting d4lf. Otherwise has to be started manually with the vision button or the hotkey | -| use_tts | use TTS instead of OCR, see [TTS](#TTS) | - -| \[char\] | Description | +| profiles | A set of profiles separated by comma. d4lf will look for these yaml files in config/profiles and in C:/Users/WINDOWS_USER/.d4lf/profiles | +| browser | Which browser to use to get builds, please make sure you pick an installed browser: chrome, edge or firefox are currently supported. Note: Trade importing can only work with Chrome and ignores this setting. | +| check_chest_tabs | Which chest tabs will be checked and filtered for items in case chest is open when starting the filter. You need to buy all slots. Counting is done left to right. E.g. 1,2,4 will check tab 1, tab 2, tab 4 | +| full_dump | When using the import build feature, whether to use the full dump (e.g. contains all filter items) or not | +| handle_rares | - `filter`: Filter them based on your profiles
- `ignore`: Ignores all rares, vision mode shows them as blue and auto mode never junks or favorites them
- `junk`: Vision mode shows them always as red, auto mode always junks rares | +| handle_uniques | How to handle uniques that do not match any filter. This property does not apply to filtered uniques. All mythics are favorited regardless of filter.
- `favorite`: Mark the unique as favorite and vision mode will show it as green (default)
- `ignore`: Do nothing with the unique and vision mode will show it as green
- `junk`: Mark any uniques that don't match any filters as junk and show as red in vision mode | +| keep_aspects | - `all`: Keep all legendary items
- `upgrade`: Keep all legendary items that upgrade your codex of power
- `none`: Keep no legendary items based on aspect (they are still filtered!) | +| mark_as_favorite | Whether to favorite matched items or not. Defaults to true | +| minimum_overlay_font_size | The minimum font size for the vision overlay, specifically the green text that shows which filter(s) are matching. Note: For small profile names, the font may actually be larger than this size but will never go below this size. | +| move_to_inv_item_type
move_to_stash_item_type | Which types of items to move when using fast move functionality. Will only affect tabs defined in check_chest_tabs. You can select more than one option.
- `favorites`: Move favorites only
- `junk`: Move junk only
- `unmarked`: Only items not marked as favorite or junk
- `everything`: Move everything | +| run_vision_mode_on_startup | If the vision mode should automatically start when starting d4lf. Otherwise has to be started manually with the vision button or the hotkey | +| use_tts | use TTS instead of OCR, see [TTS](#TTS) | + +| [char] | Description | |-----------|-----------------------------------| | inventory | Your hotkey for opening inventory | -| \[advanced_options\] | Description | +| [advanced_options] | Description | |--------------------------|--------------------------------------------------------------------------------------------------------------------------| -| move_to_inv | Hotkey for moving items from stash to inventory | -| move_to_chest | Hotkey for moving items from inventory to stash | -| run_scripts | Hotkey to start/stop vision mode | -| run_filter | Hotkey to start/stop filtering items | -| run_filter_force_refresh | Hotkey to start/stop filtering items with a force refresh. All item statuses will be reset | -| force_refresh_only | Hotkey to reset all item statuses without running a filter after | -| exit_key | Hotkey to exit d4lf.exe | -| log_level | Logging level. Can be any of \[debug, info, warning, error, critical\] | -| scripts | Running different scripts | -| process_name | Process name of the D4 app. Defaults to "Diablo IV.exe". In case of using some remote play this might need to be adapted | -| vision_mode_only | If set to true, only the vision mode will be available. All functionality that clicks the screen is disabled. | +| move_to_inv | Hotkey for moving items from stash to inventory | +| move_to_chest | Hotkey for moving items from inventory to stash | +| run_scripts | Hotkey to start/stop vision mode | +| run_filter | Hotkey to start/stop filtering items | +| run_filter_force_refresh | Hotkey to start/stop filtering items with a force refresh. All item statuses will be reset | +| force_refresh_only | Hotkey to reset all item statuses without running a filter after | +| exit_key | Hotkey to exit d4lf.exe | +| log_level | Logging level. Can be any of [debug, info, warning, error, critical] | +| scripts | Running different scripts | +| process_name | Process name of the D4 app. Defaults to "Diablo IV.exe". In case of using some remote play this might need to be adapted | +| vision_mode_only | If set to true, only the vision mode will be available. All functionality that clicks the screen is disabled. | ### GUI diff --git a/tests/conftest.py b/tests/conftest.py index bce1975b..83d0c00f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,7 +5,7 @@ from src.config.models import BrowserType -@pytest.fixture +@pytest.fixture() def mock_ini_loader(mocker: MockerFixture): general_mock = mocker.patch.object(IniConfigLoader(), "_general") general_mock.language = "enUS" diff --git a/tests/gui/importer/test_d4builds.py b/tests/gui/importer/test_d4builds.py index a4987c45..fdf28bb9 100644 --- a/tests/gui/importer/test_d4builds.py +++ b/tests/gui/importer/test_d4builds.py @@ -18,7 +18,7 @@ @pytest.mark.parametrize("url", URLS) -@pytest.mark.selenium +@pytest.mark.selenium() def test_import_d4builds(url: str, mock_ini_loader: MockerFixture, mocker: MockerFixture): Dataloader() # need to load data first or the mock will make it impossible mocker.patch("builtins.open", new=mocker.mock_open()) diff --git a/tests/gui/importer/test_diablo_trade.py b/tests/gui/importer/test_diablo_trade.py index cc24e808..a41cc677 100644 --- a/tests/gui/importer/test_diablo_trade.py +++ b/tests/gui/importer/test_diablo_trade.py @@ -10,7 +10,7 @@ @pytest.mark.parametrize("url", URLS) -@pytest.mark.selenium +@pytest.mark.selenium() def test_import_diablo_trade(url: str, mock_ini_loader: MockerFixture, mocker: MockerFixture): Dataloader() # need to load data first or the mock will make it impossible mocker.patch("builtins.open", new=mocker.mock_open()) diff --git a/tests/gui/importer/test_maxroll.py b/tests/gui/importer/test_maxroll.py index 78e3a8e4..bae7cead 100644 --- a/tests/gui/importer/test_maxroll.py +++ b/tests/gui/importer/test_maxroll.py @@ -15,7 +15,7 @@ @pytest.mark.parametrize("url", URLS) -@pytest.mark.requests +@pytest.mark.requests() def test_import_maxroll(url: str, mock_ini_loader: MockerFixture, mocker: MockerFixture): Dataloader() # need to load data first or the mock will make it impossible mocker.patch("builtins.open", new=mocker.mock_open()) diff --git a/tests/gui/importer/test_mobalytics.py b/tests/gui/importer/test_mobalytics.py index 65426f22..dfb65944 100644 --- a/tests/gui/importer/test_mobalytics.py +++ b/tests/gui/importer/test_mobalytics.py @@ -24,7 +24,7 @@ @pytest.mark.parametrize("url", URLS) -@pytest.mark.requests +@pytest.mark.requests() def test_import_mobalytics(url: str, mock_ini_loader: MockerFixture, mocker: MockerFixture): Dataloader() # need to load data first or the mock will make it impossible mocker.patch("builtins.open", new=mocker.mock_open()) diff --git a/tests/utils/image_operations_test.py b/tests/utils/image_operations_test.py index c81f9fdc..5d8ab413 100644 --- a/tests/utils/image_operations_test.py +++ b/tests/utils/image_operations_test.py @@ -102,12 +102,12 @@ def test_create_mask(): assert np.count_nonzero(mask) == 100 - 36 -@pytest.fixture +@pytest.fixture() def filter_img(): return np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8) -@pytest.fixture +@pytest.fixture() def color_range(): return [np.array([0, 0, 0]), np.array([180, 255, 255])] From 34a7d9f0d48af1723b760955da1f68851fb8b1f7 Mon Sep 17 00:00:00 2001 From: CJ Shrader Date: Sun, 26 Jan 2025 13:45:04 -0500 Subject: [PATCH 2/5] Revert "Ruff fixes" This reverts commit 1675788aad391c191748a2f56f45438a84c35ae5. --- README.md | 54 ++++++++++++------------- tests/conftest.py | 2 +- tests/gui/importer/test_d4builds.py | 2 +- tests/gui/importer/test_diablo_trade.py | 2 +- tests/gui/importer/test_maxroll.py | 2 +- tests/gui/importer/test_mobalytics.py | 2 +- tests/utils/image_operations_test.py | 4 +- 7 files changed, 34 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 09e168bf..277ee39d 100644 --- a/README.md +++ b/README.md @@ -104,38 +104,38 @@ The config folder in `C:/Users//.d4lf` contains: ### params.ini -| [general] | Description | +| \[general\] | Description | |---------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| profiles | A set of profiles separated by comma. d4lf will look for these yaml files in config/profiles and in C:/Users/WINDOWS_USER/.d4lf/profiles | -| browser | Which browser to use to get builds, please make sure you pick an installed browser: chrome, edge or firefox are currently supported. Note: Trade importing can only work with Chrome and ignores this setting. | -| check_chest_tabs | Which chest tabs will be checked and filtered for items in case chest is open when starting the filter. You need to buy all slots. Counting is done left to right. E.g. 1,2,4 will check tab 1, tab 2, tab 4 | -| full_dump | When using the import build feature, whether to use the full dump (e.g. contains all filter items) or not | -| handle_rares | - `filter`: Filter them based on your profiles
- `ignore`: Ignores all rares, vision mode shows them as blue and auto mode never junks or favorites them
- `junk`: Vision mode shows them always as red, auto mode always junks rares | -| handle_uniques | How to handle uniques that do not match any filter. This property does not apply to filtered uniques. All mythics are favorited regardless of filter.
- `favorite`: Mark the unique as favorite and vision mode will show it as green (default)
- `ignore`: Do nothing with the unique and vision mode will show it as green
- `junk`: Mark any uniques that don't match any filters as junk and show as red in vision mode | -| keep_aspects | - `all`: Keep all legendary items
- `upgrade`: Keep all legendary items that upgrade your codex of power
- `none`: Keep no legendary items based on aspect (they are still filtered!) | -| mark_as_favorite | Whether to favorite matched items or not. Defaults to true | -| minimum_overlay_font_size | The minimum font size for the vision overlay, specifically the green text that shows which filter(s) are matching. Note: For small profile names, the font may actually be larger than this size but will never go below this size. | -| move_to_inv_item_type
move_to_stash_item_type | Which types of items to move when using fast move functionality. Will only affect tabs defined in check_chest_tabs. You can select more than one option.
- `favorites`: Move favorites only
- `junk`: Move junk only
- `unmarked`: Only items not marked as favorite or junk
- `everything`: Move everything | -| run_vision_mode_on_startup | If the vision mode should automatically start when starting d4lf. Otherwise has to be started manually with the vision button or the hotkey | -| use_tts | use TTS instead of OCR, see [TTS](#TTS) | - -| [char] | Description | +| profiles | A set of profiles separated by comma. d4lf will look for these yaml files in config/profiles and in C:/Users/WINDOWS_USER/.d4lf/profiles | +| browser | Which browser to use to get builds, please make sure you pick an installed browser: chrome, edge or firefox are currently supported. Note: Trade importing can only work with Chrome and ignores this setting. | +| check_chest_tabs | Which chest tabs will be checked and filtered for items in case chest is open when starting the filter. You need to buy all slots. Counting is done left to right. E.g. 1,2,4 will check tab 1, tab 2, tab 4 | +| full_dump | When using the import build feature, whether to use the full dump (e.g. contains all filter items) or not | +| handle_rares | - `filter`: Filter them based on your profiles
- `ignore`: Ignores all rares, vision mode shows them as blue and auto mode never junks or favorites them
- `junk`: Vision mode shows them always as red, auto mode always junks rares | +| handle_uniques | How to handle uniques that do not match any filter. This property does not apply to filtered uniques. All mythics are favorited regardless of filter.
- `favorite`: Mark the unique as favorite and vision mode will show it as green (default)
- `ignore`: Do nothing with the unique and vision mode will show it as green
- `junk`: Mark any uniques that don't match any filters as junk and show as red in vision mode | +| keep_aspects | - `all`: Keep all legendary items
- `upgrade`: Keep all legendary items that upgrade your codex of power
- `none`: Keep no legendary items based on aspect (they are still filtered!) | +| mark_as_favorite | Whether to favorite matched items or not. Defaults to true | +| minimum_overlay_font_size | The minimum font size for the vision overlay, specifically the green text that shows which filter(s) are matching. Note: For small profile names, the font may actually be larger than this size but will never go below this size. | +| move_to_inv_item_type
move_to_stash_item_type | Which types of items to move when using fast move functionality. Will only affect tabs defined in check_chest_tabs. You can select more than one option.
- `favorites`: Move favorites only
- `junk`: Move junk only
- `unmarked`: Only items not marked as favorite or junk
- `everything`: Move everything | +| run_vision_mode_on_startup | If the vision mode should automatically start when starting d4lf. Otherwise has to be started manually with the vision button or the hotkey | +| use_tts | use TTS instead of OCR, see [TTS](#TTS) | + +| \[char\] | Description | |-----------|-----------------------------------| | inventory | Your hotkey for opening inventory | -| [advanced_options] | Description | +| \[advanced_options\] | Description | |--------------------------|--------------------------------------------------------------------------------------------------------------------------| -| move_to_inv | Hotkey for moving items from stash to inventory | -| move_to_chest | Hotkey for moving items from inventory to stash | -| run_scripts | Hotkey to start/stop vision mode | -| run_filter | Hotkey to start/stop filtering items | -| run_filter_force_refresh | Hotkey to start/stop filtering items with a force refresh. All item statuses will be reset | -| force_refresh_only | Hotkey to reset all item statuses without running a filter after | -| exit_key | Hotkey to exit d4lf.exe | -| log_level | Logging level. Can be any of [debug, info, warning, error, critical] | -| scripts | Running different scripts | -| process_name | Process name of the D4 app. Defaults to "Diablo IV.exe". In case of using some remote play this might need to be adapted | -| vision_mode_only | If set to true, only the vision mode will be available. All functionality that clicks the screen is disabled. | +| move_to_inv | Hotkey for moving items from stash to inventory | +| move_to_chest | Hotkey for moving items from inventory to stash | +| run_scripts | Hotkey to start/stop vision mode | +| run_filter | Hotkey to start/stop filtering items | +| run_filter_force_refresh | Hotkey to start/stop filtering items with a force refresh. All item statuses will be reset | +| force_refresh_only | Hotkey to reset all item statuses without running a filter after | +| exit_key | Hotkey to exit d4lf.exe | +| log_level | Logging level. Can be any of \[debug, info, warning, error, critical\] | +| scripts | Running different scripts | +| process_name | Process name of the D4 app. Defaults to "Diablo IV.exe". In case of using some remote play this might need to be adapted | +| vision_mode_only | If set to true, only the vision mode will be available. All functionality that clicks the screen is disabled. | ### GUI diff --git a/tests/conftest.py b/tests/conftest.py index 83d0c00f..bce1975b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,7 +5,7 @@ from src.config.models import BrowserType -@pytest.fixture() +@pytest.fixture def mock_ini_loader(mocker: MockerFixture): general_mock = mocker.patch.object(IniConfigLoader(), "_general") general_mock.language = "enUS" diff --git a/tests/gui/importer/test_d4builds.py b/tests/gui/importer/test_d4builds.py index fdf28bb9..a4987c45 100644 --- a/tests/gui/importer/test_d4builds.py +++ b/tests/gui/importer/test_d4builds.py @@ -18,7 +18,7 @@ @pytest.mark.parametrize("url", URLS) -@pytest.mark.selenium() +@pytest.mark.selenium def test_import_d4builds(url: str, mock_ini_loader: MockerFixture, mocker: MockerFixture): Dataloader() # need to load data first or the mock will make it impossible mocker.patch("builtins.open", new=mocker.mock_open()) diff --git a/tests/gui/importer/test_diablo_trade.py b/tests/gui/importer/test_diablo_trade.py index a41cc677..cc24e808 100644 --- a/tests/gui/importer/test_diablo_trade.py +++ b/tests/gui/importer/test_diablo_trade.py @@ -10,7 +10,7 @@ @pytest.mark.parametrize("url", URLS) -@pytest.mark.selenium() +@pytest.mark.selenium def test_import_diablo_trade(url: str, mock_ini_loader: MockerFixture, mocker: MockerFixture): Dataloader() # need to load data first or the mock will make it impossible mocker.patch("builtins.open", new=mocker.mock_open()) diff --git a/tests/gui/importer/test_maxroll.py b/tests/gui/importer/test_maxroll.py index bae7cead..78e3a8e4 100644 --- a/tests/gui/importer/test_maxroll.py +++ b/tests/gui/importer/test_maxroll.py @@ -15,7 +15,7 @@ @pytest.mark.parametrize("url", URLS) -@pytest.mark.requests() +@pytest.mark.requests def test_import_maxroll(url: str, mock_ini_loader: MockerFixture, mocker: MockerFixture): Dataloader() # need to load data first or the mock will make it impossible mocker.patch("builtins.open", new=mocker.mock_open()) diff --git a/tests/gui/importer/test_mobalytics.py b/tests/gui/importer/test_mobalytics.py index dfb65944..65426f22 100644 --- a/tests/gui/importer/test_mobalytics.py +++ b/tests/gui/importer/test_mobalytics.py @@ -24,7 +24,7 @@ @pytest.mark.parametrize("url", URLS) -@pytest.mark.requests() +@pytest.mark.requests def test_import_mobalytics(url: str, mock_ini_loader: MockerFixture, mocker: MockerFixture): Dataloader() # need to load data first or the mock will make it impossible mocker.patch("builtins.open", new=mocker.mock_open()) diff --git a/tests/utils/image_operations_test.py b/tests/utils/image_operations_test.py index 5d8ab413..c81f9fdc 100644 --- a/tests/utils/image_operations_test.py +++ b/tests/utils/image_operations_test.py @@ -102,12 +102,12 @@ def test_create_mask(): assert np.count_nonzero(mask) == 100 - 36 -@pytest.fixture() +@pytest.fixture def filter_img(): return np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8) -@pytest.fixture() +@pytest.fixture def color_range(): return [np.array([0, 0, 0]), np.array([180, 255, 255])] From e778e810ffc6ea23b326ffb4b2ae4a92b02d397c Mon Sep 17 00:00:00 2001 From: CJ Shrader Date: Tue, 28 Jan 2025 07:47:07 -0500 Subject: [PATCH 3/5] Checkin commit, slowed down mouse when using the complete reset and added a double check for every item --- src/item/descr/read_descr_tts.py | 4 +++- src/scripts/common.py | 7 ++++--- src/scripts/loot_filter_tts.py | 27 +++++++++++++++++++++------ src/ui/inventory_base.py | 10 ++++++++++ src/utils/custom_mouse.py | 2 +- 5 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/item/descr/read_descr_tts.py b/src/item/descr/read_descr_tts.py index 6190a67f..0f63b7fb 100644 --- a/src/item/descr/read_descr_tts.py +++ b/src/item/descr/read_descr_tts.py @@ -324,7 +324,9 @@ def read_descr() -> Item | None: ] ): return item - if all([not is_armor(item.item_type), not is_jewelry(item.item_type), not is_weapon(item.item_type)]): + if all( + [not is_armor(item.item_type), not is_jewelry(item.item_type), not is_weapon(item.item_type), item.item_type != ItemType.Shield] + ): return None if item.rarity not in [ItemRarity.Legendary, ItemRarity.Mythic, ItemRarity.Unique]: return item diff --git a/src/scripts/common.py b/src/scripts/common.py index ff417278..6e9cabe6 100644 --- a/src/scripts/common.py +++ b/src/scripts/common.py @@ -33,13 +33,14 @@ def reset_canvas(root, canvas): def reset_item_status(occupied, inv): for item_slot in occupied: if item_slot.is_fav: - inv.hover_item(item_slot) + inv.hover_item_with_delay(item_slot) keyboard.send("space") if item_slot.is_junk: - inv.hover_item(item_slot) + inv.hover_item_with_delay(item_slot) keyboard.send("space") - time.sleep(0.13) + time.sleep(0.15) keyboard.send("space") + time.sleep(0.15) if occupied: mouse.move(*Cam().abs_window_to_monitor((0, 0))) diff --git a/src/scripts/loot_filter_tts.py b/src/scripts/loot_filter_tts.py index 74f3638d..28054f8c 100644 --- a/src/scripts/loot_filter_tts.py +++ b/src/scripts/loot_filter_tts.py @@ -16,11 +16,11 @@ def check_items(inv: InventoryBase, force_refresh: ItemRefreshType): - occupied, _ = inv.get_item_slots() + occupied, empty = inv.get_item_slots() if force_refresh == ItemRefreshType.force_with_filter or force_refresh == ItemRefreshType.force_without_filter: reset_item_status(occupied, inv) - occupied, _ = inv.get_item_slots() + occupied, empty = inv.get_item_slots() if force_refresh == ItemRefreshType.force_without_filter: return @@ -28,6 +28,7 @@ def check_items(inv: InventoryBase, force_refresh: ItemRefreshType): num_fav = sum(1 for slot in occupied if slot.is_fav) num_junk = sum(1 for slot in occupied if slot.is_junk) LOGGER.info(f"Items: {len(occupied)} (favorite: {num_fav}, junk: {num_junk}) in {inv.menu_name}") + previous_item = occupied[1] if len(occupied) > 1 else empty[0] for item in occupied: if item.is_junk or item.is_fav: continue @@ -35,24 +36,38 @@ def check_items(inv: InventoryBase, force_refresh: ItemRefreshType): time.sleep(0.15) img = Cam().grab() item_descr = None + item_descr_previous_check = None try: - item_descr = src.item.descr.read_descr_tts.read_descr() + item_descr_previous_check = src.item.descr.read_descr_tts.read_descr() LOGGER.debug(f"Parsed item based on TTS: {item_descr}") except Exception: screenshot("tts_error", img=img) LOGGER.exception(f"Error in TTS read_descr. {src.tts.LAST_ITEM=}") - if item_descr is None: - LOGGER.info("Retry item detection") - time.sleep(0.3) + + retry_count = 0 + while item_descr is None and retry_count != 5: + # Check again to make sure the item is what we think. + # Move off of the item then back on again + inv.hover_left_of_item(item) + time.sleep(0.05) + inv.hover_item(item) + time.sleep(0.15) try: item_descr = src.item.descr.read_descr_tts.read_descr() LOGGER.debug(f"Parsed item based on TTS: {item_descr}") + if item_descr != item_descr_previous_check: + item_descr_previous_check = item_descr + item_descr = None except Exception: screenshot("tts_error", img=img) LOGGER.exception(f"Error in TTS read_descr. {src.tts.LAST_ITEM=}") + retry_count += 1 + if item_descr is None: continue + previous_item = item + # Hardcoded filters if item_descr.rarity == ItemRarity.Common and item_descr.item_type == ItemType.Material: LOGGER.info("Matched: Material") diff --git a/src/ui/inventory_base.py b/src/ui/inventory_base.py index b9cbdcdc..d63fb63f 100644 --- a/src/ui/inventory_base.py +++ b/src/ui/inventory_base.py @@ -84,3 +84,13 @@ def get_item_slots(self, img: np.ndarray | None = None) -> tuple[list[ItemSlot], def hover_item(self, item: ItemSlot): mouse.move(*Cam().window_to_monitor(item.center), randomize=15) + + # Needed for double checking a TTS + def hover_left_of_item(self, item: ItemSlot): + mouse.move( + *Cam().window_to_monitor([item.bounding_box[0] - item.bounding_box[2] / 2, item.bounding_box[1] + item.bounding_box[3] / 2]), + randomize=15, + ) + + def hover_item_with_delay(self, item: ItemSlot, delay_factor: tuple[float, float] = (1.5, 2.0)): + mouse.move(*Cam().window_to_monitor(item.center), randomize=15, delay_factor=delay_factor) diff --git a/src/utils/custom_mouse.py b/src/utils/custom_mouse.py index e0550c10..46a10058 100644 --- a/src/utils/custom_mouse.py +++ b/src/utils/custom_mouse.py @@ -244,7 +244,7 @@ def move(x: int, y: int, absolute: bool = True, randomize: int | tuple[int, int] from_point, (x, y), offsetBoundaryX=offsetBoundaryX, offsetBoundaryY=offsetBoundaryY, targetPoints=targetPoints ) - duration = min(0.3, max(0.05, dist * 0.0004) * random.uniform(delay_factor[0], delay_factor[1])) + duration = min(0.5, max(0.05, dist * 0.0004) * random.uniform(delay_factor[0], delay_factor[1])) delta = duration / len(human_curve.points) for point in human_curve.points: From d7031461fc6d2e42ccdb09c052235302b5275389 Mon Sep 17 00:00:00 2001 From: CJ Shrader Date: Wed, 29 Jan 2025 09:03:08 -0500 Subject: [PATCH 4/5] - Added double checking of all TTS read items to ensure they are what we think they are - Added support for some missing item types - Known issue: Lidless wall is still not read properly --- src/__init__.py | 2 +- src/item/data/item_type.py | 7 +++---- src/item/descr/read_descr_tts.py | 11 +++++++++-- src/scripts/loot_filter_tts.py | 12 ++++-------- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/__init__.py b/src/__init__.py index aec74368..47adc4c0 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -2,4 +2,4 @@ TP = concurrent.futures.ThreadPoolExecutor() -__version__ = "6.0.0alpha3" +__version__ = "6.0.0alpha4" diff --git a/src/item/data/item_type.py b/src/item/data/item_type.py index 8aa4777d..950881f4 100644 --- a/src/item/data/item_type.py +++ b/src/item/data/item_type.py @@ -32,6 +32,7 @@ class ItemType(Enum): Tome = "tome" Wand = "wand" # Custom Types + Cache = "cache" Compass = "compass" Consumable = "consumable" Gem = "gem" @@ -41,6 +42,7 @@ class ItemType(Enum): Sigil = "nightmare sigil" TemperManual = "temper manual" Tribute = "tribute" + WhisperingWood = "whispering wood" def is_armor(item_type: ItemType) -> bool: @@ -63,10 +65,7 @@ def is_consumable(item_type: ItemType) -> bool: def is_mapping(item_type: ItemType) -> bool: - return item_type in [ - ItemType.Compass, - ItemType.Sigil, - ] + return item_type in [ItemType.Compass, ItemType.Sigil, ItemType.WhisperingWood] def is_jewelry(item_type: ItemType) -> bool: diff --git a/src/item/descr/read_descr_tts.py b/src/item/descr/read_descr_tts.py index 0f63b7fb..dbba2ba7 100644 --- a/src/item/descr/read_descr_tts.py +++ b/src/item/descr/read_descr_tts.py @@ -123,6 +123,10 @@ def _create_base_item_from_tts(tts_item: list[str]) -> Item | None: return Item(item_type=ItemType.Material) if any(tts_item[1].lower().endswith(x) for x in ["gem"]): return Item(item_type=ItemType.Gem) + if any(tts_item[1].lower().endswith(x) for x in ["cache"]): + return Item(item_type=ItemType.Cache) + if any(tts_item[1].lower().endswith(x) for x in ["whispering wood"]): + return Item(item_type=ItemType.WhisperingWood) if "rune of" in tts_item[1].lower(): item = Item(item_type=ItemType.Rune) search_string_split = tts_item[1].lower().split(" rune of ") @@ -232,7 +236,10 @@ def _get_item_type(data: str): def _is_codex_upgrade(tts_section: list[str], item: Item) -> bool: - return any("upgrades an aspect in the codex of power on salvage" in line.lower() for line in tts_section) + for line in tts_section: + if "upgrades an aspect in the codex of power on salvage" in line.lower() or "unlocks new aspect" in line.lower(): + return True + return False def read_descr_mixed(img_item_descr: np.ndarray) -> Item | None: @@ -320,7 +327,7 @@ def read_descr() -> Item | None: is_consumable(item.item_type), is_mapping(item.item_type), is_socketable(item.item_type), - item.item_type in [ItemType.Material, ItemType.Tribute], + item.item_type in [ItemType.Material, ItemType.Tribute, ItemType.Cache], ] ): return item diff --git a/src/scripts/loot_filter_tts.py b/src/scripts/loot_filter_tts.py index 28054f8c..ab23e559 100644 --- a/src/scripts/loot_filter_tts.py +++ b/src/scripts/loot_filter_tts.py @@ -16,11 +16,11 @@ def check_items(inv: InventoryBase, force_refresh: ItemRefreshType): - occupied, empty = inv.get_item_slots() + occupied, _ = inv.get_item_slots() if force_refresh == ItemRefreshType.force_with_filter or force_refresh == ItemRefreshType.force_without_filter: reset_item_status(occupied, inv) - occupied, empty = inv.get_item_slots() + occupied, _ = inv.get_item_slots() if force_refresh == ItemRefreshType.force_without_filter: return @@ -28,12 +28,11 @@ def check_items(inv: InventoryBase, force_refresh: ItemRefreshType): num_fav = sum(1 for slot in occupied if slot.is_fav) num_junk = sum(1 for slot in occupied if slot.is_junk) LOGGER.info(f"Items: {len(occupied)} (favorite: {num_fav}, junk: {num_junk}) in {inv.menu_name}") - previous_item = occupied[1] if len(occupied) > 1 else empty[0] for item in occupied: if item.is_junk or item.is_fav: continue inv.hover_item(item) - time.sleep(0.15) + time.sleep(0.10) img = Cam().grab() item_descr = None item_descr_previous_check = None @@ -49,9 +48,8 @@ def check_items(inv: InventoryBase, force_refresh: ItemRefreshType): # Check again to make sure the item is what we think. # Move off of the item then back on again inv.hover_left_of_item(item) - time.sleep(0.05) inv.hover_item(item) - time.sleep(0.15) + time.sleep(0.10) try: item_descr = src.item.descr.read_descr_tts.read_descr() LOGGER.debug(f"Parsed item based on TTS: {item_descr}") @@ -66,8 +64,6 @@ def check_items(inv: InventoryBase, force_refresh: ItemRefreshType): if item_descr is None: continue - previous_item = item - # Hardcoded filters if item_descr.rarity == ItemRarity.Common and item_descr.item_type == ItemType.Material: LOGGER.info("Matched: Material") From 33c516c8d4a77fc3af2d6f523667a0c682a9b8f9 Mon Sep 17 00:00:00 2001 From: CJ Shrader Date: Thu, 30 Jan 2025 08:32:25 -0500 Subject: [PATCH 5/5] - TTS vision mode is no longer in a thread and now properly stops and starts --- src/scripts/handler.py | 59 +++++++++++++++++++++------------- src/scripts/vision_mode.py | 2 +- src/scripts/vision_mode_tts.py | 14 +++++++- 3 files changed, 50 insertions(+), 25 deletions(-) diff --git a/src/scripts/handler.py b/src/scripts/handler.py index 3660d0c7..a6bca0fe 100644 --- a/src/scripts/handler.py +++ b/src/scripts/handler.py @@ -31,6 +31,7 @@ class ScriptHandler: def __init__(self): self.loot_interaction_thread = None self.script_threads = [] + self.vision_mode = src.scripts.vision_mode_tts.VisionMode() self.setup_key_binds() if IniConfigLoader().general.run_vision_mode_on_startup: @@ -71,6 +72,8 @@ def _start_or_stop_loot_interaction_thread(self, loot_interaction_method: typing LOGGER.info("Stopping filter or move process") kill_thread(self.loot_interaction_thread) self.loot_interaction_thread = None + if self.did_stop_scripts and IniConfigLoader().general.use_tts == UseTTSType.full and not self.vision_mode.running(): + self.vision_mode.start() else: self.loot_interaction_thread = threading.Thread( target=self._wrapper_run_loot_interaction_method, args=(loot_interaction_method, method_args), daemon=True @@ -84,17 +87,22 @@ def _start_or_stop_loot_interaction_thread(self, loot_interaction_method: typing def _wrapper_run_loot_interaction_method(self, loot_interaction_method: typing.Callable, method_args=()): try: # We will stop all scripts if they are currently running and restart them afterward if needed - did_stop_scripts = False - if len(self.script_threads) > 0: - LOGGER.info("Stopping Scripts") - for script_thread in self.script_threads: - kill_thread(script_thread) - self.script_threads = [] - did_stop_scripts = True + self.did_stop_scripts = False + if IniConfigLoader().general.use_tts == UseTTSType.full: + if self.vision_mode.running(): + self.vision_mode.stop() + self.did_stop_scripts = True + else: + if len(self.script_threads) > 0: + LOGGER.info("Stopping Scripts") + for script_thread in self.script_threads: + kill_thread(script_thread) + self.script_threads = [] + self.did_stop_scripts = True loot_interaction_method(*method_args) - if did_stop_scripts: + if self.did_stop_scripts: self.run_scripts() finally: self.loot_interaction_thread = None @@ -102,23 +110,28 @@ def _wrapper_run_loot_interaction_method(self, loot_interaction_method: typing.C def run_scripts(self): if LOCK.acquire(blocking=False): try: - if len(self.script_threads) > 0: - LOGGER.info("Stopping Vision Mode") - for script_thread in self.script_threads: - kill_thread(script_thread) - self.script_threads = [] + if not IniConfigLoader().advanced_options.scripts: + LOGGER.info("No scripts configured") + return + + # TODO Probably just remove the "scripts" concept and change to a checkbox for vision mode + if IniConfigLoader().general.use_tts == UseTTSType.full: + if self.vision_mode.running(): + self.vision_mode.stop() + else: + self.vision_mode.start() else: - if not IniConfigLoader().advanced_options.scripts: - LOGGER.info("No scripts configured") - return - for name in IniConfigLoader().advanced_options.scripts: - if name == "vision_mode": - if IniConfigLoader().general.use_tts == UseTTSType.full: - vision_mode_thread = threading.Thread(target=src.scripts.vision_mode_tts.VisionMode().start, daemon=True) - else: + if len(self.script_threads) > 0: + LOGGER.info("Stopping Vision Mode") + for script_thread in self.script_threads: + kill_thread(script_thread) + self.script_threads = [] + else: + for name in IniConfigLoader().advanced_options.scripts: + if name == "vision_mode": vision_mode_thread = threading.Thread(target=src.scripts.vision_mode.vision_mode, daemon=True) - vision_mode_thread.start() - self.script_threads.append(vision_mode_thread) + vision_mode_thread.start() + self.script_threads.append(vision_mode_thread) finally: LOCK.release() else: diff --git a/src/scripts/vision_mode.py b/src/scripts/vision_mode.py index b332b653..6dbc0f3c 100644 --- a/src/scripts/vision_mode.py +++ b/src/scripts/vision_mode.py @@ -118,7 +118,7 @@ def vision_mode(): canvas = tk.Canvas(root, bg="black", highlightthickness=0) canvas.pack(fill=tk.BOTH, expand=True) - LOGGER.info("Starting Vision Filter") + LOGGER.info("Starting Vision Mode") inv = CharInventory() chest = Chest() img = Cam().grab() diff --git a/src/scripts/vision_mode_tts.py b/src/scripts/vision_mode_tts.py index 4a43bf9c..0988fc7e 100644 --- a/src/scripts/vision_mode_tts.py +++ b/src/scripts/vision_mode_tts.py @@ -37,6 +37,8 @@ def __init__(self): self.clear_timer_id = None self.queue = queue.Queue() self.draw_from_queue() + self.stop_thread = None + self.is_running = False def adjust_textbox_size(self): self.textbox.config(state=tk.NORMAL) @@ -157,8 +159,18 @@ def on_tts(self, _): LOGGER.exception("Error in vision mode. Please create a bug report") def start(self): - LOGGER.info("Starting Vision Filter") + LOGGER.info("Starting Vision Mode") Publisher().subscribe(self.on_tts) + self.is_running = True + + def stop(self): + LOGGER.info("Stopping Vision Mode") + self.request_clear() + Publisher().unsubscribe(self.on_tts) + self.is_running = False + + def running(self): + return self.is_running def create_match_text(matches: list[MatchedFilter]):