1
0
mirror of https://github.com/sui-feng-cb/AzurLaneAutoScript1.git synced 2026-06-15 06:32:11 +08:00

Refractor: island transport preparation

This commit is contained in:
2025-10-15 03:45:59 +08:00
parent 91ed9f7b52
commit a0261d62ac
5 changed files with 442 additions and 693 deletions

View File

@@ -16,6 +16,8 @@ ISLAND_TRANSPORT = Button(area={'cn': (905, 335, 986, 358), 'en': (905, 335, 986
ISLAND_TRANSPORT_CHECK = Button(area={'cn': (264, 154, 317, 180), 'en': (264, 154, 317, 180), 'jp': (264, 154, 317, 180), 'tw': (264, 154, 317, 180)}, color={'cn': (142, 143, 143), 'en': (142, 143, 143), 'jp': (142, 143, 143), 'tw': (142, 143, 143)}, button={'cn': (264, 154, 317, 180), 'en': (264, 154, 317, 180), 'jp': (264, 154, 317, 180), 'tw': (264, 154, 317, 180)}, file={'cn': './assets/cn/island/ISLAND_TRANSPORT_CHECK.png', 'en': './assets/cn/island/ISLAND_TRANSPORT_CHECK.png', 'jp': './assets/cn/island/ISLAND_TRANSPORT_CHECK.png', 'tw': './assets/cn/island/ISLAND_TRANSPORT_CHECK.png'})
OCR_PRODUCTION_TIME = Button(area={'cn': (714, 611, 808, 631), 'en': (714, 611, 808, 631), 'jp': (714, 611, 808, 631), 'tw': (714, 611, 808, 631)}, color={'cn': (120, 210, 255), 'en': (120, 210, 255), 'jp': (120, 210, 255), 'tw': (120, 210, 255)}, button={'cn': (714, 611, 808, 631), 'en': (714, 611, 808, 631), 'jp': (714, 611, 808, 631), 'tw': (714, 611, 808, 631)}, file={'cn': './assets/cn/island/OCR_PRODUCTION_TIME.png', 'en': './assets/cn/island/OCR_PRODUCTION_TIME.png', 'jp': './assets/cn/island/OCR_PRODUCTION_TIME.png', 'tw': './assets/cn/island/OCR_PRODUCTION_TIME.png'})
OCR_PRODUCTION_TIME_REMAIN = Button(area={'cn': (621, 427, 666, 439), 'en': (621, 427, 666, 439), 'jp': (621, 427, 666, 439), 'tw': (621, 427, 666, 439)}, color={'cn': (196, 203, 205), 'en': (196, 203, 205), 'jp': (196, 203, 205), 'tw': (196, 203, 205)}, button={'cn': (621, 427, 666, 439), 'en': (621, 427, 666, 439), 'jp': (621, 427, 666, 439), 'tw': (621, 427, 666, 439)}, file={'cn': './assets/cn/island/OCR_PRODUCTION_TIME_REMAIN.png', 'en': './assets/cn/island/OCR_PRODUCTION_TIME_REMAIN.png', 'jp': './assets/cn/island/OCR_PRODUCTION_TIME_REMAIN.png', 'tw': './assets/cn/island/OCR_PRODUCTION_TIME_REMAIN.png'})
OCR_TRANSPORT_TIME = Button(area={'cn': (611, 139, 687, 154), 'en': (611, 139, 687, 154), 'jp': (611, 139, 687, 154), 'tw': (611, 139, 687, 154)}, color={'cn': (119, 119, 120), 'en': (119, 119, 120), 'jp': (119, 119, 120), 'tw': (119, 119, 120)}, button={'cn': (611, 139, 687, 154), 'en': (611, 139, 687, 154), 'jp': (611, 139, 687, 154), 'tw': (611, 139, 687, 154)}, file={'cn': './assets/cn/island/OCR_TRANSPORT_TIME.png', 'en': './assets/cn/island/OCR_TRANSPORT_TIME.png', 'jp': './assets/cn/island/OCR_TRANSPORT_TIME.png', 'tw': './assets/cn/island/OCR_TRANSPORT_TIME.png'})
OCR_TRANSPORT_TIME_REMAIN = Button(area={'cn': (753, 210, 842, 230), 'en': (753, 210, 842, 230), 'jp': (753, 210, 842, 230), 'tw': (753, 210, 842, 230)}, color={'cn': (252, 203, 127), 'en': (252, 203, 127), 'jp': (252, 203, 127), 'tw': (252, 203, 127)}, button={'cn': (753, 210, 842, 230), 'en': (753, 210, 842, 230), 'jp': (753, 210, 842, 230), 'tw': (753, 210, 842, 230)}, file={'cn': './assets/cn/island/OCR_TRANSPORT_TIME_REMAIN.png', 'en': './assets/cn/island/OCR_TRANSPORT_TIME_REMAIN.png', 'jp': './assets/cn/island/OCR_TRANSPORT_TIME_REMAIN.png', 'tw': './assets/cn/island/OCR_TRANSPORT_TIME_REMAIN.png'})
PROJECT_COMPLETE = Button(area={'cn': (576, 394, 704, 414), 'en': (576, 394, 704, 414), 'jp': (576, 394, 704, 414), 'tw': (576, 394, 704, 414)}, color={'cn': (210, 210, 209), 'en': (210, 210, 209), 'jp': (210, 210, 209), 'tw': (210, 210, 209)}, button={'cn': (580, 533, 762, 584), 'en': (580, 533, 762, 584), 'jp': (580, 533, 762, 584), 'tw': (580, 533, 762, 584)}, file={'cn': './assets/cn/island/PROJECT_COMPLETE.png', 'en': './assets/cn/island/PROJECT_COMPLETE.png', 'jp': './assets/cn/island/PROJECT_COMPLETE.png', 'tw': './assets/cn/island/PROJECT_COMPLETE.png'})
PROJECT_START = Button(area={'cn': (686, 610, 706, 630), 'en': (686, 610, 706, 630), 'jp': (686, 610, 706, 630), 'tw': (686, 610, 706, 630)}, color={'cn': (129, 213, 255), 'en': (129, 213, 255), 'jp': (129, 213, 255), 'tw': (129, 213, 255)}, button={'cn': (494, 599, 1087, 642), 'en': (494, 599, 1087, 642), 'jp': (494, 599, 1087, 642), 'tw': (494, 599, 1087, 642)}, file={'cn': './assets/cn/island/PROJECT_START.png', 'en': './assets/cn/island/PROJECT_START.png', 'jp': './assets/cn/island/PROJECT_START.png', 'tw': './assets/cn/island/PROJECT_START.png'})
ROLE_SELECT_CHECK = Button(area={'cn': (1162, 182, 1214, 205), 'en': (1162, 182, 1214, 205), 'jp': (1162, 182, 1214, 205), 'tw': (1162, 182, 1214, 205)}, color={'cn': (221, 192, 128), 'en': (221, 192, 128), 'jp': (221, 192, 128), 'tw': (221, 192, 128)}, button={'cn': (1162, 182, 1214, 205), 'en': (1162, 182, 1214, 205), 'jp': (1162, 182, 1214, 205), 'tw': (1162, 182, 1214, 205)}, file={'cn': './assets/cn/island/ROLE_SELECT_CHECK.png', 'en': './assets/cn/island/ROLE_SELECT_CHECK.png', 'jp': './assets/cn/island/ROLE_SELECT_CHECK.png', 'tw': './assets/cn/island/ROLE_SELECT_CHECK.png'})
@@ -25,3 +27,8 @@ TEMPLATE_ISLAND_MANJUU = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAN
TEMPLATE_ISLAND_SWITCH = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAND_SWITCH.png', 'en': './assets/cn/island/TEMPLATE_ISLAND_SWITCH.png', 'jp': './assets/cn/island/TEMPLATE_ISLAND_SWITCH.png', 'tw': './assets/cn/island/TEMPLATE_ISLAND_SWITCH.png'})
TEMPLATE_PROJECT_LOCKED = Template(file={'cn': './assets/cn/island/TEMPLATE_PROJECT_LOCKED.png', 'en': './assets/cn/island/TEMPLATE_PROJECT_LOCKED.png', 'jp': './assets/cn/island/TEMPLATE_PROJECT_LOCKED.png', 'tw': './assets/cn/island/TEMPLATE_PROJECT_LOCKED.png'})
TEMPLATE_SLOT_LOCKED = Template(file={'cn': './assets/cn/island/TEMPLATE_SLOT_LOCKED.png', 'en': './assets/cn/island/TEMPLATE_SLOT_LOCKED.png', 'jp': './assets/cn/island/TEMPLATE_SLOT_LOCKED.png', 'tw': './assets/cn/island/TEMPLATE_SLOT_LOCKED.png'})
TRANSPORT_LOCKED = Button(area={'cn': (659, 380, 683, 412), 'en': (659, 380, 683, 412), 'jp': (659, 380, 683, 412), 'tw': (659, 380, 683, 412)}, color={'cn': (192, 192, 190), 'en': (192, 192, 190), 'jp': (192, 192, 190), 'tw': (192, 192, 190)}, button={'cn': (659, 380, 683, 412), 'en': (659, 380, 683, 412), 'jp': (659, 380, 683, 412), 'tw': (659, 380, 683, 412)}, file={'cn': './assets/cn/island/TRANSPORT_LOCKED.png', 'en': './assets/cn/island/TRANSPORT_LOCKED.png', 'jp': './assets/cn/island/TRANSPORT_LOCKED.png', 'tw': './assets/cn/island/TRANSPORT_LOCKED.png'})
TRANSPORT_RECEIVE = Button(area={'cn': (938, 206, 1065, 235), 'en': (938, 206, 1065, 235), 'jp': (938, 206, 1065, 235), 'tw': (938, 206, 1065, 235)}, color={'cn': (76, 195, 255), 'en': (76, 195, 255), 'jp': (76, 195, 255), 'tw': (76, 195, 255)}, button={'cn': (938, 206, 1065, 235), 'en': (938, 206, 1065, 235), 'jp': (938, 206, 1065, 235), 'tw': (938, 206, 1065, 235)}, file={'cn': './assets/cn/island/TRANSPORT_RECEIVE.png', 'en': './assets/cn/island/TRANSPORT_RECEIVE.png', 'jp': './assets/cn/island/TRANSPORT_RECEIVE.png', 'tw': './assets/cn/island/TRANSPORT_RECEIVE.png'})
TRANSPORT_START = Button(area={'cn': (1142, 170, 1174, 281), 'en': (1142, 170, 1174, 281), 'jp': (1142, 170, 1174, 281), 'tw': (1142, 170, 1174, 281)}, color={'cn': (98, 202, 255), 'en': (98, 202, 255), 'jp': (98, 202, 255), 'tw': (98, 202, 255)}, button={'cn': (1142, 170, 1174, 281), 'en': (1142, 170, 1174, 281), 'jp': (1142, 170, 1174, 281), 'tw': (1142, 170, 1174, 281)}, file={'cn': './assets/cn/island/TRANSPORT_START.png', 'en': './assets/cn/island/TRANSPORT_START.png', 'jp': './assets/cn/island/TRANSPORT_START.png', 'tw': './assets/cn/island/TRANSPORT_START.png'})
TRANSPORT_STATUS_PENDING = Button(area={'cn': (459, 138, 522, 156), 'en': (459, 138, 522, 156), 'jp': (459, 138, 522, 156), 'tw': (459, 138, 522, 156)}, color={'cn': (120, 121, 121), 'en': (120, 121, 121), 'jp': (120, 121, 121), 'tw': (120, 121, 121)}, button={'cn': (459, 138, 522, 156), 'en': (459, 138, 522, 156), 'jp': (459, 138, 522, 156), 'tw': (459, 138, 522, 156)}, file={'cn': './assets/cn/island/TRANSPORT_STATUS_PENDING.png', 'en': './assets/cn/island/TRANSPORT_STATUS_PENDING.png', 'jp': './assets/cn/island/TRANSPORT_STATUS_PENDING.png', 'tw': './assets/cn/island/TRANSPORT_STATUS_PENDING.png'})
TRANSPORT_STATUS_RUNNING = Button(area={'cn': (459, 138, 522, 156), 'en': (459, 138, 522, 156), 'jp': (459, 138, 522, 156), 'tw': (459, 138, 522, 156)}, color={'cn': (137, 136, 133), 'en': (137, 136, 133), 'jp': (137, 136, 133), 'tw': (137, 136, 133)}, button={'cn': (459, 138, 522, 156), 'en': (459, 138, 522, 156), 'jp': (459, 138, 522, 156), 'tw': (459, 138, 522, 156)}, file={'cn': './assets/cn/island/TRANSPORT_STATUS_RUNNING.png', 'en': './assets/cn/island/TRANSPORT_STATUS_RUNNING.png', 'jp': './assets/cn/island/TRANSPORT_STATUS_RUNNING.png', 'tw': './assets/cn/island/TRANSPORT_STATUS_RUNNING.png'})

View File

@@ -1,389 +1,14 @@
import cv2
import numpy as np
from scipy import signal
import module.config.server as server
from module.base.timer import Timer
from module.base.utils import color_similarity_2d, random_rectangle_vector, rgb2gray
from module.config.deep import deep_get
from module.island.assets import *
from module.island.project_data import *
from module.island.project import IslandItem, IslandProduct, IslandProject
from module.island.ui import IslandUI
from module.island.project import IslandProjectRun
from module.island.transport import IslandTransportRun
from module.logger import logger
from module.map.map_grids import SelectedGrids
from module.ui.page import page_dormmenu, page_island, page_island_phone, page_main
class Island(IslandUI):
project = SelectedGrids([])
total = SelectedGrids([])
def project_detect(self, image):
"""
Get all projects from an image.
Args:
image (np.ndarray):
Returns:
SelectedGrids:
"""
image_gray = rgb2gray(image)
projects = SelectedGrids([IslandProject(image, image_gray, button)
for button in TEMPLATE_ISLAND_SWITCH.match_multi(image_gray)])
return projects.select(valid=True)
def project_receive(self, button, skip_first_screenshot=True):
"""
Receive and start a project.
Args:
button (Button): project button to click
Returns:
bool: if received.
"""
logger.hr('Island Project', level=2)
self.device.click_record_clear()
self.interval_clear([ISLAND_MANAGEMENT_CHECK, PROJECT_COMPLETE,
GET_ITEMS_ISLAND, ROLE_SELECT_ENTER])
received = False
enter = True
timeout = Timer(3, count=6).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if self.island_in_management(interval=5):
self.device.click(button)
timeout.reset()
continue
if self.handle_info_bar():
timeout.reset()
continue
if enter and self.appear_then_click(ROLE_SELECT_ENTER, offset=(5, 5), interval=2):
received = True
self.interval_clear(GET_ITEMS_ISLAND)
timeout.reset()
continue
if self.appear_then_click(PROJECT_COMPLETE, offset=(20, 20), interval=2):
received = True
enter = False
self.interval_clear(GET_ITEMS_ISLAND)
self.interval_reset(ROLE_SELECT_ENTER)
timeout.reset()
continue
if self.appear_then_click(GET_ITEMS_ISLAND, offset=(20, 20), interval=2):
enter = True
self.interval_clear(ROLE_SELECT_ENTER)
timeout.reset()
continue
if self.appear(ROLE_SELECT_CONFIRM, offset=(20, 20)):
break
if timeout.reached():
break
if not received:
product = IslandProduct(self.device.image)
if product.valid:
self.total = self.total.add_by_eq(SelectedGrids([product]))
self.device.click(ISLAND_CLICK_SAFE_AREA)
break
else:
self.interval_clear(ROLE_SELECT_ENTER)
return received
def island_select_manjuu(self, button):
"""
Args:
button (Button): role button to click
"""
self.interval_clear([ROLE_SELECT_CONFIRM, ISLAND_AMOUNT_MAX])
for _ in self.loop():
if self.appear(ROLE_SELECT_CHECK, offset=(20, 20)):
break
if self.appear(ROLE_SELECT_CONFIRM, offset=(20, 20), interval=5):
self.device.click(button)
continue
self.ui_click(
click_button=ROLE_SELECT_CONFIRM,
check_button=ISLAND_AMOUNT_MAX,
offset=(20, 20),
retry_wait=3,
skip_first_screenshot=True
)
def island_select_role(self, skip_first_screenshot=True):
"""
Select a role to produce.
Args:
skip_first_screenshot (bool):
Returns:
bool: if selected
"""
logger.info('Island select role')
timeout = Timer(1.5, count=3).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if timeout.reached():
self.island_product_quit()
return False
image = self.image_crop((0, 0, 910, 1280), copy=False)
sim, button = TEMPLATE_ISLAND_MANJUU.match_result(image)
if sim > 0.9:
self.island_select_manjuu(button)
return True
else:
logger.info('No manjuu found')
continue
def island_current_product(self):
"""
Get currently selected product on self.device.image.
Returns:
IslandItem: currently selected item
"""
image = self.image_crop(ISLAND_PRODUCT_ITEMS, copy=False)
y_top = ISLAND_PRODUCT_ITEMS.area[1]
line = cv2.reduce(image, 1, cv2.REDUCE_AVG)
# blue line
line = color_similarity_2d(line, color=(57, 189, 255))[:, 0]
parameters = {
'height': 200,
'distance': 50,
}
peaks, _ = signal.find_peaks(line, **parameters)
peaks = np.array(peaks) + y_top
return IslandItem(self.device.image, peaks)
def island_select_product(self, option, trial=2, skip_first_screenshot=True):
"""
Select a product in items list.
Args:
option (str): option to select
trail (int): retry times
skip_first_screenshot (bool):
Returns:
bool: if selected
"""
logger.hr('Island Select Product')
last = None
click_interval = Timer(1)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
current = self.island_current_product()
if trial > 0 and not len(current.items):
trial -= 1
continue
if trial <= 0:
self.island_product_quit()
return False
if option == current.name:
logger.info(f'Selected item {option}')
return True
drag = True
for item in current.items:
if option == item.name:
if click_interval.reached():
self.device.click(item.button)
self.device.sleep(0.2)
click_interval.reset()
drag = False
if last == current.items[-1]:
logger.info(f'Reach the bottom of items, dit not match item {option}')
self.island_product_quit()
return False
if drag:
last = current.items[-1]
self.device.click(last.button)
self.island_drag_next_page((0, -300), ISLAND_PRODUCT_ITEMS.area, 0.5)
def island_product_confirm(self, skip_first_screenshot=True):
"""
Start the product after product selected.
Args:
skip_first_screenshot (bool):
"""
logger.info('Island product confirm')
last = None
success = False
timeout = Timer(1.5, count=3).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if timeout.reached():
break
if self.image_color_count(PROJECT_START, color=(151, 155, 155), threshold=221, count=200):
self.island_product_quit()
break
if not success:
if self.appear_then_click(ISLAND_AMOUNT_MAX, offset=(5, 5), interval=5):
timeout.reset()
continue
product = IslandProduct(self.device.image, new=True)
if product == last:
success = True
self.total = self.total.add_by_eq(SelectedGrids([product]))
timeout.reset()
continue
last = product
else:
if self.appear_then_click(PROJECT_START, offset=(20,20), interval=2):
timeout.reset()
self.interval_reset(ISLAND_MANAGEMENT_CHECK)
continue
if self.info_bar_count():
self.island_product_quit()
break
if self.island_in_management():
break
def island_drag_next_page(self, vector, box, sleep=0.5):
"""
Drag to the next page.
Args:
vector (tuple):
box (tuple):
sleep (float):
"""
logger.info('Island drag to next page')
p1, p2 = random_rectangle_vector(vector, box=box, random_range=(0, -5, 0, 5))
self.device.drag(p1, p2, segments=2, shake=(0, 25), point_random=(0, 0, 0, 0), shake_random=(0, -5, 0, 5))
self.device.sleep(sleep)
def ensure_project(self, project: IslandProject, trial=7, skip_first_screenshot=True):
logger.hr('Project ensure')
for _ in range(trial):
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
projects = self.project_detect(self.device.image)
if not projects:
continue
if project.name in projects.get('name'):
logger.info(f'Ensured project: {project}')
break
self.island_drag_next_page((0, -500), ISLAND_PROJECT_SWIPE.area, 0.6)
def island_project_run(self, names, trial=2, skip_first_screenshot=True):
"""
Execute island run to receive and start project.
Args:
names (list[str]): a list of name for island receive
trial (int): retry times
skip_first_screenshot (bool):
Returns:
list[timedelta]: future finish timedelta
"""
logger.hr('Island Project Run', level=1)
end = False
timeout = Timer(3, count=3).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if timeout.reached():
break
projects = self.project_detect(self.device.image)
if trial > 0 and not projects:
trial -= 1
continue
projects: SelectedGrids = projects.filter(
lambda proj: proj.name in names and proj.name not in self.project.get('name'))
self.project = self.project.add_by_eq(projects)
for proj in projects:
if proj.name == names[-1]:
end = True
proj_config = self.island_project_config(proj)
for button, option, index in zip(
proj.slot_buttons.buttons, proj_config, range(len(proj_config))):
if option is None:
continue
if self.project_receive(button):
if self.island_select_role():
if self.island_select_product(option):
self.island_product_confirm()
self.ui_ensure_management_page()
if not end or index != len(proj_config) - 1:
self.ensure_project(proj)
timeout.reset()
if end:
break
self.island_drag_next_page((0, -500), ISLAND_PROJECT_SWIPE.area, 0.6)
# task delay
future_finish = sorted([f for f in self.total.get('finish_time') if f is not None])
logger.info(f'Project finish: {[str(f) for f in future_finish]}')
if not len(future_finish):
logger.info('No island project running')
return future_finish
def island_project_config(self, project: IslandProject):
"""
Args:
project (IslandProject):
Returns:
list[str]: a list of options for production
"""
slot_option = []
proj_id = project.id
for proj_slot in range(1, project.slot + 1):
option = self.config.__getattribute__(f'Island{proj_id}_Option{proj_slot}')
if option == 0:
slot_option.append(None)
continue
slot_option.append(deep_get(items_data_cn, [proj_id, option]))
return slot_option
class Island(IslandProjectRun, IslandTransportRun):
@staticmethod
def island_config_to_names(config):
"""
@@ -398,12 +23,10 @@ class Island(IslandUI):
else:
return []
def island_transport_run(self):
return []
def island_run(self, transport=False, project=True, names=None):
"""
Execute island routine.
Args:
transport (bool):
project (bool):
@@ -425,6 +48,7 @@ class Island(IslandUI):
self.config.Scheduler_Enable = False
return False
# task delay
if len(future_finish):
self.config.task_delay(target=future_finish)
else:

View File

@@ -1,13 +1,19 @@
from datetime import datetime, timedelta
import cv2
import re
import numpy as np
from scipy import signal
from module.base.button import Button, ButtonGrid
from module.base.utils import crop
from module.config.deep import deep_values
from module.base.timer import Timer
from module.base.utils import color_similarity_2d, crop, random_rectangle_vector, rgb2gray
from module.config.deep import deep_get, deep_values
from module.island.assets import *
from module.island.project_data import *
from module.island.ui import OCR_PRODUCTION_TIME, OCR_PRODUCTION_TIME_REMAIN
from module.ocr.ocr import Ocr
from module.island.ui import IslandUI
from module.logger import logger
from module.map.map_grids import SelectedGrids
from module.ocr.ocr import Duration, Ocr
class ProjectNameOcr(Ocr):
@@ -18,11 +24,17 @@ class ProjectNameOcr(Ocr):
class IslandProject:
# If success to parse project
valid: bool
# OCR result
name: str
# Project workplace id
id: int
# max slot that the workplace has
max_slot: int
# available slots that the workplace has
slot: int
# buttons of all available slots
slot_buttons: ButtonGrid
def __init__(self, image, image_gray, button):
@@ -109,15 +121,27 @@ class ItemNameOcr(Ocr):
return result
class IslandProductionTime(Duration):
def after_process(self, result):
result = super().after_process(result)
if result == '0:40:00':
result = '01:40:00'
return result
class IslandProduct:
# Duration to run this product
duration: timedelta
# If success to parse product duration
valid: bool
def __init__(self, image, new=False):
if new:
self.duration = OCR_PRODUCTION_TIME.ocr(image)
ocr = IslandProductionTime(OCR_PRODUCTION_TIME, lang='azur_lane_jp', name='OCR_PRODUCTION_TIME')
self.duration = ocr.ocr(image)
else:
self.duration = OCR_PRODUCTION_TIME_REMAIN.ocr(image)
ocr = Duration(OCR_PRODUCTION_TIME_REMAIN, name='OCR_PRODUCTION_TIME_REMAIN')
self.duration = ocr.ocr(image)
self.valid = True
if not self.duration.total_seconds():
@@ -151,6 +175,15 @@ class IslandProduct:
return True
class IslandItem:
# OCR result
name: str
# If success to parse item name
valid: bool
# Button to click for the current item
button: Button
# All buttons on this page to click
item_buttons: ButtonGrid
def __init__(self, image, y, get_button=True):
self.image = image
self.y = y
@@ -238,4 +271,374 @@ class IslandItem:
if self.name != other.name:
return False
return True
return True
class IslandProjectRun(IslandUI):
project = SelectedGrids([])
total = SelectedGrids([])
def project_detect(self, image):
"""
Get all projects from an image.
Args:
image (np.ndarray):
Returns:
SelectedGrids:
"""
image_gray = rgb2gray(image)
projects = SelectedGrids([IslandProject(image, image_gray, button)
for button in TEMPLATE_ISLAND_SWITCH.match_multi(image_gray)])
return projects.select(valid=True)
def project_receive(self, button, skip_first_screenshot=True):
"""
Receive and start a project.
Args:
button (Button): project button to click
Returns:
bool: if received.
"""
logger.hr('Island Project', level=2)
self.device.click_record_clear()
self.interval_clear([ISLAND_MANAGEMENT_CHECK, PROJECT_COMPLETE,
GET_ITEMS_ISLAND, ROLE_SELECT_ENTER])
received = False
enter = True
timeout = Timer(3, count=6).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if self.island_in_management(interval=5):
self.device.click(button)
timeout.reset()
continue
if self.handle_info_bar():
timeout.reset()
continue
if enter and self.appear_then_click(ROLE_SELECT_ENTER, offset=(5, 5), interval=2):
received = True
self.interval_clear(GET_ITEMS_ISLAND)
timeout.reset()
continue
if self.appear_then_click(PROJECT_COMPLETE, offset=(20, 20), interval=2):
received = True
enter = False
self.interval_clear(GET_ITEMS_ISLAND)
self.interval_reset(ROLE_SELECT_ENTER)
timeout.reset()
continue
if self.appear_then_click(GET_ITEMS_ISLAND, offset=(20, 20), interval=2):
enter = True
self.interval_clear(ROLE_SELECT_ENTER)
timeout.reset()
continue
if self.appear(ROLE_SELECT_CONFIRM, offset=(20, 20)):
break
if timeout.reached():
break
if not received:
product = IslandProduct(self.device.image)
if product.valid:
self.total = self.total.add_by_eq(SelectedGrids([product]))
self.device.click(ISLAND_CLICK_SAFE_AREA)
break
else:
self.interval_clear(ROLE_SELECT_ENTER)
return received
def island_select_manjuu(self, button):
"""
Args:
button (Button): role button to click
"""
self.interval_clear([ROLE_SELECT_CONFIRM, ISLAND_AMOUNT_MAX])
for _ in self.loop():
if self.appear(ROLE_SELECT_CHECK, offset=(20, 20)):
break
if self.appear(ROLE_SELECT_CONFIRM, offset=(20, 20), interval=5):
self.device.click(button)
continue
self.ui_click(
click_button=ROLE_SELECT_CONFIRM,
check_button=ISLAND_AMOUNT_MAX,
offset=(20, 20),
retry_wait=3,
skip_first_screenshot=True
)
def island_select_role(self, skip_first_screenshot=True):
"""
Select a role to produce.
Args:
skip_first_screenshot (bool):
Returns:
bool: if selected
"""
logger.info('Island select role')
timeout = Timer(1.5, count=3).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if timeout.reached():
self.island_product_quit()
return False
image = self.image_crop((0, 0, 910, 1280), copy=False)
sim, button = TEMPLATE_ISLAND_MANJUU.match_result(image)
if sim > 0.9:
self.island_select_manjuu(button)
return True
else:
logger.info('No manjuu found')
continue
def island_current_product(self):
"""
Get currently selected product on self.device.image.
Returns:
IslandItem: currently selected item
"""
image = self.image_crop(ISLAND_PRODUCT_ITEMS, copy=False)
y_top = ISLAND_PRODUCT_ITEMS.area[1]
line = cv2.reduce(image, 1, cv2.REDUCE_AVG)
# blue line
line = color_similarity_2d(line, color=(57, 189, 255))[:, 0]
parameters = {
'height': 200,
'distance': 50,
}
peaks, _ = signal.find_peaks(line, **parameters)
peaks = np.array(peaks) + y_top
return IslandItem(self.device.image, peaks)
def island_select_product(self, option, trial=2, skip_first_screenshot=True):
"""
Select a product in items list.
Args:
option (str): option to select
trail (int): retry times
skip_first_screenshot (bool):
Returns:
bool: if selected
"""
logger.hr('Island Select Product')
last = None
click_interval = Timer(1)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
current = self.island_current_product()
if trial > 0 and not len(current.items):
trial -= 1
continue
if trial <= 0:
self.island_product_quit()
return False
if option == current.name:
logger.info(f'Selected item {option}')
return True
drag = True
for item in current.items:
if option == item.name:
if click_interval.reached():
self.device.click(item.button)
self.device.sleep(0.2)
click_interval.reset()
drag = False
if last == current.items[-1]:
logger.info(f'Reach the bottom of items, dit not match item {option}')
self.island_product_quit()
return False
if drag:
last = current.items[-1]
self.device.click(last.button)
self.island_drag_next_page((0, -300), ISLAND_PRODUCT_ITEMS.area, 0.5)
def island_product_confirm(self, skip_first_screenshot=True):
"""
Start the product after product selected.
Args:
skip_first_screenshot (bool):
"""
logger.info('Island product confirm')
last = None
success = False
timeout = Timer(1.5, count=3).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if timeout.reached():
break
if self.image_color_count(PROJECT_START, color=(151, 155, 155), threshold=221, count=200):
self.island_product_quit()
break
if not success:
if self.appear_then_click(ISLAND_AMOUNT_MAX, offset=(5, 5), interval=5):
timeout.reset()
continue
product = IslandProduct(self.device.image, new=True)
if product == last:
success = True
self.total = self.total.add_by_eq(SelectedGrids([product]))
timeout.reset()
continue
last = product
else:
if self.appear_then_click(PROJECT_START, offset=(20,20), interval=2):
timeout.reset()
self.interval_reset(ISLAND_MANAGEMENT_CHECK)
continue
if self.info_bar_count():
self.island_product_quit()
break
if self.island_in_management():
break
def island_drag_next_page(self, vector, box, sleep=0.5):
"""
Drag to the next page.
Args:
vector (tuple):
box (tuple):
sleep (float):
"""
logger.info('Island drag to next page')
p1, p2 = random_rectangle_vector(vector, box=box, random_range=(0, -5, 0, 5))
self.device.drag(p1, p2, segments=2, shake=(0, 25), point_random=(0, 0, 0, 0), shake_random=(0, -5, 0, 5))
self.device.sleep(sleep)
def ensure_project(self, project: IslandProject, trial=7, skip_first_screenshot=True):
logger.hr('Project ensure')
for _ in range(trial):
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
projects = self.project_detect(self.device.image)
if not projects:
continue
if project.name in projects.get('name'):
logger.info(f'Ensured project: {project}')
break
self.island_drag_next_page((0, -500), ISLAND_PROJECT_SWIPE.area, 0.6)
def island_project_config(self, project: IslandProject):
"""
Args:
project (IslandProject):
Returns:
list[str]: a list of options for production
"""
slot_option = []
proj_id = project.id
for proj_slot in range(1, project.slot + 1):
option = self.config.__getattribute__(f'Island{proj_id}_Option{proj_slot}')
if option == 0:
slot_option.append(None)
continue
slot_option.append(deep_get(items_data_cn, [proj_id, option]))
return slot_option
def island_project_run(self, names, trial=2, skip_first_screenshot=True):
"""
Execute island run to receive and start project.
Args:
names (list[str]): a list of name for island receive
trial (int): retry times
skip_first_screenshot (bool):
Returns:
list[timedelta]: future finish timedelta
"""
logger.hr('Island Project Run', level=1)
end = False
timeout = Timer(3, count=3).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if timeout.reached():
break
projects = self.project_detect(self.device.image)
if trial > 0 and not projects:
trial -= 1
continue
projects: SelectedGrids = projects.filter(
lambda proj: proj.name in names and proj.name not in self.project.get('name'))
self.project = self.project.add_by_eq(projects)
for proj in projects:
if proj.name == names[-1]:
end = True
proj_config = self.island_project_config(proj)
for button, option, index in zip(
proj.slot_buttons.buttons, proj_config, range(len(proj_config))):
if option is None:
continue
if self.project_receive(button):
if self.island_select_role():
if self.island_select_product(option):
self.island_product_confirm()
self.ui_ensure_management_page()
if not end or index != len(proj_config) - 1:
self.ensure_project(proj)
timeout.reset()
if end:
break
self.island_drag_next_page((0, -500), ISLAND_PROJECT_SWIPE.area, 0.6)
# task delay
future_finish = sorted([f for f in self.total.get('finish_time') if f is not None])
logger.info(f'Project finish: {[str(f) for f in future_finish]}')
if not len(future_finish):
logger.info('No island project running')
return future_finish

View File

@@ -1,18 +1,11 @@
from datetime import datetime, timedelta
import numpy as np
import re
from cached_property import cached_property
from module.base.button import ButtonGrid
from module.base.timer import Timer
from module.base.utils import area_offset, crop, image_color_count, rgb2gray
from module.base.utils import area_offset
from module.island.assets import *
from module.island.ui import IslandUI
from module.logger import logger
from module.map.map_grids import SelectedGrids
from module.ocr.ocr import Duration
from module.ui_white.assets import POPUP_CANCEL_WHITE, POPUP_CONFIRM_WHITE
class IslandTransport:
@@ -20,37 +13,20 @@ class IslandTransport:
index: int
# If success to parse transport commission
valid: bool
# If the commission is locked
locked: bool
# Duration to run this transport commission
duration: timedelta
# Status of transport commission
# Value: finished, running, pending, refreshing, unknown
# Value: finished, running, pending, unknown
status: str
# If the transprt commission need to start
start: bool
# If the transprt commission need to refresh
refresh: bool
def __init__(self, main, index, blacklist):
"""
Args:
main:
index (int):
blacklist (list[Template]): a blacklist of templates of items to submit
"""
def __init__(self, main, index):
self.index = index
self.blacklist = blacklist
self.image = main.device.image
self.valid = True
self.locked = False
self.duration = None
self.start = True
self.refresh = False
self.items = SelectedGrids([])
self.can_start = True
self.parse_transport(main)
if not self.valid:
self.start = False
self.can_start = False
self.create_time = datetime.now()
def parse_transport(self, main):
@@ -61,7 +37,7 @@ class IslandTransport:
# commission locked
lock_offset = area_offset(offset, (0, delta * (self.index - 1)))
if self.index >= 1 and main.appear(TRANSPORT_LOCKED, lock_offset):
self.locked = True
self.valid = False
return
self.status = self.get_transport_status(main)
@@ -71,42 +47,25 @@ class IslandTransport:
elif self.status == 'pending':
button = OCR_TRANSPORT_TIME.move((0, self.offset[1] + 20))
ocr = Duration(button, lang='cnocr', letter=(207, 207, 207), name='OCR_TRANSPORT_TIME')
self.duration = ocr.ocr(self.image)
self.duration = ocr.ocr(main.device.image)
if not self.duration.total_seconds():
self.valid = False
return
# items info
origin_y = 174 + delta * self.index
grids = ButtonGrid(origin=(481, origin_y), delta=(105, 0),
button_shape=(86, 86), grid_shape=(3, 1), name='ITEMS')
self.items = SelectedGrids([TransportItem(self.image, button, self.blacklist)
for button in grids.buttons]).select(valid=True)
self.start = self.items.select(load=True).count == self.items.count
self.refresh = main.appear(TRANSPORT_REFRESH, offset=self.offset) and \
bool(self.items.select(refresh=True).count)
# detect items first because we need to get refresh info
if not main.match_template_color(TRANSPORT_START, offset=self.offset):
self.start = False
self.can_start = False
return
elif self.status == 'running':
self.start = False
self.can_start = False
button = OCR_TRANSPORT_TIME_REMAIN.move((0, self.offset[1] + 20))
ocr = Duration(button, name='OCR_TRANSPORT_TIME')
self.duration = ocr.ocr(self.image)
self.duration = ocr.ocr(main.device.image)
if not self.duration.total_seconds():
self.valid = False
return
elif self.status == 'finished':
self.start = False
elif self.status == 'refreshing':
self.start = False
button = OCR_TRANSPORT_REFRESH.move((0, self.offset[1] + 20))
ocr = Duration(button, letter=(63, 64, 66), name='OCR_TRANSPORT_REFRESH')
self.duration = ocr.ocr(self.image)
if not self.duration.total_seconds():
self.valid = False
return
self.can_start = False
def get_transport_status(self, main):
if main.appear(TRANSPORT_STATUS_PENDING, offset=self.offset):
@@ -115,24 +74,12 @@ class IslandTransport:
return 'running'
elif main.appear(TRANSPORT_RECEIVE, offset=self.offset):
return 'finished'
elif main.appear(TRANSPORT_REFRESH_CHECK, offset=self.offset):
return 'refreshing'
else:
return 'unknown'
def convert_to_refreshing(self):
if self.valid:
self.status = 'refreshing'
self.start = False
self.refresh = False
self.duration = timedelta(hours=0, minutes=30, seconds=0)
self.create_time = datetime.now()
def convert_to_running(self):
if self.valid:
self.status = 'running'
self.start = False
self.refresh = False
self.create_time = datetime.now()
@property
@@ -145,87 +92,15 @@ class IslandTransport:
def __str__(self):
if not self.valid:
return f'Index: {self.index} (Invalid)'
if self.locked:
return f'Index: {self.index} (Locked)'
info = {'Index': self.index, 'Status': self.status}
if self.duration:
info['Duration'] = self.duration
info['Start'] = self.start
info['Refresh'] = self.refresh
info = ', '.join([f'{k}: {v}' for k, v in info.items()])
return info
class TransportItem:
# If the item is enough to submit and not in blacklist
load: bool
# If the item is in blacklist
refresh: bool
def __init__(self, image, button, blacklist):
"""
Args:
image:
button:
blacklist:
"""
self.image_raw = image
self.button = button
self.blacklist = blacklist
self.image = crop(image, button.area)
self.valid = self.predict_valid()
self.refresh = False
self.load = self.predict_load()
def predict_valid(self):
# gray item means an empty item
mean = np.mean(np.max(self.image, axis=2) > 234)
# blue bar on top of the item means already loaded
blue_bar_check = image_color_count(self.image[:10, :, :], color=(90, 201, 255), threshold=221, count=500)
return mean > 0.3 and not blue_bar_check
def predict_load(self):
if not self.valid:
return False
self.refresh = self.handle_blacklist_items()
if self.refresh:
return False
if not TEMPLATE_ITEM_SATISFIED.match(rgb2gray(self.image)):
return False
return True
def handle_blacklist_items(self):
"""
Check if current item is a blacklist item.
Returns:
bool: if any blacklist item
"""
for template in self.blacklist:
if template.match(self.image):
return True
return False
def __str__(self):
if not self.valid:
return '(Invalid)'
info = {'Load': self.load, 'Refresh': self.refresh}
info['can_start'] = self.can_start
info = ', '.join([f'{k}: {v}' for k, v in info.items()])
return info
class IslandTransportRun(IslandUI):
@cached_property
def blacklist(self):
blacklist = []
for k, v in self.config.cross_get(keys='Island.IslandTransport').items():
if k.startswith('Submit') and not v:
item = k.split('Submit')[-1]
item = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', item)
item = re.sub('([a-z0-9])([A-Z])', r'\1_\2', item)
blacklist.append(globals()[f'TEMPLATE_{item.upper()}'])
return blacklist
def _transport_detect(self):
"""
Get all commissions from self.device.image.
@@ -236,10 +111,8 @@ class IslandTransportRun(IslandUI):
logger.hr('Transport Commission detect')
commission = []
for index in range(3):
comm = IslandTransport(main=self, index=index, blacklist=self.blacklist)
comm = IslandTransport(main=self, index=index)
logger.attr(f'Transport Commission', comm)
for item in comm.items:
logger.attr(item.button, item)
commission.append(comm)
return SelectedGrids(commission)
@@ -262,11 +135,8 @@ class IslandTransportRun(IslandUI):
self.device.screenshot()
commissions = self._transport_detect()
if not commissions.count:
logger.warning('No commission detected, retry commission detect')
continue
elif commissions.select(valid=False).count:
logger.warning('Found 1 invalid commission at least, retry commission detect')
if commissions.count >= 2 and commissions.select(valid=False).count == 1:
logger.warning('Found 1 invalid commission, retry commission detect')
continue
else:
return commissions.select(valid=True)
@@ -274,154 +144,12 @@ class IslandTransportRun(IslandUI):
logger.info('trials of transport commission detect exhausted, stop')
return commissions.select(valid=True)
def transport_receive(self):
"""
Receive all transport missions from transport page.
Returns:
bool: if received
"""
logger.hr('Island Transport', level=2)
self.device.click_record_clear()
self.interval_clear([GET_ITEMS_ISLAND, TRANSPORT_RECEIVE, POPUP_CANCEL_WHITE])
success = True
click_timer = Timer(5, count=10).start()
confirm_timer = Timer(1, count=2).start()
for _ in self.loop():
if self.handle_info_bar():
click_timer.reset()
confirm_timer.reset()
continue
if self.appear_then_click(TRANSPORT_RECEIVE, offset=(-20, -20, 20, 400), interval=2):
success = False
click_timer.reset()
confirm_timer.reset()
continue
if self.handle_get_items():
success = True
click_timer.reset()
confirm_timer.reset()
continue
if self.handle_popup_cancel('REFRESH_CANCEL', offset=(30, 30)):
click_timer.reset()
confirm_timer.reset()
continue
# handle island level up
if click_timer.reached():
success = True
self.device.click(GET_ITEMS_ISLAND)
self.device.sleep(0.3)
click_timer.reset()
confirm_timer.reset()
continue
if self.island_in_transport():
if success and confirm_timer.reached():
break
click_timer.reset()
else:
confirm_timer.reset()
return success
def transport_refresh(self, comm):
"""
Refresh the specific transport mission from transport page.
Args:
comm (IslandTransport): the commission to refresh
Returns:
bool: if success
"""
logger.info('Transport commission refresh')
self.interval_clear([TRANSPORT_REFRESH, POPUP_CONFIRM_WHITE])
success = True
confirm_timer = Timer(1, count=2).start()
for _ in self.loop():
if self.appear_then_click(TRANSPORT_REFRESH, offset=comm.offset, interval=2):
continue
if self.handle_popup_confirm('REFRESH_CONFIRM', offset=(30, 30)):
success = True
confirm_timer.reset()
continue
if self.island_in_transport():
if success and confirm_timer.reached():
break
else:
confirm_timer.reset()
return success
def transport_start(self, comm):
"""
Start the specific transport mission from transport page.
Args:
comm (IslandTransport): the commission to start
Returns:
bool: if success
"""
logger.info('Transport commission start')
self.interval_clear([GET_ITEMS_ISLAND, TRANSPORT_START, POPUP_CANCEL_WHITE])
success = True
confirm_timer = Timer(1, count=2).start()
for _ in self.loop():
if self.appear_then_click(TRANSPORT_START, offset=comm.offset, interval=2):
success = False
confirm_timer.reset()
continue
if self.handle_get_items():
success = True
confirm_timer.reset()
continue
if self.handle_popup_cancel('REFRESH_CANCEL', offset=(30, 30)):
confirm_timer.reset()
continue
if self.island_in_transport():
if success and confirm_timer.reached():
break
else:
confirm_timer.reset()
return success
def island_transport_run(self):
"""
Execute island run to receive and start transport commissions.
Returns:
list[timedelta]: future finish timedelta
"""
logger.hr('Island Transport Run', level=1)
future_finish = []
self.transport_receive()
commissions = self.transport_detect(trial=5)
commissions = self.transport_detect(trial=2)
comm_refresh = commissions.select(status='pending', refresh=True)
comm_choose = commissions.select(status='pending', start=True)
for comm in comm_refresh:
if self.transport_refresh(comm):
comm.convert_to_refreshing()
for comm in comm_choose:
if self.transport_start(comm):
comm.convert_to_running()
logger.hr('Showing transport commission', level=2)
for comm in commissions:
logger.attr(f'Transport Commission', comm)
running_finish = [f for f in commissions.select(status='running').get('finish_time') if f is not None]
refreshing_finish = [f for f in commissions.select(status='refreshing').get('finish_time') if f is not None]
future_finish = sorted(running_finish + refreshing_finish)
future_finish = sorted([f for f in commissions.select(status='running').get('finish_time') if f is not None])
logger.info(f'Transport finish: {[str(f) for f in future_finish]}')
if not len(future_finish):
logger.info('No island transport running')

View File

@@ -1,24 +1,11 @@
from module.base.timer import Timer
from module.island.assets import *
from module.logger import logger
from module.ocr.ocr import Duration
from module.ui.assets import SHOP_BACK_ARROW
from module.ui.page import page_island_phone
from module.ui.ui import UI
class IslandProductionTime(Duration):
def after_process(self, result):
result = super().after_process(result)
if result == '0:40:00':
result = '01:40:00'
return result
OCR_PRODUCTION_TIME = IslandProductionTime(OCR_PRODUCTION_TIME, lang='azur_lane_jp', name='OCR_PRODUCTION_TIME')
OCR_PRODUCTION_TIME_REMAIN = Duration(OCR_PRODUCTION_TIME_REMAIN, name='OCR_PRODUCTION_TIME_REMAIN')
class IslandUI(UI):
def island_in_management(self, interval=0):
"""