Merge pull request #604 from LmeSzinc/dev

Bug fix
This commit is contained in:
LmeSzinc 2024-08-11 02:48:20 +08:00 committed by GitHub
commit 196f39989e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
65 changed files with 775 additions and 344 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -107,6 +107,7 @@
},
"DungeonStorage": {
"TrailblazePower": {},
"Reserved": {},
"Immersifier": {},
"DungeonDouble": {},
"EchoOfWar": {},

View File

@ -715,6 +715,12 @@
"order": 1,
"color": "#eb8efe"
},
"Reserved": {
"type": "stored",
"value": {},
"display": "hide",
"stored": "StoredResersed"
},
"Immersifier": {
"type": "stored",
"value": {},

View File

@ -120,6 +120,8 @@ DungeonStorage:
stored: StoredTrailblazePower
order: 1
color: "#eb8efe"
Reserved:
stored: StoredResersed
Immersifier:
stored: StoredImmersifier
DungeonDouble:

View File

@ -662,6 +662,19 @@
"order": 0,
"color": "#777777"
},
"Reserved": {
"name": "Reserved",
"path": "Dungeon.DungeonStorage.Reserved",
"i18n": "DungeonStorage.Reserved.name",
"stored": "StoredResersed",
"attrs": {
"time": "2020-01-01 00:00:00",
"total": 8,
"value": 0
},
"order": 0,
"color": "#777777"
},
"Immersifier": {
"name": "Immersifier",
"path": "Dungeon.DungeonStorage.Immersifier",

View File

@ -57,6 +57,7 @@ class GeneratedConfig:
# Group `DungeonStorage`
DungeonStorage_TrailblazePower = {}
DungeonStorage_Reserved = {}
DungeonStorage_Immersifier = {}
DungeonStorage_DungeonDouble = {}
DungeonStorage_EchoOfWar = {}

View File

@ -445,6 +445,10 @@
"name": "Power",
"help": ""
},
"Reserved": {
"name": "Reserved Trailblaze Power",
"help": ""
},
"Immersifier": {
"name": "Immersifier",
"help": ""

View File

@ -445,6 +445,10 @@
"name": "Poder",
"help": ""
},
"Reserved": {
"name": "Trailblaze Poder Reservada",
"help": ""
},
"Immersifier": {
"name": "Inmersor",
"help": ""

View File

@ -445,6 +445,10 @@
"name": "DungeonStorage.TrailblazePower.name",
"help": "DungeonStorage.TrailblazePower.help"
},
"Reserved": {
"name": "DungeonStorage.Reserved.name",
"help": "DungeonStorage.Reserved.help"
},
"Immersifier": {
"name": "DungeonStorage.Immersifier.name",
"help": "DungeonStorage.Immersifier.help"

View File

@ -445,6 +445,10 @@
"name": "开拓力",
"help": ""
},
"Reserved": {
"name": "后备开拓力",
"help": ""
},
"Immersifier": {
"name": "沉浸器",
"help": ""

View File

@ -445,6 +445,10 @@
"name": "開拓力",
"help": ""
},
"Reserved": {
"name": "后备開拓力",
"help": ""
},
"Immersifier": {
"name": "沉浸器",
"help": ""

View File

@ -206,6 +206,10 @@ class StoredTrailblazePower(StoredCounter):
return value
class StoredResersed(StoredCounter):
FIXED_TOTAL = 2400
class StoredImmersifier(StoredCounter):
FIXED_TOTAL = 8

View File

@ -22,6 +22,7 @@ from module.config.stored.classes import (
StoredInt,
StoredPlanner,
StoredPlannerOverall,
StoredResersed,
StoredSimulatedUniverse,
StoredSimulatedUniverseElite,
StoredTrailblazePower,
@ -85,6 +86,7 @@ class StoredGenerated:
Item_Dream_Making_Engine = StoredPlanner("Dungeon.Planner.Item_Dream_Making_Engine")
Item_Shards_of_Desires = StoredPlanner("Dungeon.Planner.Item_Shards_of_Desires")
TrailblazePower = StoredTrailblazePower("Dungeon.DungeonStorage.TrailblazePower")
Reserved = StoredResersed("Dungeon.DungeonStorage.Reserved")
Immersifier = StoredImmersifier("Dungeon.DungeonStorage.Immersifier")
DungeonDouble = StoredDungeonDouble("Dungeon.DungeonStorage.DungeonDouble")
EchoOfWar = StoredEchoOfWar("Dungeon.DungeonStorage.EchoOfWar")

View File

@ -271,6 +271,7 @@ class Connection(ConnectionAttr):
return self.adb_shell(['getprop', name]).strip()
@cached_property
@retry
def cpu_abi(self) -> str:
"""
Returns:
@ -282,6 +283,7 @@ class Connection(ConnectionAttr):
return abi
@cached_property
@retry
def sdk_ver(self) -> int:
"""
Android SDK/API levels, see https://apilevels.com/
@ -295,6 +297,7 @@ class Connection(ConnectionAttr):
return 0
@cached_property
@retry
def is_avd(self):
if get_serial_pair(self.serial)[0] is None:
return False
@ -305,12 +308,35 @@ class Connection(ConnectionAttr):
return False
@cached_property
@retry
def is_waydroid(self):
res = self.adb_getprop('ro.product.brand')
logger.attr('ro.product.brand', res)
return 'waydroid' in res.lower()
@cached_property
@retry
def nemud_app_keep_alive(self) -> str:
res = self.adb_getprop('nemud.app_keep_alive')
logger.attr('nemud.app_keep_alive', res)
return res
@cached_property
@retry
def nemud_player_version(self) -> str:
# [nemud.player_product_version]: [3.8.27.2950]
res = self.adb_getprop('nemud.player_version')
logger.attr('nemud.player_version', res)
return res
@cached_property
@retry
def nemud_player_engine(self) -> str:
# NEMUX or MACPRO
res = self.adb_getprop('nemud.player_engine')
logger.attr('nemud.player_engine', res)
return res
def check_mumu_app_keep_alive(self):
if not self.is_mumu_family:
return False
@ -340,12 +366,13 @@ class Connection(ConnectionAttr):
"""
if not self.is_mumu_family:
return False
# >= 4.0 has no info in getprop
if self.nemud_player_version == '':
return True
if self.nemud_app_keep_alive != '':
return True
if IS_MACINTOSH:
res = self.adb_getprop('nemud.player_engine')
logger.attr('nemud.player_engine', res)
if 'MACPRO' in res:
if 'MACPRO' in self.nemud_player_engine:
return True
return False
@ -665,13 +692,9 @@ class Connection(ConnectionAttr):
# Brute force connect nearby ports to handle serial switches
if self.is_mumu12_family:
before = self.serial
for port_offset in [1, -1, 2, -2]:
port = self.port + port_offset
serial = self.serial.replace(str(self.port), str(port))
msg = self.adb_client.connect(serial)
logger.info(msg)
if 'connected' in msg:
break
serial_list = [self.serial.replace(str(self.port), str(self.port + offset))
for offset in [1, -1, 2, -2]]
self.adb_brute_force_connect(serial_list)
self.detect_device()
if self.serial != before:
return True
@ -684,6 +707,25 @@ class Connection(ConnectionAttr):
self.detect_device()
return False
def adb_brute_force_connect(self, serial_list):
"""
Args:
serial_list (list[str]):
"""
import asyncio
ev = asyncio.new_event_loop()
def _connect(serial):
msg = self.adb_client.connect(serial)
logger.info(msg)
return msg
async def connect():
tasks = [ev.run_in_executor(None, _connect, serial) for serial in serial_list]
await asyncio.gather(*tasks)
ev.run_until_complete(connect())
@Config.when(DEVICE_OVER_HTTP=True)
def adb_connect(self):
# No adb connect if over http

View File

@ -163,6 +163,10 @@ class ConnectionAttr:
def is_network_device(self):
return bool(re.match(r'\d+\.\d+\.\d+\.\d+:\d+', self.serial))
@cached_property
def is_local_network_device(self):
return bool(re.match(r'192\.168\.\d+\.\d+:\d+', self.serial))
@cached_property
def is_over_http(self):
return bool(re.match(r"^https?://", self.serial))

View File

@ -303,8 +303,11 @@ class Adb(Connection):
logger.error(result)
raise PackageNotInstalled(package_name)
ret = self.adb_shell(['am', 'start', '-a', 'android.intent.action.MAIN', '-c',
'android.intent.category.LAUNCHER', '-n', f'{package_name}/{activity_name}'])
cmd = ['am', 'start', '-a', 'android.intent.action.MAIN', '-c',
'android.intent.category.LAUNCHER', '-n', f'{package_name}/{activity_name}']
if self.is_local_network_device and self.is_waydroid:
cmd += ['--windowingMode', '4']
ret = self.adb_shell(cmd)
# Invalid activity
# Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=... }
# Error type 3
@ -320,6 +323,24 @@ class Adb(Connection):
if 'Warning: Activity not started' in ret:
logger.info('App activity is already started')
return True
# Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.YoStarEN.AzurLane/com.manjuu.azurlane.MainActivity }
# java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.YoStarEN.AzurLane/com.manjuu.azurlane.MainActivity } from null (pid=5140, uid=2000) not exported from uid 10064
# at android.os.Parcel.readException(Parcel.java:1692)
# at android.os.Parcel.readException(Parcel.java:1645)
# at android.app.ActivityManagerProxy.startActivityAsUser(ActivityManagerNative.java:3152)
# at com.android.commands.am.Am.runStart(Am.java:643)
# at com.android.commands.am.Am.onRun(Am.java:394)
# at com.android.internal.os.BaseCommand.run(BaseCommand.java:51)
# at com.android.commands.am.Am.main(Am.java:124)
# at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
# at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:290)
if 'Permission Denial' in ret:
if allow_failure:
return False
else:
logger.error(ret)
logger.error('Permission Denial while starting app, probably because activity invalid')
return False
# Success
# Starting: Intent...
return True

View File

@ -486,6 +486,9 @@ class NemuIpc(Platform):
def nemu_ipc_available(self) -> bool:
if not self.is_mumu_family:
return False
# >= 4.0 has no info in getprop
if self.nemud_player_version == '':
return True
if self.nemud_app_keep_alive == '':
return False
try:

View File

@ -224,28 +224,147 @@ class Uiautomator2(Connection):
return result['package']
@retry
def app_start_uiautomator2(self, package_name=None, activity_name=None):
def _app_start_u2_monkey(self, package_name=None, allow_failure=False):
"""
Args:
package_name (str):
allow_failure (bool):
Returns:
bool: If success to start
Raises:
PackageNotInstalled:
"""
if not package_name:
package_name = self.package
result = self.u2.shell([
'monkey', '-p', package_name, '-c',
'android.intent.category.LAUNCHER', '--pct-syskeys', '0', '1'
])
if 'No activities found' in result.output:
# ** No activities found to run, monkey aborted.
if allow_failure:
return False
else:
logger.error(result)
raise PackageNotInstalled(package_name)
elif 'inaccessible' in result:
# /system/bin/sh: monkey: inaccessible or not found
return False
else:
# Events injected: 1
# ## Network stats: elapsed time=4ms (0ms mobile, 0ms wifi, 4ms not connected)
return True
@retry
def _app_start_u2_am(self, package_name=None, activity_name=None, allow_failure=False):
"""
Args:
package_name (str):
activity_name (str):
allow_failure (bool):
Returns:
bool: If success to start
Raises:
PackageNotInstalled:
"""
if not package_name:
package_name = self.package
if not activity_name:
try:
info = self.u2.app_info(package_name)
except u2.BaseError as e:
if allow_failure:
return False
# BaseError('package "111" not found')
elif 'not found' in str(e):
logger.error(e)
raise PackageNotInstalled(package_name)
# Unknown error
else:
raise
activity_name = info['mainActivity']
cmd = ['am', 'start', '-a', 'android.intent.action.MAIN', '-c',
'android.intent.category.LAUNCHER', '-n', f'{package_name}/{activity_name}']
if self.is_local_network_device and self.is_waydroid:
cmd += ['--windowingMode', '4']
ret = self.u2.shell(cmd)
# Invalid activity
# Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=... }
# Error type 3
# Error: Activity class {.../...} does not exist.
if 'Error: Activity class' in ret.output:
if allow_failure:
return False
else:
logger.error(ret)
return False
# Already running
# Warning: Activity not started, intent has been delivered to currently running top-most instance.
if 'Warning: Activity not started' in ret.output:
logger.info('App activity is already started')
return True
# Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.YoStarEN.AzurLane/com.manjuu.azurlane.MainActivity }
# java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.YoStarEN.AzurLane/com.manjuu.azurlane.MainActivity } from null (pid=5140, uid=2000) not exported from uid 10064
# at android.os.Parcel.readException(Parcel.java:1692)
# at android.os.Parcel.readException(Parcel.java:1645)
# at android.app.ActivityManagerProxy.startActivityAsUser(ActivityManagerNative.java:3152)
# at com.android.commands.am.Am.runStart(Am.java:643)
# at com.android.commands.am.Am.onRun(Am.java:394)
# at com.android.internal.os.BaseCommand.run(BaseCommand.java:51)
# at com.android.commands.am.Am.main(Am.java:124)
# at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
# at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:290)
if 'Permission Denial' in ret.output:
if allow_failure:
return False
else:
logger.error(ret)
logger.error('Permission Denial while starting app, probably because activity invalid')
return False
# Success
# Starting: Intent...
return True
# No @retry decorator since _app_start_adb_am and _app_start_adb_monkey have @retry already
# @retry
def app_start_uiautomator2(self, package_name=None, activity_name=None, allow_failure=False):
"""
Args:
package_name (str):
If None, to get from config
activity_name (str):
If None, to get from DICT_PACKAGE_TO_ACTIVITY
If still None, launch from monkey
If monkey failed, fetch activity name and launch from am
allow_failure (bool):
True for no PackageNotInstalled raising, just return False
Returns:
bool: If success to start
Raises:
PackageNotInstalled:
"""
if not package_name:
package_name = self.package
if not activity_name:
activity_name = DICT_PACKAGE_TO_ACTIVITY.get(package_name)
try:
self.u2.app_start(package_name, activity_name)
except u2.exceptions.BaseError as e:
# BaseError: package "com.bilibili.azurlane" not found
logger.error(e)
raise PackageNotInstalled(package_name)
if activity_name:
if self._app_start_u2_am(package_name, activity_name, allow_failure):
return True
if self._app_start_u2_monkey(package_name, allow_failure):
return True
if self._app_start_u2_am(package_name, activity_name, allow_failure):
return True
logger.error('app_start_uiautomator2: All trials failed')
return False
@retry
def app_stop_uiautomator2(self, package_name=None):

View File

@ -23,3 +23,13 @@ ROGUE_LEAVE_FOR_NOW = ButtonWrapper(
button=(729, 475, 765, 519),
),
)
ROGUE_LEAVE_FOR_NOW_OE = ButtonWrapper(
name='ROGUE_LEAVE_FOR_NOW_OE',
share=Button(
file='./assets/share/base/main_page/ROGUE_LEAVE_FOR_NOW_OE.png',
area=(730, 551, 760, 587),
search=(710, 531, 780, 607),
color=(63, 52, 40),
button=(730, 551, 760, 587),
),
)

View File

@ -297,6 +297,16 @@ MAP_EXIT = ButtonWrapper(
),
],
)
MAP_EXIT_OE = ButtonWrapper(
name='MAP_EXIT_OE',
share=Button(
file='./assets/share/base/page/MAP_EXIT_OE.png',
area=(51, 55, 68, 84),
search=(31, 35, 88, 104),
color=(141, 140, 141),
button=(51, 55, 68, 84),
),
)
MAP_GOTO_WORLD = ButtonWrapper(
name='MAP_GOTO_WORLD',
share=Button(

View File

@ -4,8 +4,8 @@ from module.base.timer import Timer
from module.exception import GameNotRunningError, GamePageUnknownError
from module.logger import logger
from module.ocr.ocr import Ocr
from tasks.base.assets.assets_base_main_page import ROGUE_LEAVE_FOR_NOW
from tasks.base.assets.assets_base_page import CLOSE, MAIN_GOTO_CHARACTER, MAP_EXIT
from tasks.base.assets.assets_base_main_page import ROGUE_LEAVE_FOR_NOW, ROGUE_LEAVE_FOR_NOW_OE
from tasks.base.assets.assets_base_page import CLOSE, MAIN_GOTO_CHARACTER, MAP_EXIT, MAP_EXIT_OE
from tasks.base.main_page import MainPage
from tasks.base.page import Page, page_gacha, page_main
from tasks.combat.assets.assets_combat_finish import COMBAT_EXIT
@ -344,7 +344,6 @@ class UI(MainPage):
return appear
def is_in_map_exit(self, interval=0):
self.device.stuck_record_add(MAP_EXIT)
@ -355,6 +354,9 @@ class UI(MainPage):
if MAP_EXIT.match_template_luma(self.device.image):
if self.image_color_count(MAP_EXIT, color=(235, 235, 235), threshold=221, count=50):
appear = True
if MAP_EXIT_OE.match_template_luma(self.device.image):
if self.image_color_count(MAP_EXIT_OE, color=(235, 235, 235), threshold=221, count=50):
appear = True
if appear and interval:
self.interval_reset(MAP_EXIT, interval=interval)
@ -482,3 +484,6 @@ class UI(MainPage):
if self.appear_then_click(ROGUE_LEAVE_FOR_NOW, interval=2):
clicked = True
continue
if self.appear_then_click(ROGUE_LEAVE_FOR_NOW_OE, interval=2):
clicked = True
continue

View File

@ -1,165 +0,0 @@
from module.base.button import Button, ButtonWrapper
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.button_extract ```
EXTRACT_RESERVED_TRAILBLAZE_POWER = ButtonWrapper(
name='EXTRACT_RESERVED_TRAILBLAZE_POWER',
share=Button(
file='./assets/share/combat/fuel/EXTRACT_RESERVED_TRAILBLAZE_POWER.png',
area=(909, 506, 929, 526),
search=(889, 486, 949, 546),
color=(91, 91, 91),
button=(909, 506, 929, 526),
),
)
FUEL = ButtonWrapper(
name='FUEL',
share=Button(
file='./assets/share/combat/fuel/FUEL.png',
area=(592, 276, 688, 366),
search=(474, 271, 811, 396),
color=(123, 96, 134),
button=(592, 276, 688, 366),
),
)
FUEL_ENTRANCE = ButtonWrapper(
name='FUEL_ENTRANCE',
share=Button(
file='./assets/share/combat/fuel/FUEL_ENTRANCE.png',
area=(1035, 26, 1056, 48),
search=(1015, 6, 1076, 68),
color=(188, 180, 226),
button=(1035, 26, 1056, 48),
),
)
FUEL_MINUS = ButtonWrapper(
name='FUEL_MINUS',
share=Button(
file='./assets/share/combat/fuel/FUEL_MINUS.png',
area=(472, 425, 510, 450),
search=(452, 405, 530, 470),
color=(236, 236, 236),
button=(472, 425, 510, 450),
),
)
FUEL_PLUS = ButtonWrapper(
name='FUEL_PLUS',
share=Button(
file='./assets/share/combat/fuel/FUEL_PLUS.png',
area=(967, 426, 1005, 449),
search=(947, 406, 1025, 469),
color=(232, 232, 232),
button=(967, 426, 1005, 449),
),
)
FUEL_SELECTED = ButtonWrapper(
name='FUEL_SELECTED',
share=Button(
file='./assets/share/combat/fuel/FUEL_SELECTED.png',
area=(587, 271, 692, 368),
search=(474, 271, 811, 396),
color=(136, 112, 144),
button=(587, 271, 692, 368),
),
)
FUEL_SLIDER = ButtonWrapper(
name='FUEL_SLIDER',
share=Button(
file='./assets/share/combat/fuel/FUEL_SLIDER.png',
area=(561, 434, 916, 441),
search=(541, 414, 936, 461),
color=(215, 185, 154),
button=(561, 434, 916, 441),
),
)
OCR_EXTRACT_RESERVED_TRAILBLAZE_POWER_COUNT = ButtonWrapper(
name='OCR_EXTRACT_RESERVED_TRAILBLAZE_POWER_COUNT',
share=Button(
file='./assets/share/combat/fuel/OCR_EXTRACT_RESERVED_TRAILBLAZE_POWER_COUNT.png',
area=(425, 415, 688, 436),
search=(405, 395, 708, 456),
color=(192, 192, 192),
button=(425, 415, 688, 436),
),
)
OCR_FUEL = ButtonWrapper(
name='OCR_FUEL',
share=Button(
file='./assets/share/combat/fuel/OCR_FUEL.png',
area=(605, 369, 678, 386),
search=(585, 349, 698, 406),
color=(66, 66, 66),
button=(605, 369, 678, 386),
),
)
OCR_FUEL_COUNT = ButtonWrapper(
name='OCR_FUEL_COUNT',
share=Button(
file='./assets/share/combat/fuel/OCR_FUEL_COUNT.png',
area=(686, 409, 881, 425),
search=(666, 389, 901, 445),
color=(205, 205, 205),
button=(686, 409, 881, 425),
),
)
OCR_RESERVED_TRAILBLAZE_POWER = ButtonWrapper(
name='OCR_RESERVED_TRAILBLAZE_POWER',
share=Button(
file='./assets/share/combat/fuel/OCR_RESERVED_TRAILBLAZE_POWER.png',
area=(883, 29, 992, 44),
search=(863, 9, 1012, 64),
color=(51, 65, 65),
button=(883, 29, 992, 44),
),
)
RESERVED_MINUS = ButtonWrapper(
name='RESERVED_MINUS',
share=Button(
file='./assets/share/combat/fuel/RESERVED_MINUS.png',
area=(248, 474, 281, 498),
search=(228, 454, 301, 518),
color=(238, 238, 238),
button=(248, 474, 281, 498),
),
)
RESERVED_PLUS = ButtonWrapper(
name='RESERVED_PLUS',
share=Button(
file='./assets/share/combat/fuel/RESERVED_PLUS.png',
area=(938, 475, 974, 498),
search=(918, 455, 994, 518),
color=(232, 232, 232),
button=(938, 475, 974, 498),
),
)
RESERVED_SLIDER = ButtonWrapper(
name='RESERVED_SLIDER',
share=Button(
file='./assets/share/combat/fuel/RESERVED_SLIDER.png',
area=(334, 483, 873, 489),
search=(314, 463, 893, 509),
color=(212, 173, 130),
button=(334, 483, 873, 489),
),
)
RESERVED_TRAILBLAZE_POWER_ENTRANCE = ButtonWrapper(
name='RESERVED_TRAILBLAZE_POWER_ENTRANCE',
share=Button(
file='./assets/share/combat/fuel/RESERVED_TRAILBLAZE_POWER_ENTRANCE.png',
area=(895, 26, 916, 48),
search=(875, 6, 936, 68),
color=(154, 213, 214),
button=(895, 26, 916, 48),
),
)
USING_FUEL = ButtonWrapper(
name='USING_FUEL',
share=Button(
file='./assets/share/combat/fuel/USING_FUEL.png',
area=(263, 265, 363, 365),
search=(243, 245, 383, 385),
color=(161, 116, 129),
button=(263, 265, 363, 365),
),
)

View File

@ -20,16 +20,6 @@ COMBAT_PREPARE = ButtonWrapper(
button=(956, 640, 1225, 676),
),
)
OCR_TRAILBLAZE_POWER = ButtonWrapper(
name='OCR_TRAILBLAZE_POWER',
share=Button(
file='./assets/share/combat/prepare/OCR_TRAILBLAZE_POWER.png',
area=(998, 26, 1130, 48),
search=(978, 6, 1150, 68),
color=(77, 76, 87),
button=(998, 26, 1130, 48),
),
)
OCR_WAVE_COST = ButtonWrapper(
name='OCR_WAVE_COST',
share=Button(

View File

@ -0,0 +1,85 @@
from module.base.button import Button, ButtonWrapper
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.button_extract ```
FUEL = ButtonWrapper(
name='FUEL',
share=Button(
file='./assets/share/combat/stamina/fuel/FUEL.png',
area=(592, 276, 688, 366),
search=(474, 271, 811, 396),
color=(123, 96, 134),
button=(592, 276, 688, 366),
),
)
FUEL_MINUS = ButtonWrapper(
name='FUEL_MINUS',
share=Button(
file='./assets/share/combat/stamina/fuel/FUEL_MINUS.png',
area=(472, 425, 510, 450),
search=(452, 405, 530, 470),
color=(236, 236, 236),
button=(472, 425, 510, 450),
),
)
FUEL_PLUS = ButtonWrapper(
name='FUEL_PLUS',
share=Button(
file='./assets/share/combat/stamina/fuel/FUEL_PLUS.png',
area=(967, 426, 1005, 449),
search=(947, 406, 1025, 469),
color=(232, 232, 232),
button=(967, 426, 1005, 449),
),
)
FUEL_SELECTED = ButtonWrapper(
name='FUEL_SELECTED',
share=Button(
file='./assets/share/combat/stamina/fuel/FUEL_SELECTED.png',
area=(587, 271, 692, 368),
search=(474, 271, 811, 396),
color=(136, 112, 144),
button=(587, 271, 692, 368),
),
)
FUEL_SLIDER = ButtonWrapper(
name='FUEL_SLIDER',
share=Button(
file='./assets/share/combat/stamina/fuel/FUEL_SLIDER.png',
area=(561, 434, 916, 441),
search=(541, 414, 936, 461),
color=(215, 185, 154),
button=(561, 434, 916, 441),
),
)
OCR_FUEL = ButtonWrapper(
name='OCR_FUEL',
share=Button(
file='./assets/share/combat/stamina/fuel/OCR_FUEL.png',
area=(605, 369, 678, 386),
search=(585, 349, 698, 406),
color=(66, 66, 66),
button=(605, 369, 678, 386),
),
)
OCR_FUEL_COUNT = ButtonWrapper(
name='OCR_FUEL_COUNT',
share=Button(
file='./assets/share/combat/stamina/fuel/OCR_FUEL_COUNT.png',
area=(686, 409, 881, 425),
search=(666, 389, 901, 445),
color=(205, 205, 205),
button=(686, 409, 881, 425),
),
)
USING_FUEL = ButtonWrapper(
name='USING_FUEL',
share=Button(
file='./assets/share/combat/stamina/fuel/USING_FUEL.png',
area=(263, 265, 363, 365),
search=(243, 245, 383, 385),
color=(161, 116, 129),
button=(263, 265, 363, 365),
),
)

View File

@ -0,0 +1,55 @@
from module.base.button import Button, ButtonWrapper
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.button_extract ```
EXTRACT_RESERVED_TRAILBLAZE_POWER = ButtonWrapper(
name='EXTRACT_RESERVED_TRAILBLAZE_POWER',
share=Button(
file='./assets/share/combat/stamina/reserved/EXTRACT_RESERVED_TRAILBLAZE_POWER.png',
area=(909, 506, 929, 526),
search=(889, 486, 949, 546),
color=(91, 91, 91),
button=(909, 506, 929, 526),
),
)
OCR_EXTRACT_RESERVED_TRAILBLAZE_POWER_COUNT = ButtonWrapper(
name='OCR_EXTRACT_RESERVED_TRAILBLAZE_POWER_COUNT',
share=Button(
file='./assets/share/combat/stamina/reserved/OCR_EXTRACT_RESERVED_TRAILBLAZE_POWER_COUNT.png',
area=(425, 415, 688, 436),
search=(405, 395, 708, 456),
color=(192, 192, 192),
button=(425, 415, 688, 436),
),
)
RESERVED_MINUS = ButtonWrapper(
name='RESERVED_MINUS',
share=Button(
file='./assets/share/combat/stamina/reserved/RESERVED_MINUS.png',
area=(248, 474, 281, 498),
search=(228, 454, 301, 518),
color=(238, 238, 238),
button=(248, 474, 281, 498),
),
)
RESERVED_PLUS = ButtonWrapper(
name='RESERVED_PLUS',
share=Button(
file='./assets/share/combat/stamina/reserved/RESERVED_PLUS.png',
area=(938, 475, 974, 498),
search=(918, 455, 994, 518),
color=(232, 232, 232),
button=(938, 475, 974, 498),
),
)
RESERVED_SLIDER = ButtonWrapper(
name='RESERVED_SLIDER',
share=Button(
file='./assets/share/combat/stamina/reserved/RESERVED_SLIDER.png',
area=(334, 483, 873, 489),
search=(314, 463, 893, 509),
color=(212, 173, 130),
button=(334, 483, 873, 489),
),
)

View File

@ -0,0 +1,75 @@
from module.base.button import Button, ButtonWrapper
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.button_extract ```
ICON_SEARCH = ButtonWrapper(
name='ICON_SEARCH',
share=Button(
file='./assets/share/combat/stamina/status/ICON_SEARCH.png',
area=(568, 8, 1265, 66),
search=(548, 0, 1280, 86),
color=(71, 70, 100),
button=(568, 8, 1265, 66),
),
)
IMMERSIFIER_ICON = ButtonWrapper(
name='IMMERSIFIER_ICON',
share=Button(
file='./assets/share/combat/stamina/status/IMMERSIFIER_ICON.png',
area=(1047, 26, 1066, 49),
search=(1027, 6, 1086, 69),
color=(138, 127, 117),
button=(1047, 26, 1066, 49),
),
)
IMMERSIFIER_OCR = ButtonWrapper(
name='IMMERSIFIER_OCR',
share=Button(
file='./assets/share/combat/stamina/status/IMMERSIFIER_OCR.png',
area=(1049, 26, 1151, 48),
search=(1029, 6, 1171, 68),
color=(64, 61, 61),
button=(1049, 26, 1151, 48),
),
)
RESERVED_ICON = ButtonWrapper(
name='RESERVED_ICON',
share=Button(
file='./assets/share/combat/stamina/status/RESERVED_ICON.png',
area=(895, 26, 916, 48),
search=(875, 6, 936, 68),
color=(155, 212, 215),
button=(895, 26, 916, 48),
),
)
RESERVED_OCR = ButtonWrapper(
name='RESERVED_OCR',
share=Button(
file='./assets/share/combat/stamina/status/RESERVED_OCR.png',
area=(895, 26, 999, 48),
search=(875, 6, 1019, 68),
color=(50, 69, 83),
button=(895, 26, 999, 48),
),
)
STAMINA_ICON = ButtonWrapper(
name='STAMINA_ICON',
share=Button(
file='./assets/share/combat/stamina/status/STAMINA_ICON.png',
area=(873, 26, 894, 48),
search=(853, 6, 914, 68),
color=(188, 180, 226),
button=(873, 26, 894, 48),
),
)
STAMINA_OCR = ButtonWrapper(
name='STAMINA_OCR',
share=Button(
file='./assets/share/combat/stamina/status/STAMINA_OCR.png',
area=(873, 26, 1013, 48),
search=(853, 6, 1033, 68),
color=(66, 64, 88),
button=(873, 26, 1013, 48),
),
)

View File

@ -54,7 +54,7 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo
# Check limits
if self.config.stored.TrailblazePower.value < self.combat_wave_cost:
return self._try_get_more_trablaize_power(self.config.stored.TrailblazePower.value, self.combat_wave_cost)
return self._try_get_more_trablaize_power(self.combat_wave_cost)
if self.combat_waves <= 0:
logger.info('Combat wave limited, cannot continue combat')
return False
@ -163,6 +163,8 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo
skip_first_screenshot = True
is_executing = True
self.combat_state_reset()
self.device.stuck_record_clear()
self.device.click_record_clear()
self.device.screenshot_interval_set('combat')
while 1:
if skip_first_screenshot:
@ -196,6 +198,8 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo
if self.handle_battle_pass_notification():
continue
self.device.stuck_record_clear()
self.device.click_record_clear()
self.device.screenshot_interval_set()
def _combat_can_again(self) -> bool:
@ -221,7 +225,7 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo
logger.info(f'Current has {current}, combat costs {self.combat_wave_cost}, can run again')
return True
else:
return self._try_get_more_trablaize_power(current, self.combat_wave_cost * self.combat_waves)
return self._try_get_more_trablaize_power(self.combat_wave_cost * self.combat_waves)
elif self.combat_wave_cost <= 0:
logger.info(f'Free combat, combat costs {self.combat_wave_cost}, can not run again')
return False
@ -230,20 +234,15 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo
logger.info(f'Current has {current}, combat costs {self.combat_wave_cost}, can run again')
return True
else:
return self._try_get_more_trablaize_power(current, self.combat_wave_cost * self.combat_waves)
def _try_get_more_trablaize_power(self, current, cost):
if self.config.TrailblazePower_ExtractReservedTrailblazePower:
logger.info('Extract reserved trailblaze power to get more trailblaze power')
if self.extract_reserved_trailblaze_power(current):
self.combat_get_trailblaze_power()
self.get_interval_timer(COMBAT_EXIT).wait()
if self.config.TrailblazePower_UseFuel:
logger.info('Use fuel to get more trailblaze power')
if self.use_fuel(current):
self.combat_get_trailblaze_power()
self.get_interval_timer(COMBAT_AGAIN).wait()
return self._try_get_more_trablaize_power(self.combat_wave_cost * self.combat_waves)
def _try_get_more_trablaize_power(self, cost):
self.extract_stamina(
update=False,
use_reserved=self.config.TrailblazePower_ExtractReservedTrailblazePower,
use_fuel=self.config.TrailblazePower_UseFuel
)
current = self.config.stored.TrailblazePower.value
if current >= cost:
return True
else:

View File

@ -4,14 +4,16 @@ from module.base.utils import area_offset, crop
from module.logger import logger
from module.ocr.ocr import Digit
from tasks.base.assets.assets_base_popup import GET_REWARD, POPUP_CANCEL, POPUP_CONFIRM
from tasks.base.ui import UI
from tasks.combat.assets.assets_combat_finish import COMBAT_AGAIN, COMBAT_EXIT
from tasks.combat.assets.assets_combat_fuel import *
from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE
from tasks.combat.assets.assets_combat_stamina_fuel import *
from tasks.combat.assets.assets_combat_stamina_reserved import *
from tasks.combat.assets.assets_combat_stamina_status import *
from tasks.combat.stamina_status import StaminaStatus
from tasks.item.slider import Slider
class Fuel(UI):
class Fuel(StaminaStatus):
fuel_trailblaze_power = 60
def _use_fuel_finish(self):
@ -26,7 +28,7 @@ class Fuel(UI):
return True
if self.appear(COMBAT_PREPARE):
if self.image_color_count(COMBAT_PREPARE.button, color=(230, 230, 230), threshold=240, count=400):
logger.info(f'Use fuel finished at COMBAT_AGAIN')
logger.info(f'Use fuel finished at COMBAT_PREPARE')
return True
return False
@ -87,7 +89,7 @@ class Fuel(UI):
timer = self.get_interval_timer(COMBAT_EXIT, interval=5, renew=True)
timer.set_current(4.4)
def extract_reserved_trailblaze_power(self, current, skip_first_screenshot=True):
def extract_reserved_trailblaze_power(self, skip_first_screenshot=True):
"""
Extract reserved trailblaze power from previous combat.
@ -95,11 +97,8 @@ class Fuel(UI):
bool: If extracted
"""
logger.info('Extract reserved trailblaze power')
reserved = Digit(OCR_RESERVED_TRAILBLAZE_POWER).ocr_single_line(self.device.image)
if reserved <= 0:
logger.info('No reserved trailblaze power')
return False
RESERVED_ICON.load_search(ICON_SEARCH.area)
self.interval_clear([POPUP_CONFIRM, POPUP_CANCEL, GET_REWARD])
while 1:
if skip_first_screenshot:
@ -111,12 +110,14 @@ class Fuel(UI):
break
if self.appear_then_click(EXTRACT_RESERVED_TRAILBLAZE_POWER):
continue
if self.appear_then_click(RESERVED_TRAILBLAZE_POWER_ENTRANCE):
if self.appear_then_click(RESERVED_ICON):
continue
count = min(reserved, self.config.stored.TrailblazePower.FIXED_TOTAL - current)
logger.info(f'Having {reserved} reserved, going to use {count}')
self.set_reserved_trailblaze_power(count, total=reserved)
# No need, amount will be set by game client
# count = min(reserved, self.config.stored.TrailblazePower.FIXED_TOTAL - current)
# logger.info(f'Having {reserved} reserved, going to use {count}')
# self.set_reserved_trailblaze_power(count, total=reserved)
self._fuel_confirm()
return True
@ -160,6 +161,7 @@ class Fuel(UI):
logger.info("Use Fuel")
STAMINA_ICON.load_search(ICON_SEARCH.area)
timeout = Timer(1, count=3)
has_fuel = False
while 1:
@ -182,7 +184,7 @@ class Fuel(UI):
if self.appear_then_click(FUEL):
has_fuel = True
continue
if not self.appear(POPUP_CONFIRM) and self.appear_then_click(FUEL_ENTRANCE):
if not self.appear(POPUP_CONFIRM) and self.appear_then_click(STAMINA_ICON):
continue
offset = FUEL_SELECTED.button_offset
@ -215,3 +217,39 @@ class Fuel(UI):
self.set_fuel_count(use)
self._fuel_confirm()
return True
def extract_stamina(self, update=True, use_reserved=True, use_fuel=False):
"""
Args:
update:
use_reserved:
use_fuel:
Returns:
bool: If used
"""
if not use_reserved and not use_fuel:
return False
logger.hr('Extract stamina', level=2)
logger.info(f'Extract stamina, reserved={use_reserved}, fuel={use_fuel}')
if update:
self.update_stamina_status()
used = False
if use_reserved:
if self.config.stored.Reserved.value <= 0:
logger.info('No reserved stamina')
else:
self.extract_reserved_trailblaze_power()
used = True
self.update_stamina_status()
self.get_interval_timer(COMBAT_AGAIN).wait()
if use_fuel:
self.use_fuel(current=self.config.stored.TrailblazePower.value)
used = True
self.update_stamina_status()
self.get_interval_timer(COMBAT_AGAIN).wait()
return used

View File

@ -1,35 +1,20 @@
import re
import module.config.server as server
from module.base.timer import Timer
from module.base.utils import color_similar, get_color
from module.logger import logger
from module.ocr.ocr import Digit, DigitCounter
from tasks.base.ui import UI
from module.ocr.ocr import Digit
from tasks.combat.assets.assets_combat_prepare import (
OCR_TRAILBLAZE_POWER,
OCR_WAVE_COST,
OCR_WAVE_COUNT,
WAVE_MINUS,
WAVE_PLUS, WAVE_SLIDER
WAVE_PLUS,
WAVE_SLIDER
)
from tasks.combat.stamina_status import StaminaStatus
from tasks.item.slider import Slider
class TrailblazePowerOcr(DigitCounter):
def after_process(self, result):
result = super().after_process(result)
# The trailblaze power icon is recognized as 买
# OCR_TRAILBLAZE_POWER includes the icon because the length varies by value
result = re.sub(r'[买米装:()]', '', result)
# 61240 -> 6/240
result = re.sub(r'1240$', '/240', result)
# 0*0/24 -> 0/240
result = re.sub(r'24$', '240', result)
return result
class CombatPrepare(UI):
class CombatPrepare(StaminaStatus):
# Current combat waves,
combat_waves = 1
# Limit combat runs, 0 means no limit.
@ -78,20 +63,20 @@ class CombatPrepare(UI):
else:
self.device.screenshot()
current, _, total = TrailblazePowerOcr(OCR_TRAILBLAZE_POWER).ocr_single_line(self.device.image)
# Empty result
if total == 0:
data = self.update_stamina_status(image=self.device.image)
if data.stamina is None:
continue
# Confirm if it is > 240, sometimes just OCR errors
if current > 240 and timeout.reached():
# if current > 240 and timeout.reached():
# break
if expect_reduce and timeout.reached():
break
if expect_reduce and current >= before:
if expect_reduce and data.stamina >= before:
continue
if current <= 240:
if data.stamina <= 240:
break
self.config.stored.TrailblazePower.value = current
return current
return data.stamina
def combat_get_wave_cost(self, skip_first_screenshot=True):
"""

View File

@ -0,0 +1,161 @@
import re
from pydantic import BaseModel
from module.base.timer import Timer
from module.base.utils import crop
from module.logger import logger
from module.ocr.ocr import Digit, DigitCounter
from tasks.base.ui import UI
from tasks.combat.assets.assets_combat_stamina_status import *
class StaminaOcr(DigitCounter):
def after_process(self, result):
result = super().after_process(result)
# The trailblaze power icon is recognized as 买
# OCR_TRAILBLAZE_POWER includes the icon because the length varies by value
result = re.sub(r'[买米装来:()]', '', result)
# 61240 -> 6/240
result = re.sub(r'1240$', '/240', result)
# 0*0/24 -> 0/240
result = re.sub(r'24$', '240', result)
return result
class ReservedOcr(Digit):
pass
class ImmersifierOcr(DigitCounter):
pass
class DataStaminaStatus(BaseModel):
stamina: int | None
reserved: int | None
immersifier: int | None
class StaminaStatus(UI):
@staticmethod
def get_stamina_status(image) -> DataStaminaStatus:
"""
Update trailblaze power, stored trailblaze power, immersifier
Returns:
int: Stamina, or None if stamina not displayed or error on OCR
int: Reserved stamina
int: Immersifier
"""
for button in [STAMINA_ICON, RESERVED_ICON, IMMERSIFIER_ICON]:
button.load_search(ICON_SEARCH.area)
stamina = None
if STAMINA_ICON.match_template(image):
STAMINA_OCR.load_offset(STAMINA_ICON)
im = crop(image, STAMINA_OCR.button, copy=False)
stamina, _, total = StaminaOcr(STAMINA_OCR).ocr_single_line(im, direct_ocr=True)
if total > 240 or total == 0:
logger.warning(f'Unexpected stamina total: {total}')
stamina = None
reserved = None
if RESERVED_ICON.match_template(image):
RESERVED_OCR.load_offset(RESERVED_ICON)
im = crop(image, RESERVED_OCR.button, copy=False)
reserved = ReservedOcr(RESERVED_OCR).ocr_single_line(im, direct_ocr=True)
if reserved > 2400:
logger.warning(f'Unexpected reserved value: {reserved}')
reserved = None
immersifier = None
if IMMERSIFIER_ICON.match_template(image):
IMMERSIFIER_OCR.load_offset(IMMERSIFIER_ICON)
im = crop(image, IMMERSIFIER_OCR.button, copy=False)
immersifier, _, total = StaminaOcr(IMMERSIFIER_OCR).ocr_single_line(im, direct_ocr=True)
if total != 8:
logger.warning(f'Unexpected immersifier total: {total}')
immersifier = None
return DataStaminaStatus(
stamina=stamina,
reserved=reserved,
immersifier=immersifier,
)
def update_stamina_status(
self,
image=None,
skip_first_screenshot=True,
expect_stamina=False,
expect_reserved=False,
expect_immersifier=False,
) -> DataStaminaStatus:
"""
Update stamina status with retry
Args:
image: Detect given image only, no new screenshot will be taken
and all expect_* are considered False
skip_first_screenshot:
expect_stamina:
True to expect stamina exists, retry detect if it wasn't
expect_reserved:
expect_immersifier:
Pages:
in: page_guild, Survival_Index, Simulated_Universe
or page_rogue, LEVEL_CONFIRM
or rogue, REWARD_CLOSE
"""
timeout = Timer(1, count=2).start()
if image is None:
image = self.device.image
use_cached_image = False
else:
skip_first_screenshot = True
use_cached_image = True
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
image = self.device.image
# Timeout
if timeout.reached():
logger.warning('dungeon_update_stamina() timeout')
return DataStaminaStatus(
stamina=None,
reserved=None,
immersifier=None,
)
# Ocr
status = self.get_stamina_status(image)
valid = True
if expect_stamina and status.stamina is None:
valid = False
if expect_reserved and status.reserved is None:
valid = False
if expect_immersifier and status.immersifier is None:
valid = False
if status.stamina is None and status.reserved is None and status.immersifier is None:
logger.warning('update_stamina_status: No icon detected')
valid = False
# Write config
with self.config.multi_set():
if status.stamina is not None:
self.config.stored.TrailblazePower.value = status.stamina
if status.reserved is not None:
self.config.stored.Reserved.value = status.reserved
if status.immersifier is not None:
self.config.stored.Immersifier.value = status.reserved
if use_cached_image or valid:
return status
else:
continue

View File

@ -203,6 +203,7 @@ class CombatSupport(UI):
scroll.set_bottom(main=self)
scroll.drag_threshold = backup
scroll.set_top(main=self)
self.device.click_record_clear()
logger.info("Searching support")
skip_first_screenshot = True
@ -216,9 +217,11 @@ class CombatSupport(UI):
if character:
logger.info("Support found")
if self._select_support(character):
self.device.click_record_clear()
return True
else:
logger.warning("Support not selected")
self.device.click_record_clear()
return False
if not scroll.at_bottom(main=self):
@ -226,6 +229,7 @@ class CombatSupport(UI):
continue
else:
logger.info("Support not found")
self.device.click_record_clear()
return False
def _select_support(self, character: SupportCharacter):

View File

@ -41,16 +41,6 @@ AMOUNT_PLUS = ButtonWrapper(
),
],
)
ENTER_IMMERSIFIER = ButtonWrapper(
name='ENTER_IMMERSIFIER',
share=Button(
file='./assets/share/dungeon/stamina/ENTER_IMMERSIFIER.png',
area=(1047, 26, 1066, 49),
search=(1027, 6, 1086, 69),
color=(138, 127, 117),
button=(1049, 26, 1151, 48),
),
)
IMMERSIFIER_CHECK = ButtonWrapper(
name='IMMERSIFIER_CHECK',
share=Button(

View File

@ -45,13 +45,22 @@ SIMULATED_UNIVERSE_LOADED_CLASSIC = ButtonWrapper(
)
SURVIVAL_INDEX_OE_LOADED = ButtonWrapper(
name='SURVIVAL_INDEX_OE_LOADED',
share=Button(
file='./assets/share/dungeon/ui_rogue/SURVIVAL_INDEX_OE_LOADED.png',
area=(455, 208, 485, 338),
search=(460, 238, 480, 268),
color=(130, 116, 91),
button=(455, 208, 485, 338),
),
share=[
Button(
file='./assets/share/dungeon/ui_rogue/SURVIVAL_INDEX_OE_LOADED.png',
area=(460, 238, 480, 268),
search=(455, 208, 485, 338),
color=(221, 192, 131),
button=(460, 238, 480, 268),
),
Button(
file='./assets/share/dungeon/ui_rogue/SURVIVAL_INDEX_OE_LOADED.2.png',
area=(460, 238, 480, 268),
search=(455, 208, 485, 338),
color=(198, 161, 100),
button=(460, 238, 480, 268),
),
],
)
SURVIVAL_INDEX_SU_LOADED = ButtonWrapper(
name='SURVIVAL_INDEX_SU_LOADED',

View File

@ -3,6 +3,7 @@ from module.base.timer import Timer
from module.logger import logger
from module.ocr.ocr import Digit
from tasks.base.page import page_guide
from tasks.combat.assets.assets_combat_stamina_status import ICON_SEARCH, IMMERSIFIER_ICON
from tasks.dungeon.assets.assets_dungeon_stamina import *
from tasks.dungeon.keywords import KEYWORDS_DUNGEON_TAB
from tasks.dungeon.ui import DungeonUI
@ -16,6 +17,7 @@ class DungeonStamina(DungeonUI):
out: IMMERSIFIER_CHECK
"""
logger.info('Enter immersifier')
IMMERSIFIER_ICON.load_search(ICON_SEARCH.area)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
@ -24,7 +26,7 @@ class DungeonStamina(DungeonUI):
if self.appear(IMMERSIFIER_CHECK):
break
if self.appear_then_click(ENTER_IMMERSIFIER, interval=2):
if self.appear_then_click(IMMERSIFIER_ICON, interval=2):
continue
def _immersifier_exit(self, skip_first_screenshot=True):
@ -140,7 +142,7 @@ class DungeonStamina(DungeonUI):
logger.hr('Immersifier store', level=2)
logger.info(f'Max store: {max_store}')
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index)
self.dungeon_update_stamina()
self.update_stamina_status()
before = self.config.stored.Immersifier.value
if self.config.stored.Immersifier.is_full():
@ -159,7 +161,7 @@ class DungeonStamina(DungeonUI):
self._immersifier_enter()
self._item_amount_set(amount, ocr_button=OCR_IMMERSIFIER_AMOUNT)
self._item_confirm()
self.dungeon_update_stamina()
self.update_stamina_status()
diff = self.config.stored.Immersifier.value - before
logger.info(f'Stored {diff} immersifiers')
return diff

View File

@ -1,14 +1,13 @@
from datetime import timedelta
from module.base.base import ModuleBase
from module.base.timer import Timer
from module.base.utils import crop
from module.config.stored.classes import now
from module.config.utils import DEFAULT_TIME, get_server_next_monday_update, get_server_next_update
from module.logger import logger
from module.ocr.ocr import DigitCounter
from tasks.base.ui import UI
from tasks.dungeon.assets.assets_dungeon_state import OCR_SIMUNI_POINT, OCR_SIMUNI_POINT_OFFSET, OCR_STAMINA
from tasks.combat.stamina_status import StaminaStatus
from tasks.dungeon.assets.assets_dungeon_state import OCR_SIMUNI_POINT, OCR_SIMUNI_POINT_OFFSET
from tasks.dungeon.keywords import DungeonList
@ -19,7 +18,7 @@ class OcrSimUniPoint(DigitCounter):
return result
class DungeonState(UI):
class DungeonState(StaminaStatus):
def dungeon_get_simuni_point(self, image=None) -> int:
"""
Page:
@ -48,67 +47,6 @@ class DungeonState(UI):
logger.warning(f'Invalid SimulatedUniverse points: {value}/{total}')
return 0
def dungeon_update_stamina(self, image=None, skip_first_screenshot=True):
"""
Returns:
bool: If success
Pages:
in: page_guild, Survival_Index, Simulated_Universe
or page_rogue, LEVEL_CONFIRM
or rogue, REWARD_CLOSE
"""
ocr = DigitCounter(OCR_STAMINA)
timeout = Timer(1, count=2).start()
if image is None:
image = self.device.image
use_cached_image = False
else:
skip_first_screenshot = True
use_cached_image = True
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
image = self.device.image
stamina = (0, 0, 0)
immersifier = (0, 0, 0)
if timeout.reached():
logger.warning('dungeon_update_stamina() timeout')
return False
for row in ocr.detect_and_ocr(image):
if row.ocr_text.isdigit():
continue
if row.ocr_text == '+':
continue
if not ocr.is_format_matched(row.ocr_text):
continue
data = ocr.format_result(row.ocr_text)
if data[2] == self.config.stored.TrailblazePower.FIXED_TOTAL:
stamina = data
if data[2] == self.config.stored.Immersifier.FIXED_TOTAL:
immersifier = data
if stamina[2] > 0 and immersifier[2] > 0:
break
if use_cached_image:
logger.info('dungeon_update_stamina() ended')
return
stamina = stamina[0]
immersifier = immersifier[0]
logger.attr('TrailblazePower', stamina)
logger.attr('Imersifier', immersifier)
with self.config.multi_set():
self.config.stored.TrailblazePower.value = stamina
self.config.stored.Immersifier.value = immersifier
return True
def dungeon_update_simuni(self):
"""
Update rogue weekly points, stamina, immersifier
@ -122,8 +60,8 @@ class DungeonState(UI):
def func(image):
logger.info('Update thread start')
with self.config.multi_set():
self.dungeon_get_simuni_point(image)
self.dungeon_update_stamina(image)
# self.dungeon_get_simuni_point(image)
self.update_stamina_status(image)
ModuleBase.worker.submit(func, self.device.image)

View File

@ -578,6 +578,7 @@ class DungeonUI(DungeonState):
DUNGEON_LIST.use_plane = bool(dungeon.is_Calyx_Crimson)
# Insight dungeon
DUNGEON_LIST.insight_row(dungeon, main=self)
self.device.click_record_clear()
# Check if dungeon unlocked
for entrance in DUNGEON_LIST.navigates:
entrance: OcrResultButton = entrance
@ -596,6 +597,7 @@ class DungeonUI(DungeonState):
DUNGEON_LIST.drag_vector = (0.2, 0.4)
DUNGEON_LIST.limit_entrance = True
DUNGEON_LIST.insight_row(dungeon, main=self)
self.device.click_record_clear()
DUNGEON_LIST.drag_vector = DraggableList.drag_vector
DUNGEON_LIST.limit_entrance = False
DUNGEON_LIST.load_rows(main=self)

View File

@ -130,7 +130,7 @@ class OrnamentCombat(Dungeon, RouteLoader, DungeonState):
after = before
for _ in range(3):
self.dungeon_update_stamina()
self.update_stamina_status()
after = self.get_equivalent_stamina()
if expect_reduce:
if before > after:

View File

@ -261,7 +261,7 @@ class RogueEntry(RouteBase, RogueRewardHandler, RoguePathHandler, DungeonRogueUI
if not self.image_color_count(LEVEL_CONFIRM, color=(223, 223, 225), threshold=240, count=50):
self.interval_clear(LEVEL_CONFIRM)
continue
self.dungeon_update_stamina()
self.update_stamina_status()
self.check_stop_condition()
self.device.click(LEVEL_CONFIRM)
continue

View File

@ -54,7 +54,7 @@ class RogueReward(RogueUI, CombatInteract, DungeonState):
confirm.reset()
continue
if self.appear(REWARD_CLOSE, interval=2):
self.dungeon_update_stamina()
self.update_stamina_status()
if not init:
initial_stamina = self.config.stored.TrailblazePower.value
initial_immersifier = self.config.stored.Immersifier.value