diff --git a/assets/character/Aventurine.png b/assets/character/Aventurine.png new file mode 100644 index 000000000..2e1c1de45 Binary files /dev/null and b/assets/character/Aventurine.png differ diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 61de75e65..7e5fc8b74 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -427,6 +427,7 @@ "Argenti", "Arlan", "Asta", + "Aventurine", "Bailu", "BlackSwan", "Blade", @@ -1341,6 +1342,7 @@ "Argenti", "Arlan", "Asta", + "Aventurine", "Bailu", "BlackSwan", "Blade", diff --git a/module/config/config.py b/module/config/config.py index 6ba4e6d5a..26e54e287 100644 --- a/module/config/config.py +++ b/module/config/config.py @@ -176,6 +176,10 @@ def close_game(self): self.data, keys="Alas.Optimization.CloseGameDuringWait", default=False ) + @property + def is_actual_task(self): + return self.task.command.lower() not in ['alas', 'template'] + @property def is_cloud_game(self): return deep_get( diff --git a/module/config/config_generated.py b/module/config/config_generated.py index 31363dddb..54781a597 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -59,7 +59,7 @@ class GeneratedConfig: # Group `DungeonSupport` DungeonSupport_Use = 'when_daily' # always_use, when_daily, do_not_use - DungeonSupport_Character = 'FirstCharacter' # FirstCharacter, Acheron, Argenti, Arlan, Asta, Bailu, BlackSwan, Blade, Bronya, Clara, DanHeng, DanHengImbibitorLunae, DrRatio, FuXuan, Gallagher, Gepard, Guinaifen, Hanya, Herta, Himeko, Hook, Huohuo, JingYuan, Jingliu, Kafka, Luka, Luocha, Lynx, March7th, Misha, Natasha, Pela, Qingque, RuanMei, Sampo, Seele, Serval, SilverWolf, Sparkle, Sushang, Tingyun, TopazNumby, TrailblazerDestruction, TrailblazerPreservation, Welt, Xueyi, Yanqing, Yukong + DungeonSupport_Character = 'FirstCharacter' # FirstCharacter, Acheron, Argenti, Arlan, Asta, Aventurine, Bailu, BlackSwan, Blade, Bronya, Clara, DanHeng, DanHengImbibitorLunae, DrRatio, FuXuan, Gallagher, Gepard, Guinaifen, Hanya, Herta, Himeko, Hook, Huohuo, JingYuan, Jingliu, Kafka, Luka, Luocha, Lynx, March7th, Misha, Natasha, Pela, Qingque, RuanMei, Sampo, Seele, Serval, SilverWolf, Sparkle, Sushang, Tingyun, TopazNumby, TrailblazerDestruction, TrailblazerPreservation, Welt, Xueyi, Yanqing, Yukong # Group `DungeonStorage` DungeonStorage_TrailblazePower = {} diff --git a/module/config/config_updater.py b/module/config/config_updater.py index 3b83ed950..b5e9c8e13 100644 --- a/module/config/config_updater.py +++ b/module/config/config_updater.py @@ -100,7 +100,7 @@ def option_add(keys, options): options=[dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Echo_of_War]) # Insert characters from tasks.character.keywords import CharacterList - unsupported_characters = ['Aventurine'] + unsupported_characters = [] characters = [character.name for character in CharacterList.instances.values() if character.name not in unsupported_characters] option_add(keys='DungeonSupport.Character.option', options=characters) diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index a062ac5ff..4639d1ff6 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -441,6 +441,7 @@ "Argenti": "Argenti", "Arlan": "Arlan", "Asta": "Asta", + "Aventurine": "Aventurine", "Bailu": "Bailu", "BlackSwan": "Black Swan", "Blade": "Blade", diff --git a/module/config/i18n/es-ES.json b/module/config/i18n/es-ES.json index 6130a4cce..d3c378f0c 100644 --- a/module/config/i18n/es-ES.json +++ b/module/config/i18n/es-ES.json @@ -441,6 +441,7 @@ "Argenti": "Argenti", "Arlan": "Arlan", "Asta": "Asta", + "Aventurine": "Aventurino", "Bailu": "Bailu", "BlackSwan": "Cisne Negro", "Blade": "Blade", diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index b2ff361a2..00da6dd94 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -441,6 +441,7 @@ "Argenti": "アルジェンティ", "Arlan": "アーラン", "Asta": "アスター", + "Aventurine": "アベンチュリン", "Bailu": "白露", "BlackSwan": "ブラックスワン", "Blade": "刃", diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index cf3a86232..e93f56d34 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -441,6 +441,7 @@ "Argenti": "银枝", "Arlan": "阿兰", "Asta": "艾丝妲", + "Aventurine": "砂金", "Bailu": "白露", "BlackSwan": "黑天鹅", "Blade": "刃", diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index a9afefdfb..75d160610 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -441,6 +441,7 @@ "Argenti": "銀枝", "Arlan": "阿蘭", "Asta": "艾絲妲", + "Aventurine": "砂金", "Bailu": "白露", "BlackSwan": "黑天鵝", "Blade": "刃", diff --git a/module/device/device.py b/module/device/device.py index 948fefe03..d9cdbf1f8 100644 --- a/module/device/device.py +++ b/module/device/device.py @@ -1,6 +1,12 @@ import collections import itertools +# Patch pkg_resources before importing adbutils and uiautomator2 +from module.device.pkg_resources import get_distribution + +# Just avoid being removed by import optimization +_ = get_distribution + from module.base.timer import Timer from module.device.app_control import AppControl from module.device.control import Control @@ -88,6 +94,13 @@ def __init__(self, *args, **kwargs): if not self.config.is_template_config and self.config.Emulator_ScreenshotMethod == 'auto': self.run_simple_screenshot_benchmark() + # Early init + if self.config.is_actual_task: + if self.config.Emulator_ControlMethod == 'MaaTouch': + self.early_maatouch_init() + if self.config.Emulator_ControlMethod == 'minitouch': + self.early_minitouch_init() + # SRC only, use nemu_ipc if available available = self.nemu_ipc_available() logger.attr('nemu_ipc_available', available) diff --git a/module/device/method/maatouch.py b/module/device/method/maatouch.py index 8122505bc..d1a97d04b 100644 --- a/module/device/method/maatouch.py +++ b/module/device/method/maatouch.py @@ -1,14 +1,15 @@ import socket +import threading from functools import wraps from adbutils.errors import AdbError -from module.base.decorator import cached_property, del_cached_property +from module.base.decorator import cached_property, del_cached_property, has_cached_property from module.base.timer import Timer from module.base.utils import * from module.device.connection import Connection from module.device.method.minitouch import CommandBuilder, insert_swipe -from module.device.method.utils import RETRY_TRIES, retry_sleep, handle_adb_error +from module.device.method.utils import RETRY_TRIES, handle_adb_error, retry_sleep from module.exception import RequestHumanTakeover from module.logger import logger @@ -36,20 +37,20 @@ def retry_wrapper(self, *args, **kwargs): def init(): self.adb_reconnect() - del_cached_property(self, 'maatouch_builder') + del_cached_property(self, '_maatouch_builder') # Emulator closed except ConnectionAbortedError as e: logger.error(e) def init(): self.adb_reconnect() - del_cached_property(self, 'maatouch_builder') + del_cached_property(self, '_maatouch_builder') # AdbError except AdbError as e: if handle_adb_error(e): def init(): self.adb_reconnect() - del_cached_property(self, 'maatouch_builder') + del_cached_property(self, '_maatouch_builder') else: break # MaaTouchNotInstalledError: Received "Aborted" from MaaTouch @@ -58,12 +59,12 @@ def init(): def init(): self.maatouch_install() - del_cached_property(self, 'maatouch_builder') + del_cached_property(self, '_maatouch_builder') except BrokenPipeError as e: logger.error(e) def init(): - del_cached_property(self, 'maatouch_builder') + del_cached_property(self, '_maatouch_builder') # Unknown, probably a trucked image except Exception as e: logger.exception(e) @@ -103,12 +104,39 @@ class MaaTouch(Connection): max_y: int _maatouch_stream = socket.socket _maatouch_stream_storage = None + _maatouch_init_thread = None @cached_property - def maatouch_builder(self): + @retry + def _maatouch_builder(self): self.maatouch_init() return MaatouchBuilder(self) + @property + def maatouch_builder(self): + # Wait init thread + if self._maatouch_init_thread is not None: + self._maatouch_init_thread.join() + del self._maatouch_init_thread + self._maatouch_init_thread = None + + return self._maatouch_builder + + def early_maatouch_init(self): + """ + Start a thread to init maatouch connection while the Alas instance just starting to take screenshots + This would speed up the first click 0.2 ~ 0.4s. + """ + if has_cached_property(self, '_maatouch_builder'): + return + + def early_maatouch_init_func(): + _ = self._maatouch_builder + + thread = threading.Thread(target=early_maatouch_init_func, daemon=True) + self._maatouch_init_thread = thread + thread.start() + def maatouch_init(self): logger.hr('MaaTouch init') max_x, max_y = 1280, 720 @@ -245,3 +273,8 @@ def drag_maatouch(self, p1, p2, point_random=(-10, -10, 10, 10)): builder.up().commit() builder.send() + + +if __name__ == '__main__': + self = MaaTouch('src') + self.maatouch_uninstall() \ No newline at end of file diff --git a/module/device/method/minitouch.py b/module/device/method/minitouch.py index 405bc0299..339c17cc1 100644 --- a/module/device/method/minitouch.py +++ b/module/device/method/minitouch.py @@ -1,7 +1,7 @@ import asyncio import json -import re import socket +import threading import time from functools import wraps from typing import List @@ -10,11 +10,11 @@ from adbutils.errors import AdbError from uiautomator2 import _Service -from module.base.decorator import Config, cached_property, del_cached_property +from module.base.decorator import Config, cached_property, del_cached_property, has_cached_property from module.base.timer import Timer from module.base.utils import * from module.device.connection import Connection -from module.device.method.utils import RETRY_TRIES, retry_sleep, handle_adb_error +from module.device.method.utils import RETRY_TRIES, handle_adb_error, retry_sleep from module.exception import RequestHumanTakeover, ScriptError from module.logger import logger @@ -328,7 +328,7 @@ def init(): self.install_uiautomator2() if self._minitouch_port: self.adb_forward_remove(f'tcp:{self._minitouch_port}') - del_cached_property(self, 'minitouch_builder') + del_cached_property(self, '_minitouch_builder') # MinitouchOccupiedError: Timeout when connecting to minitouch except MinitouchOccupiedError as e: logger.error(e) @@ -337,7 +337,7 @@ def init(): self.restart_atx() if self._minitouch_port: self.adb_forward_remove(f'tcp:{self._minitouch_port}') - del_cached_property(self, 'minitouch_builder') + del_cached_property(self, '_minitouch_builder') # AdbError except AdbError as e: if handle_adb_error(e): @@ -349,7 +349,7 @@ def init(): logger.error(e) def init(): - del_cached_property(self, 'minitouch_builder') + del_cached_property(self, '_minitouch_builder') # Unknown, probably a trucked image except Exception as e: logger.exception(e) @@ -370,12 +370,39 @@ class Minitouch(Connection): _minitouch_ws: websockets.WebSocketClientProtocol max_x: int max_y: int + _minitouch_init_thread = None @cached_property - def minitouch_builder(self): + @retry + def _minitouch_builder(self): self.minitouch_init() return CommandBuilder(self) + @property + def minitouch_builder(self): + # Wait init thread + if self._minitouch_init_thread is not None: + self._minitouch_init_thread.join() + del self._minitouch_init_thread + self._minitouch_init_thread = None + + return self._minitouch_builder + + def early_minitouch_init(self): + """ + Start a thread to init minitouch connection while the Alas instance just starting to take screenshots + This would speed up the first click 0.05s. + """ + if has_cached_property(self, '_minitouch_builder'): + return + + def early_minitouch_init_func(): + _ = self._minitouch_builder + + thread = threading.Thread(target=early_minitouch_init_func, daemon=True) + self._minitouch_init_thread = thread + thread.start() + @Config.when(DEVICE_OVER_HTTP=False) def minitouch_init(self): logger.hr('MiniTouch init') diff --git a/module/device/method/utils.py b/module/device/method/utils.py index 36d6bbd49..50b7ecc70 100644 --- a/module/device/method/utils.py +++ b/module/device/method/utils.py @@ -253,7 +253,7 @@ def remove_suffix(s, suffix): Returns: str, bytes: """ - return s[:len(suffix)] if s.endswith(suffix) else s + return s[:-len(suffix)] if s.endswith(suffix) else s def remove_shell_warning(s): diff --git a/module/device/pkg_resources/__init__.py b/module/device/pkg_resources/__init__.py new file mode 100644 index 000000000..b685a6d9a --- /dev/null +++ b/module/device/pkg_resources/__init__.py @@ -0,0 +1,109 @@ +import os +import re +import sys + +from module.base.decorator import cached_property +from module.logger import logger + +""" +Importing pkg_resources is so slow, like 0.4 ~ 1.0s, just google it you will find it indeed really slow. +Since it was some kind of standard library there is no way to modify it or speed it up. +So here's a poor but fast implementation of pkg_resources returning the things in need. + +To patch: +``` +# Patch pkg_resources before importing adbutils and uiautomator2 +from module.device.pkg_resources import get_distribution +# Just avoid being removed by import optimization +_ = get_distribution +``` +""" +# Inject sys.modules, pretend we have pkg_resources imported +try: + sys.modules['pkg_resources'] = sys.modules['module.device.pkg_resources'] +except KeyError: + logger.error('Patch pkg_resources failed, patch module does not exists') + + +def remove_suffix(s, suffix): + """ + Remove suffix of a string or bytes like `string.removesuffix(suffix)`, which is on Python3.9+ + + Args: + s (str, bytes): + suffix (str, bytes): + + Returns: + str, bytes: + """ + return s[:-len(suffix)] if s.endswith(suffix) else s + + +class FakeDistributionObject: + def __init__(self, dist, version): + self.dist = dist + self.version = version + + def __str__(self): + return f'{self.__class__.__name__}({self.dist}={self.version})' + + __repr__ = __str__ + + +class PackageCache: + @cached_property + def site_packages(self): + # Just whatever library to locate the `site-packages` directory + import requests + path = os.path.abspath(os.path.join(requests.__file__, '../../')) + return path + + @cached_property + def dict_installed_packages(self): + """ + Returns: + dict: Key: str, package name + Value: FakeDistributionObject + """ + dic = {} + for file in os.listdir(self.site_packages): + # mxnet_cu101-1.6.0.dist-info + # adbutils-0.11.0-py3.7.egg-info + res = re.match(r'^([a-zA-Z0-9._]+)-([a-zA-Z0-9._]+)-', file) + if res: + version = remove_suffix(res.group(2), '.dist') + # version = res.group(2) + obj = FakeDistributionObject( + dist=res.group(1), + version=version, + ) + dic[obj.dist] = obj + + return dic + + +PACKAGE_CACHE = PackageCache() + + +def resource_filename(*args): + if args == ("adbutils", "binaries"): + path = os.path.abspath(os.path.join(PACKAGE_CACHE.site_packages, *args)) + return path + + +def get_distribution(dist): + """Return a current distribution object for a Requirement or string""" + if dist == 'adbutils': + return PACKAGE_CACHE.dict_installed_packages.get( + 'adbutils', + FakeDistributionObject('adbutils', '0.11.0'), + ) + if dist == 'uiautomator2': + return PACKAGE_CACHE.dict_installed_packages.get( + 'uiautomator2', + FakeDistributionObject('uiautomator2', '2.16.17'), + ) + + +class DistributionNotFound(Exception): + pass diff --git a/module/notify.py b/module/notify.py index 4f8b470ac..9186e5c4a 100644 --- a/module/notify.py +++ b/module/notify.py @@ -1,4 +1,75 @@ +import onepush.core +import yaml +from onepush import get_notifier +from onepush.core import Provider +from onepush.exceptions import OnePushException +from onepush.providers.custom import Custom +from requests import Response + from module.logger import logger -def handle_notify(*args, **kwargs): - logger.error('Error notify is not supported yet') +onepush.core.log = logger + + +def handle_notify(_config: str, **kwargs) -> bool: + try: + config = {} + for item in yaml.safe_load_all(_config): + config.update(item) + except Exception: + logger.error("Fail to load onepush config, skip sending") + return False + try: + provider_name: str = config.pop("provider", None) + if provider_name is None: + logger.info("No provider specified, skip sending") + return False + notifier: Provider = get_notifier(provider_name) + required: list[str] = notifier.params["required"] + config.update(kwargs) + + # pre check + for key in required: + if key not in config: + logger.warning( + f"Notifier {notifier.name} require param '{key}' but not provided" + ) + + if isinstance(notifier, Custom): + if "method" not in config or config["method"] == "post": + config["datatype"] = "json" + if not ("data" in config or isinstance(config["data"], dict)): + config["data"] = {} + if "title" in kwargs: + config["data"]["title"] = kwargs["title"] + if "content" in kwargs: + config["data"]["content"] = kwargs["content"] + + if provider_name.lower() == "gocqhttp": + access_token = config.get("access_token") + if access_token: + config["token"] = access_token + + resp = notifier.notify(**config) + if isinstance(resp, Response): + if resp.status_code != 200: + logger.warning("Push notify failed!") + logger.warning(f"HTTP Code:{resp.status_code}") + return False + else: + if provider_name.lower() == "gocqhttp": + return_data: dict = resp.json() + if return_data["status"] == "failed": + logger.warning("Push notify failed!") + logger.warning( + f"Return message:{return_data['wording']}") + return False + except OnePushException: + logger.exception("Push notify failed") + return False + except Exception as e: + logger.exception(e) + return False + + logger.info("Push notify success") + return True \ No newline at end of file diff --git a/requirements-in.txt b/requirements-in.txt index a518216e4..656bef808 100644 --- a/requirements-in.txt +++ b/requirements-in.txt @@ -21,6 +21,7 @@ pyyaml inflection prettytable==2.2.1 pydantic>=2.4 +onepush==1.3.0 # OCR pponnxcr==2.0 diff --git a/requirements.txt b/requirements.txt index 21042b646..0c79b502b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,6 +37,7 @@ markdown-it-py==2.2.0 # via rich mdurl==0.1.2 # via markdown-it-py mpmath==1.3.0 # via sympy numpy==1.24.3 # via -r requirements-in.txt, onnxruntime, opencv-python, pponnxcr, scipy, shapely +onepush==1.3.0 # via -r requirements-in.txt onnxruntime==1.14.1 # via pponnxcr opencv-python==4.7.0.72 # via -r requirements-in.txt, pponnxcr packaging==20.9 # via deprecation, onnxruntime, uiautomator2 @@ -48,6 +49,7 @@ protobuf==4.23.0 # via onnxruntime psutil==5.9.3 # via -r requirements-in.txt py==1.11.0 # via retry pyclipper==1.3.0.post4 # via pponnxcr +pycryptodome==3.20.0 # via onepush pydantic==2.4.2 # via -r requirements-in.txt pydantic-core==2.10.1 # via pydantic pyelftools==0.29 # via apkutils2 @@ -58,7 +60,7 @@ pyreadline3==3.4.1 # via humanfriendly python-dotenv==1.0.0 # via uvicorn pywebio==1.8.3 # via -r requirements-in.txt pyyaml==6.0 # via -r requirements-in.txt, uvicorn -requests==2.30.0 # via adbutils, uiautomator2 +requests==2.30.0 # via adbutils, onepush, uiautomator2 retry==0.9.2 # via adbutils, uiautomator2 rich==13.3.5 # via -r requirements-in.txt scipy==1.10.1 # via -r requirements-in.txt diff --git a/route/daily/HimekoTrial.py b/route/daily/HimekoTrial.py index d5f77b433..8aee25967 100644 --- a/route/daily/HimekoTrial.py +++ b/route/daily/HimekoTrial.py @@ -16,7 +16,13 @@ def handle_combat_state(self, auto=True, speed_2x=True): def wait_next_skill(self, expected_end=None, skip_first_screenshot=True): # Ended at START_TRIAL def combat_end(): - return self.match_template_color(START_TRIAL) + if self.match_template_color(START_TRIAL): + logger.info('Trial ended at START_TRIAL') + return True + if self.is_in_main(): + logger.warning('Trial ended at is_in_main()') + return True + return False return super().wait_next_skill(expected_end=combat_end, skip_first_screenshot=skip_first_screenshot) diff --git a/route/rogue/Occurrence/Luofu_Cloudford_F1.py b/route/rogue/Occurrence/Luofu_Cloudford_F1.py index c57a7ea90..5930d0de9 100644 --- a/route/rogue/Occurrence/Luofu_Cloudford_F1.py +++ b/route/rogue/Occurrence/Luofu_Cloudford_F1.py @@ -27,6 +27,32 @@ def Luofu_Cloudford_F1_X241Y947(self): self.clear_event(event) # ===== End of generated waypoints ===== + @locked_rotation(270) + def Luofu_Cloudford_F1_X244Y951(self): + """ + | Waypoint | Position | Direction | Rotation | + | -------- | ------------------------- | --------- | -------- | + | spawn | Waypoint((241.4, 947.5)), | 274.2 | 274 | + | event | Waypoint((199.0, 940.8)), | 300.1 | 294 | + | exit_ | Waypoint((193.1, 947.2)), | 12.8 | 274 | + | exit1 | Waypoint((179.0, 956.4)), | 279.8 | 278 | + | exit2 | Waypoint((184.1, 940.2)), | 282.9 | 278 | + """ + self.map_init(plane=Luofu_Cloudford, floor="F1", position=(244, 951)) + self.register_domain_exit( + Waypoint((193.1, 947.2)), end_rotation=274, + left_door=Waypoint((179.0, 956.4)), right_door=Waypoint((184.1, 940.2))) + event = Waypoint((199.0, 940.8)) + + self.clear_event(event) + # ===== End of generated waypoints ===== + + """ + Notes + Luofu_Cloudford_F1_X244Y951 is the same as Luofu_Cloudford_F1_X241Y947 + but for wrong spawn point detected + """ + @locked_position @locked_rotation(0) def Luofu_Cloudford_F1_X281Y873(self): diff --git a/route/rogue/Occurrence/Luofu_StargazerNavalia_F1.py b/route/rogue/Occurrence/Luofu_StargazerNavalia_F1.py index 3e1af7a7a..fa8f6f8fd 100644 --- a/route/rogue/Occurrence/Luofu_StargazerNavalia_F1.py +++ b/route/rogue/Occurrence/Luofu_StargazerNavalia_F1.py @@ -28,3 +28,8 @@ def Luofu_StargazerNavalia_F1_X521Y595(self): self.clear_item(item_X504Y610) self.clear_event(event_X510Y626) # ===== End of generated waypoints ===== + + def clear_event(self, *waypoints): + # Too many clicks on A_BUTTON, so no items enroute in Luofu_StargazerNavalia_F1_X521Y595 + self.enroute_add_item = False + return super().clear_event(*waypoints) diff --git a/route/rogue/route.json b/route/rogue/route.json index 198c9be88..8df482bc9 100644 --- a/route/rogue/route.json +++ b/route/rogue/route.json @@ -1781,6 +1781,17 @@ ], "domain": "Occurrence" }, + { + "name": "Occurrence_Luofu_Cloudford_F1_X244Y951", + "route": "route.rogue.Occurrence.Luofu_Cloudford_F1:Luofu_Cloudford_F1_X244Y951", + "plane": "Luofu_Cloudford", + "floor": "F1", + "position": [ + 244.0, + 951.0 + ], + "domain": "Occurrence" + }, { "name": "Occurrence_Luofu_Cloudford_F1_X281Y873", "route": "route.rogue.Occurrence.Luofu_Cloudford_F1:Luofu_Cloudford_F1_X281Y873", diff --git a/tasks/combat/skill.py b/tasks/combat/skill.py index 35e8c05fa..081c9b44d 100644 --- a/tasks/combat/skill.py +++ b/tasks/combat/skill.py @@ -13,7 +13,7 @@ def is_in_skill(self) -> bool: if not self.appear(IN_SKILL): return False - if not self.image_color_count(IN_SKILL, color=(255, 255, 255), threshold=221, count=50): + if not self.image_color_count(IN_SKILL, color=(255, 255, 255), threshold=180, count=50): return False return True @@ -51,6 +51,10 @@ def _skill_click(self, button, skip_first_screenshot=True): logger.info(f'Skill used: {button} (icon changed)') break + if self.is_in_main(): + logger.warning('_skill_click ended at is_in_main') + break + def _is_skill_active(self, button): flag = self.image_color_count(button, color=(220, 196, 145), threshold=221, count=50) return flag diff --git a/tasks/map/control/waypoint.py b/tasks/map/control/waypoint.py index 829a4c05f..0a058a463 100644 --- a/tasks/map/control/waypoint.py +++ b/tasks/map/control/waypoint.py @@ -116,6 +116,10 @@ def match_results(self, results) -> list[str]: return list(same) + def enroute_add_item(self): + if 'item' not in self.expected_enroute: + self.expected_enroute.append('item') + def ensure_waypoint(point) -> Waypoint: """ diff --git a/tasks/rogue/route/base.py b/tasks/rogue/route/base.py index 2126852a3..d41522075 100644 --- a/tasks/rogue/route/base.py +++ b/tasks/rogue/route/base.py @@ -17,6 +17,7 @@ class RouteBase(RouteBase_, RogueExit, RogueEvent, RogueReward): registered_domain_exit = None + enroute_add_item = True def combat_expected_end(self): if self.is_page_choose_blessing(): @@ -140,10 +141,9 @@ def wait_until_minimap_stabled(self): def clear_enemy(self, *waypoints): waypoints = ensure_waypoints(waypoints) - if self.plane.is_rogue_combat: + if self.enroute_add_item and self.plane.is_rogue_combat: for point in waypoints: - if 'item' not in point.expected_enroute: - point.expected_enroute.append('item') + point.enroute_add_item() return super().clear_enemy(*waypoints) def clear_item(self, *waypoints): @@ -199,10 +199,9 @@ def clear_event(self, *waypoints): end_point.endpoint_threshold = 1.5 end_point.interact_radius = 7 end_point.expected_end.append(self._domain_event_expected_end) - if self.plane.is_rogue_occurrence: + if self.enroute_add_item and self.plane.is_rogue_occurrence: for point in waypoints: - if 'item' not in point.expected_enroute: - point.expected_enroute.append('item') + point.enroute_add_item() result = self.goto(*waypoints) self.clear_occurrence() @@ -302,9 +301,9 @@ def domain_single_exit(self, *waypoints): logger.hr('Domain single exit', level=1) waypoints = ensure_waypoints(waypoints) - for point in waypoints: - if 'item' not in point.expected_enroute: - point.expected_enroute.append('item') + if self.enroute_add_item: + for point in waypoints: + point.enroute_add_item() end_point = waypoints[-1] end_point.min_speed = 'run' diff --git a/tasks/rogue/route/exit.py b/tasks/rogue/route/exit.py index 7f385dedd..f8ee3c319 100644 --- a/tasks/rogue/route/exit.py +++ b/tasks/rogue/route/exit.py @@ -112,6 +112,9 @@ def screen2direction(point): distant_point = np.array((1509.46, 247.34)) name_y = 77.60 foot_y = 621.82 + if point < 80: + logger.warning(f'screen2direction: Point {point} to high') + point[1] = 80 door_projection_bottom = ( Points([point]).link(vanish_point).get_x(name_y)[0], @@ -129,6 +132,8 @@ def screen2direction(point): door_projection_bottom[0] - screen_middle[0], door_projection_bottom[0] - door_distant[0], ) + if planar_door[1] < 0: + logger.warning('screen2direction: planer_door at back') if abs(planar_door[0]) < 5: direction = 0 else: diff --git a/tasks/rogue/route/loader.py b/tasks/rogue/route/loader.py index b60be3d64..f482633a3 100644 --- a/tasks/rogue/route/loader.py +++ b/tasks/rogue/route/loader.py @@ -164,6 +164,12 @@ def _position_match_special( # if route.name == 'Occurrence_Herta_StorageZone_F2_X363Y166' and similarity > 0.05: # return True + # Before Combat_Herta_SupplyZone_F2_X45Y369 + if route.name in [ + 'Combat_Herta_SupplyZone_F2_X543Y255', # 0.462, (543.3, 255.4) + 'Combat_Luofu_DivinationCommission_F1_X737Y237', + ] and similarity > 0.25: + return True # Before Combat_Luofu_Cloudford_F1_X281Y873 if route.name in [ 'Occurrence_Jarilo_BackwaterPass_F1_X553Y643',