1
0
mirror of https://gitee.com/sui-feng-cb/AzurLaneAutoScript1 synced 2026-03-09 18:39:04 +08:00

Add: 15-1 daemon

Add: plane statistics method

Upd: config MoveDownTime for 15-1 daemon and battle time in plane statistics

Opt: StopCondition RunCount for daemon 15-1

Opt: save screenshot in quit menu

Fix: use uiautomator2 long_click and pause retry
This commit is contained in:
sui-feng-cb 2025-02-20 22:45:40 +08:00 committed by sui-feng-cb
parent 400d55d9fb
commit 3ed07d0ba1
17 changed files with 487 additions and 0 deletions

View File

@ -418,6 +418,10 @@ class AzurLaneAutoScript:
GemsFarming(config=self.config, device=self.device).run(
name=self.config.Campaign_Name, folder=self.config.Campaign_Event, mode=self.config.Campaign_Mode)
def daemon_15_1(self):
from module.daemon.daemon_15_1 import AzurLaneDaemon
AzurLaneDaemon(config=self.config, device=self.device, task="Daemon_15_1").run()
def daemon(self):
from module.daemon.daemon import AzurLaneDaemon
AzurLaneDaemon(config=self.config, device=self.device, task="Daemon").run()

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -2086,6 +2086,18 @@
"Storage": {}
}
},
"Daemon_15_1": {
"Daemon_15_1": {
"AutoCombat": false,
"QuitTime": "02:00",
"MoveDownTime": 1,
"RunCount": 0,
"QuitScreenshot": false
},
"Storage": {
"Storage": {}
}
},
"Daemon": {
"Daemon": {
"EnterMap": true

View File

@ -0,0 +1,143 @@
import csv
from tqdm import tqdm
from module.base.button import ButtonGrid
from module.base.decorator import cached_property, run_once
from module.base.utils import load_image
from module.combat.assets import BATTLE_TIME
from module.daemon.daemon_15_1 import BattleTime as BattleTime_
from module.logger import logger
from module.ocr.al_ocr import AlOcr
from module.ocr.ocr import Ocr, Digit
from module.statistics.utils import *
class BattleTime(BattleTime_):
@staticmethod
def parse_time(string):
return string
class PlaneOcr(Digit):
def __init__(self, buttons, lang='azur_lane', letter=(255, 251, 247), threshold=128, alphabet='0123456789IDSBX',
name=None):
super().__init__(buttons, lang=lang, letter=letter, threshold=threshold, alphabet=alphabet, name=name)
def after_process(self, result):
result = result.replace('X', '') if 'X' in result else '0'
result = super().after_process(result)
return result
class PlaneStatistics:
DROP_FOLDER = './screenshots'
CNOCR_CONTEXT = 'cpu'
CSV_FILE = 'drop_result.csv'
CSV_OVERWRITE = True
CSV_ENCODING = 'utf-8'
PLANE_ROWS = 7
PLANE_GRID = ButtonGrid(origin=(1230, 90), button_shape=(48, 30), grid_shape=(1, 7), delta=(0, 43))
def __init__(self):
AlOcr.CNOCR_CONTEXT = PlaneStatistics.CNOCR_CONTEXT
Ocr.SHOW_LOG = False
self.PLANE_GRID.grid_shape = (1, self.PLANE_ROWS)
self.place_ocr_model = PlaneOcr(self.PLANE_GRID.buttons)
self.time_ocr_model = BattleTime(BATTLE_TIME)
@property
def csv_file(self):
return os.path.join(PlaneStatistics.DROP_FOLDER, PlaneStatistics.CSV_FILE)
@staticmethod
def drop_folder(campaign):
return os.path.join(PlaneStatistics.DROP_FOLDER, campaign)
@cached_property
def csv_overwrite_check(self):
"""
Remove existing csv file. This method only run once.
"""
if PlaneStatistics.CSV_OVERWRITE:
if os.path.exists(self.csv_file):
logger.info(f'Remove existing csv file: {self.csv_file}')
os.remove(self.csv_file)
return True
@staticmethod
@run_once
def csv_write_column_name(writer, columns):
writer.writerows([columns])
def parse_plane(self, file):
ts = os.path.splitext(os.path.basename(file))[0]
campaign = os.path.basename(os.path.abspath(os.path.join(file, '../')))
campaign = campaign.replace('campaign_', '')
images = unpack(load_image(file))
image = images[0]
plane = self.place_ocr_model.ocr(image)
time = self.time_ocr_model.ocr(image)
yield ['\'' + ts, campaign, str(plane), sum(plane), time]
def extract_plane(self, campaign):
"""
Extract images from a given folder.
Args:
campaign (str):
"""
print('')
logger.hr(f'extract plane statistics from {campaign}', level=1)
_ = self.csv_overwrite_check
with open(self.csv_file, 'a', newline='', encoding=PlaneStatistics.CSV_ENCODING) as csv_file:
writer = csv.writer(csv_file)
self.csv_write_column_name(writer, ['截图文件名称', '关卡名称', 'ocr识别结果', '飞机总数', '右上角时间'])
for ts, file in tqdm(load_folder(self.drop_folder(campaign)).items()):
try:
rows = self.parse_plane(file)
writer.writerows(rows)
except ImageError as e:
logger.warning(e)
continue
except Exception as e:
logger.exception(e)
logger.warning(f'Error on image {ts}')
continue
if __name__ == '__main__':
# Drop screenshot folder. Default to './screenshots'
# 截图文件夹名称
PlaneStatistics.DROP_FOLDER = './screenshots'
# 'cpu' or 'gpu', default to 'cpu'.
# Use 'gpu' for faster prediction, but you must have the gpu version of mxnet installed.
PlaneStatistics.CNOCR_CONTEXT = 'cpu'
# Name of the output csv file.
# This will write to {DROP_FOLDER}/{CSV_FILE}.
# 结果文件名称
PlaneStatistics.CSV_FILE = 'plane_result.csv'
# If True, remove existing file before extraction.
# 是否覆写csv
PlaneStatistics.CSV_OVERWRITE = True
# Usually to be 'utf-8'.
# For better Chinese export to Excel, use 'gbk'.
PlaneStatistics.CSV_ENCODING = 'gbk'
# Default to be 7.
# This will change the ocr of rows of plane.
# 飞机识别行数(最多有几行飞机)
PlaneStatistics.PLANE_ROWS = 9
# campaign names to export under DROP_FOLDER.
# This will load {DROP_FOLDER}/{CAMPAIGN}.
# Just a demonstration here, you should modify it to your own.
# 关卡名称(截图文件夹内的需要识别图片的文件夹的名称)
CAMPAIGNS = ['campaign_15_1']
stat = PlaneStatistics()
"""
Step 1:
Run this code.
"""
for i in CAMPAIGNS:
stat.extract_plane(i)

View File

@ -17,6 +17,7 @@ BATTLE_STATUS_B = Button(area={'cn': (625, 297, 712, 317), 'en': (625, 297, 712,
BATTLE_STATUS_C = Button(area={'cn': (625, 211, 647, 297), 'en': (625, 211, 647, 297), 'jp': (625, 211, 647, 297), 'tw': (625, 211, 647, 297)}, color={'cn': (199, 208, 198), 'en': (199, 208, 198), 'jp': (199, 208, 198), 'tw': (199, 208, 198)}, button={'cn': (1000, 631, 1055, 689), 'en': (1000, 631, 1055, 689), 'jp': (1000, 631, 1055, 689), 'tw': (1000, 631, 1055, 689)}, file={'cn': './assets/cn/combat/BATTLE_STATUS_C.png', 'en': './assets/en/combat/BATTLE_STATUS_C.png', 'jp': './assets/jp/combat/BATTLE_STATUS_C.png', 'tw': './assets/tw/combat/BATTLE_STATUS_C.png'})
BATTLE_STATUS_D = Button(area={'cn': (618, 191, 639, 317), 'en': (618, 191, 639, 317), 'jp': (618, 191, 639, 317), 'tw': (618, 191, 639, 317)}, color={'cn': (199, 208, 199), 'en': (199, 208, 199), 'jp': (199, 208, 199), 'tw': (199, 208, 199)}, button={'cn': (1000, 631, 1055, 689), 'en': (1000, 631, 1055, 689), 'jp': (1000, 631, 1055, 689), 'tw': (1000, 631, 1055, 689)}, file={'cn': './assets/cn/combat/BATTLE_STATUS_D.png', 'en': './assets/en/combat/BATTLE_STATUS_D.png', 'jp': './assets/jp/combat/BATTLE_STATUS_D.png', 'tw': './assets/tw/combat/BATTLE_STATUS_D.png'})
BATTLE_STATUS_S = Button(area={'cn': (643, 297, 722, 317), 'en': (643, 297, 722, 317), 'jp': (643, 297, 722, 317), 'tw': (643, 297, 722, 317)}, color={'cn': (233, 242, 127), 'en': (233, 242, 127), 'jp': (233, 242, 127), 'tw': (233, 242, 127)}, button={'cn': (1000, 631, 1055, 689), 'en': (999, 630, 1047, 691), 'jp': (1000, 631, 1055, 689), 'tw': (1000, 631, 1055, 689)}, file={'cn': './assets/cn/combat/BATTLE_STATUS_S.png', 'en': './assets/en/combat/BATTLE_STATUS_S.png', 'jp': './assets/jp/combat/BATTLE_STATUS_S.png', 'tw': './assets/tw/combat/BATTLE_STATUS_S.png'})
BATTLE_TIME = Button(area={'cn': (1062, 36, 1119, 59), 'en': (1062, 36, 1119, 59), 'jp': (1062, 36, 1119, 59), 'tw': (1062, 36, 1119, 59)}, color={'cn': (98, 158, 99), 'en': (98, 158, 99), 'jp': (98, 158, 99), 'tw': (98, 158, 99)}, button={'cn': (1062, 36, 1119, 59), 'en': (1062, 36, 1119, 59), 'jp': (1062, 36, 1119, 59), 'tw': (1062, 36, 1119, 59)}, file={'cn': './assets/cn/combat/BATTLE_TIME.png', 'en': './assets/cn/combat/BATTLE_TIME.png', 'jp': './assets/cn/combat/BATTLE_TIME.png', 'tw': './assets/cn/combat/BATTLE_TIME.png'})
COMBAT_AUTO = Button(area={'cn': (136, 573, 167, 604), 'en': (136, 573, 167, 604), 'jp': (136, 573, 167, 604), 'tw': (136, 573, 167, 604)}, color={'cn': (229, 242, 255), 'en': (229, 242, 255), 'jp': (229, 242, 255), 'tw': (229, 242, 255)}, button={'cn': (136, 573, 167, 604), 'en': (136, 573, 167, 604), 'jp': (136, 573, 167, 604), 'tw': (136, 573, 167, 604)}, file={'cn': './assets/cn/combat/COMBAT_AUTO.png', 'en': './assets/en/combat/COMBAT_AUTO.png', 'jp': './assets/jp/combat/COMBAT_AUTO.png', 'tw': './assets/tw/combat/COMBAT_AUTO.png'})
COMBAT_AUTO_133 = Button(area={'cn': (131, 568, 170, 609), 'en': (131, 568, 170, 609), 'jp': (131, 568, 170, 609), 'tw': (131, 568, 170, 609)}, color={'cn': (234, 244, 255), 'en': (234, 244, 255), 'jp': (234, 244, 255), 'tw': (234, 244, 255)}, button={'cn': (131, 568, 170, 609), 'en': (131, 568, 170, 609), 'jp': (131, 568, 170, 609), 'tw': (131, 568, 170, 609)}, file={'cn': './assets/cn/combat/COMBAT_AUTO_133.png', 'en': './assets/en/combat/COMBAT_AUTO_133.png', 'jp': './assets/jp/combat/COMBAT_AUTO_133.png', 'tw': './assets/tw/combat/COMBAT_AUTO_133.png'})
COMBAT_AUTO_150 = Button(area={'cn': (129, 567, 172, 611), 'en': (129, 567, 172, 611), 'jp': (129, 567, 172, 611), 'tw': (129, 567, 172, 611)}, color={'cn': (238, 247, 255), 'en': (238, 247, 255), 'jp': (238, 247, 255), 'tw': (238, 247, 255)}, button={'cn': (129, 567, 172, 611), 'en': (129, 567, 172, 611), 'jp': (129, 567, 172, 611), 'tw': (129, 567, 172, 611)}, file={'cn': './assets/cn/combat/COMBAT_AUTO_150.png', 'en': './assets/en/combat/COMBAT_AUTO_150.png', 'jp': './assets/jp/combat/COMBAT_AUTO_150.png', 'tw': './assets/tw/combat/COMBAT_AUTO_150.png'})
@ -38,6 +39,7 @@ GET_SHIP = Button(area={'cn': (1104, 610, 1110, 630), 'en': (1104, 610, 1110, 63
LOADING_BAR = Button(area={'cn': (33, 676, 1247, 680), 'en': (33, 676, 1247, 680), 'jp': (33, 676, 1247, 680), 'tw': (33, 676, 1247, 680)}, color={'cn': (172, 205, 232), 'en': (172, 205, 232), 'jp': (172, 205, 232), 'tw': (172, 205, 232)}, button={'cn': (33, 676, 1247, 680), 'en': (33, 676, 1247, 680), 'jp': (33, 676, 1247, 680), 'tw': (33, 676, 1247, 680)}, file={'cn': './assets/cn/combat/LOADING_BAR.png', 'en': './assets/en/combat/LOADING_BAR.png', 'jp': './assets/jp/combat/LOADING_BAR.png', 'tw': './assets/tw/combat/LOADING_BAR.png'})
MAIN_FLEET_POWER_ZERO = Button(area={'cn': (131, 151, 232, 206), 'en': (131, 151, 232, 206), 'jp': (131, 151, 232, 206), 'tw': (131, 151, 232, 206)}, color={'cn': (63, 79, 98), 'en': (63, 79, 98), 'jp': (63, 79, 98), 'tw': (63, 79, 98)}, button={'cn': (131, 151, 232, 206), 'en': (131, 151, 232, 206), 'jp': (131, 151, 232, 206), 'tw': (131, 151, 232, 206)}, file={'cn': './assets/cn/combat/MAIN_FLEET_POWER_ZERO.png', 'en': './assets/en/combat/MAIN_FLEET_POWER_ZERO.png', 'jp': './assets/jp/combat/MAIN_FLEET_POWER_ZERO.png', 'tw': './assets/tw/combat/MAIN_FLEET_POWER_ZERO.png'})
MOVE_DOWN = Button(area={'cn': (148, 647, 155, 669), 'en': (148, 647, 155, 669), 'jp': (148, 647, 155, 669), 'tw': (148, 647, 155, 669)}, color={'cn': (21, 28, 57), 'en': (21, 28, 57), 'jp': (21, 28, 57), 'tw': (21, 28, 57)}, button={'cn': (148, 647, 155, 669), 'en': (148, 647, 155, 669), 'jp': (148, 647, 155, 669), 'tw': (148, 647, 155, 669)}, file={'cn': './assets/cn/combat/MOVE_DOWN.png', 'en': './assets/en/combat/MOVE_DOWN.png', 'jp': './assets/jp/combat/MOVE_DOWN.png', 'tw': './assets/tw/combat/MOVE_DOWN.png'})
MOVE_LEFT = Button(area={'cn': (3, 585, 27, 591), 'en': (3, 585, 27, 591), 'jp': (3, 585, 27, 591), 'tw': (3, 585, 27, 591)}, color={'cn': (35, 108, 156), 'en': (35, 108, 156), 'jp': (35, 108, 156), 'tw': (35, 108, 156)}, button={'cn': (3, 585, 27, 591), 'en': (3, 585, 27, 591), 'jp': (3, 585, 27, 591), 'tw': (3, 585, 27, 591)}, file={'cn': './assets/cn/combat/MOVE_LEFT.png', 'en': './assets/cn/combat/MOVE_LEFT.png', 'jp': './assets/cn/combat/MOVE_LEFT.png', 'tw': './assets/cn/combat/MOVE_LEFT.png'})
MOVE_LEFT_DOWN = Button(area={'cn': (67, 668, 112, 707), 'en': (67, 668, 112, 707), 'jp': (67, 668, 112, 707), 'tw': (67, 668, 112, 707)}, color={'cn': (65, 80, 100), 'en': (65, 80, 100), 'jp': (65, 80, 100), 'tw': (65, 80, 100)}, button={'cn': (67, 668, 112, 707), 'en': (67, 668, 112, 707), 'jp': (67, 668, 112, 707), 'tw': (67, 668, 112, 707)}, file={'cn': './assets/cn/combat/MOVE_LEFT_DOWN.png', 'en': './assets/en/combat/MOVE_LEFT_DOWN.png', 'jp': './assets/jp/combat/MOVE_LEFT_DOWN.png', 'tw': './assets/tw/combat/MOVE_LEFT_DOWN.png'})
NEW_SHIP = Button(area={'cn': (206, 87, 213, 93), 'en': (206, 87, 213, 93), 'jp': (206, 87, 213, 93), 'tw': (206, 87, 213, 93)}, color={'cn': (235, 171, 60), 'en': (235, 171, 60), 'jp': (235, 171, 60), 'tw': (235, 171, 60)}, button={'cn': (206, 87, 213, 93), 'en': (206, 87, 213, 93), 'jp': (206, 87, 213, 93), 'tw': (206, 87, 213, 93)}, file={'cn': './assets/cn/combat/NEW_SHIP.png', 'en': './assets/en/combat/NEW_SHIP.png', 'jp': './assets/jp/combat/NEW_SHIP.png', 'tw': './assets/tw/combat/NEW_SHIP.png'})
OPTS_INFO_D = Button(area={'cn': (601, 151, 704, 178), 'en': (565, 143, 692, 179), 'jp': (512, 154, 605, 176), 'tw': (602, 152, 702, 177)}, color={'cn': (158, 110, 113), 'en': (171, 116, 110), 'jp': (201, 187, 191), 'tw': (164, 130, 137)}, button={'cn': (583, 605, 677, 628), 'en': (590, 587, 627, 647), 'jp': (574, 596, 685, 635), 'tw': (583, 604, 676, 627)}, file={'cn': './assets/cn/combat/OPTS_INFO_D.png', 'en': './assets/en/combat/OPTS_INFO_D.png', 'jp': './assets/jp/combat/OPTS_INFO_D.png', 'tw': './assets/tw/combat/OPTS_INFO_D.png'})

View File

@ -9982,6 +9982,38 @@
}
}
},
"Daemon_15_1": {
"Daemon_15_1": {
"AutoCombat": {
"type": "checkbox",
"value": false
},
"QuitTime": {
"type": "input",
"value": "02:00"
},
"MoveDownTime": {
"type": "input",
"value": 1
},
"RunCount": {
"type": "input",
"value": 0
},
"QuitScreenshot": {
"type": "checkbox",
"value": false
}
},
"Storage": {
"Storage": {
"type": "storage",
"value": {},
"valuetype": "ignore",
"display": "disabled"
}
}
},
"Daemon": {
"Daemon": {
"EnterMap": {

View File

@ -779,6 +779,12 @@ OpsiHazard1Leveling:
# ==================== Tools ====================
Daemon_15_1:
AutoCombat: false
QuitTime: 02:00
MoveDownTime: 1
RunCount: 0
QuitScreenshot: false
Daemon:
EnterMap: true
OpsiDaemon:

View File

@ -101,6 +101,7 @@
"menu": "collapse",
"page": "tool",
"tasks": [
"Daemon_15_1",
"Daemon",
"OpsiDaemon",
"EventStory",

View File

@ -338,6 +338,8 @@ Tool:
menu: 'collapse'
page: 'tool'
tasks:
Daemon_15_1:
- Daemon_15_1
Daemon:
- Daemon
OpsiDaemon:

View File

@ -460,6 +460,13 @@ class GeneratedConfig:
OpsiHazard1Leveling_OperationCoinsPreserve = 100000
OpsiHazard1Leveling_DoScanningDevice = False
# Group `Daemon_15_1`
Daemon_15_1_AutoCombat = False
Daemon_15_1_QuitTime = '02:00'
Daemon_15_1_MoveDownTime = 1
Daemon_15_1_RunCount = 0
Daemon_15_1_QuitScreenshot = False
# Group `Daemon`
Daemon_EnterMap = True

View File

@ -258,6 +258,10 @@
"name": "Cross Month Daily",
"help": " ALAS will enter OpSi 10min before OpSi reset, wait until OpSi reset but not exit OpSi. Then do the daily, obscure, abyssal and meowfficer farming to get extra gold plates. When running dailies, settings in task \"OpSiDaily\" are used, the rest function are the same.\n IMPORTANT: Please do not touch the game while ALAS is waiting for OpSi reset."
},
"Daemon_15_1": {
"name": "Task.Daemon_15_1.name",
"help": "Task.Daemon_15_1.help"
},
"Daemon": {
"name": "Normal Semi-auto",
"help": ""
@ -2656,6 +2660,32 @@
"help": "Exchange purple coins to operation coins, which may cause a shortage of purple coins"
}
},
"Daemon_15_1": {
"_info": {
"name": "Daemon_15_1._info.name",
"help": "Daemon_15_1._info.help"
},
"AutoCombat": {
"name": "Daemon_15_1.AutoCombat.name",
"help": "Daemon_15_1.AutoCombat.help"
},
"QuitTime": {
"name": "Daemon_15_1.QuitTime.name",
"help": "Daemon_15_1.QuitTime.help"
},
"MoveDownTime": {
"name": "Daemon_15_1.MoveDownTime.name",
"help": "Daemon_15_1.MoveDownTime.help"
},
"RunCount": {
"name": "Daemon_15_1.RunCount.name",
"help": "Daemon_15_1.RunCount.help"
},
"QuitScreenshot": {
"name": "Daemon_15_1.QuitScreenshot.name",
"help": "Daemon_15_1.QuitScreenshot.help"
}
},
"Daemon": {
"_info": {
"name": "Semi-auto Clicking",

View File

@ -258,6 +258,10 @@
"name": "Cross Month Daily",
"help": " ALAS will enter OpSi 10min before OpSi reset, wait until OpSi reset but not exit OpSi. Then do the daily, obscure, abyssal and meowfficer farming to get extra gold plates. When running dailies, settings in task \"OpSiDaily\" are used, the rest function are the same.\n IMPORTANT: Please do not touch the game while ALAS is waiting for OpSi reset."
},
"Daemon_15_1": {
"name": "Task.Daemon_15_1.name",
"help": "Task.Daemon_15_1.help"
},
"Daemon": {
"name": "半自動クリック",
"help": ""
@ -2656,6 +2660,32 @@
"help": "OpsiHazard1Leveling.DoScanningDevice.help"
}
},
"Daemon_15_1": {
"_info": {
"name": "Daemon_15_1._info.name",
"help": "Daemon_15_1._info.help"
},
"AutoCombat": {
"name": "Daemon_15_1.AutoCombat.name",
"help": "Daemon_15_1.AutoCombat.help"
},
"QuitTime": {
"name": "Daemon_15_1.QuitTime.name",
"help": "Daemon_15_1.QuitTime.help"
},
"MoveDownTime": {
"name": "Daemon_15_1.MoveDownTime.name",
"help": "Daemon_15_1.MoveDownTime.help"
},
"RunCount": {
"name": "Daemon_15_1.RunCount.name",
"help": "Daemon_15_1.RunCount.help"
},
"QuitScreenshot": {
"name": "Daemon_15_1.QuitScreenshot.name",
"help": "Daemon_15_1.QuitScreenshot.help"
}
},
"Daemon": {
"_info": {
"name": "Daemon._info.name",

View File

@ -258,6 +258,10 @@
"name": "跨月每日",
"help": " Alas将在大世界跨月重置之前10分钟进入大世界等待大世界重置但不退出大世界然后完成新一天的大世界每日、隐秘海域、深渊海域和短猫相接以获得额外的金菜。运行大世界每日时按\"大世界每日\"任务设置运行,其余同理。\n 重要Alas等待跨月期间请不要操作游戏。"
},
"Daemon_15_1": {
"name": "15-1测试",
"help": ""
},
"Daemon": {
"name": "半自动点击",
"help": ""
@ -2656,6 +2660,32 @@
"help": "消耗紫币换取黄币,可能会导致紫币不足"
}
},
"Daemon_15_1": {
"_info": {
"name": "15-1支援舰队测试用",
"help": "配好队,关闭自律,进图后关闭阵容锁定,手动选择一个需要测试的敌人,然后运行该任务\n注意只能使用经典战斗ui老战斗ui\n使用结束后需要手动关闭此任务"
},
"AutoCombat": {
"name": "使用自律战斗",
"help": ""
},
"QuitTime": {
"name": "截图并退出关卡的时间",
"help": "如2:00, 02:30等"
},
"MoveDownTime": {
"name": "非自律开局向下拉的时间(秒)",
"help": "在非自律战斗下,开局按向下按键的时长(秒)\n根据前排航速的不同手动修改这个值确保前排在旗舰头像下方例如\n1s埃吉尔 岛风 安克雷奇\n1.5s:埃吉尔 圣女贞德 安克雷奇"
},
"RunCount": {
"name": "测试次数大于 X 后停止",
"help": "每运行一次,次数减一,归零后停止任务\n0 表示不限制次数"
},
"QuitScreenshot": {
"name": "保存退出界面截图",
"help": "开启后,会额外保存一份退出界面时的截图,以供测试使用\n大部分情况不需要开启这个选项"
}
},
"Daemon": {
"_info": {
"name": "半自动点击",

View File

@ -258,6 +258,10 @@
"name": "跨月每日",
"help": " Alas將在大世界跨月重置之前10分鐘進入大世界等待大世界重置但不退出大世界然後完成新一天的大世界每日、隱秘海域、深淵海域和短貓相接以獲得額外的金菜。運行大世界每日時按\"大世界每日\"任務設定運行,其餘同理。\n 重要Alas等待跨月期間請不要操作遊戲。"
},
"Daemon_15_1": {
"name": "Task.Daemon_15_1.name",
"help": "Task.Daemon_15_1.help"
},
"Daemon": {
"name": "半自動點擊",
"help": ""
@ -2656,6 +2660,32 @@
"help": "消耗紫幣換取黃幣,可能會導致紫幣不足"
}
},
"Daemon_15_1": {
"_info": {
"name": "Daemon_15_1._info.name",
"help": "Daemon_15_1._info.help"
},
"AutoCombat": {
"name": "Daemon_15_1.AutoCombat.name",
"help": "Daemon_15_1.AutoCombat.help"
},
"QuitTime": {
"name": "Daemon_15_1.QuitTime.name",
"help": "Daemon_15_1.QuitTime.help"
},
"MoveDownTime": {
"name": "Daemon_15_1.MoveDownTime.name",
"help": "Daemon_15_1.MoveDownTime.help"
},
"RunCount": {
"name": "Daemon_15_1.RunCount.name",
"help": "Daemon_15_1.RunCount.help"
},
"QuitScreenshot": {
"name": "Daemon_15_1.QuitScreenshot.name",
"help": "Daemon_15_1.QuitScreenshot.help"
}
},
"Daemon": {
"_info": {
"name": "半自動點擊",

View File

@ -0,0 +1,157 @@
from datetime import datetime, timedelta
import re
from module.base.timer import Timer
from module.base.utils import copy_image
from module.campaign.campaign_base import CampaignBase
from module.combat.assets import BATTLE_TIME, MOVE_DOWN, MOVE_LEFT
from module.combat_ui.assets import QUIT
from module.daemon.daemon_base import DaemonBase
from module.exception import CampaignEnd
from module.exercise.assets import QUIT_RECONFIRM
from module.handler.ambush import MAP_AMBUSH_EVADE
from module.logger import logger
from module.map.assets import MAP_OFFENSIVE
from module.ocr.ocr import Duration
class BattleTime(Duration):
SHOW_LOG = False
def __init__(self, buttons, lang='azur_lane', letter=(148, 255, 99), threshold=128, alphabet='0123456789:IDSB',
name=None):
super().__init__(buttons, lang=lang, letter=letter, threshold=threshold, alphabet=alphabet, name=name)
@staticmethod
def parse_time(string):
result = re.search(r'(\d{1,2}):?(\d{2})', string)
if result:
result = [int(s) for s in result.groups()]
return timedelta(hours=0, minutes=result[0], seconds=result[1])
else:
logger.warning(f'Invalid duration: {string}')
return timedelta(hours=0, minutes=0, seconds=0)
class AzurLaneDaemon(DaemonBase, CampaignBase):
battle_time_ocr_model = BattleTime(BATTLE_TIME)
@property
def battle_time(self):
return self.battle_time_ocr_model.ocr(self.device.image).total_seconds()
@property
def quit_time(self):
string = self.config.Daemon_15_1_QuitTime
string = string.strip().replace('', ':')
t = datetime.strptime(string, "%M:%S")
return timedelta(hours=t.hour, minutes=t.minute, seconds=t.second).total_seconds()
def run(self):
move = True
is_limit = False
end = False
self.device.screenshot_interval_set()
self.config.override(Emulator_ControlMethod='uiautomator2')
while 1:
self.device.screenshot()
# End
if is_limit and self.config.Daemon_15_1_RunCount <= 0:
logger.hr('Triggered stop condition: Run count')
self.config.Daemon_15_1_RunCount = 0
end = True
is_limit = self.config.Daemon_15_1_RunCount
pause = self.is_combat_executing()
# running a combat
if pause:
if not self.config.Daemon_15_1_AutoCombat and move:
move = False
self.device.long_click(MOVE_DOWN, duration=self.config.Daemon_15_1_MoveDownTime)
self.device.long_click(MOVE_LEFT, duration=(3, 4))
continue
# End
battle_time = self.battle_time
if battle_time and battle_time <= self.quit_time:
with self.stat.new(genre='campaign_15_1', method='save') as record:
if self.config.Daemon_15_1_RunCount:
self.config.Daemon_15_1_RunCount -= 1
combat_image = copy_image(self.device.image)
self.device.screenshot_interval_set()
skip_first_screenshot = True
pause_interval = Timer(0.5, count=1)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if pause_interval.reached():
pause = self.is_combat_executing()
if pause:
self.device.click(pause)
pause_interval.reset()
continue
if QUIT.match_luma(self.device.image, offset=(20, 20)):
record.add(combat_image)
if self.config.Daemon_15_1_QuitScreenshot:
record.add(self.device.image)
break
continue
# Quit
if self.handle_combat_quit():
continue
if self.appear_then_click(QUIT_RECONFIRM, offset=(20, 20), interval=5):
move = True
if end:
break
continue
# Combat
if self.combat_appear():
self.combat_preparation(auto='combat_auto' if self.config.Daemon_15_1_AutoCombat else '')
try:
if self.handle_battle_status():
self.combat_status(expected_end='no_searching')
continue
except CampaignEnd:
continue
# Map operation
if self.appear_then_click(MAP_AMBUSH_EVADE, offset=(20, 20)):
self.device.sleep(1)
continue
if self.handle_mystery_items():
continue
# Retire
if self.handle_retirement():
continue
# Emotion
pass
# Urgent commission
if self.handle_urgent_commission():
continue
# Popups
if self.handle_guild_popup_cancel():
return True
if self.handle_vote_popup():
continue
# Story
if self.story_skip():
continue
# Map Offensive
if not end and self.appear_then_click(MAP_OFFENSIVE, interval=2):
continue
return True

View File

@ -15,6 +15,7 @@ MOD_CONFIG_DICT = {}
def get_available_func():
return (
'Daemon_15_1',
'Daemon',
'OpsiDaemon',
'EventStory',