1
0
mirror of https://github.com/0O0o0oOoO00/Alas.git synced 2026-05-14 12:49:25 +08:00

Merge branch 'zuosizhu-alas/master'

This commit is contained in:
LA-DI-DA
2023-09-23 22:30:53 +08:00
14 changed files with 332 additions and 15 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -6,7 +6,7 @@ from module.logger import logger
MAP = CampaignMap('SP')
MAP.shape = 'H5'
MAP.camera_data = ['E3']
MAP.camera_data_spawn_point = ['D3']
MAP.camera_data_spawn_point = ['C3']
MAP.map_data = """
-- ++ ++ ++ ++ -- ME --
SP -- -- -- ++ ME -- ME
@@ -62,9 +62,10 @@ class Config:
'distance': 50,
'wlen': 1000
}
HOMO_STORAGE = ((9, 7), [(158.102, 59.806), (1142.124, 59.806), (-34.052, 695.951), (1324.267, 695.951)])
HOMO_EDGE_COLOR_RANGE = (0, 17)
HOMO_EDGE_HOUGHLINES_THRESHOLD = 210
MAP_ENSURE_EDGE_INSIGHT_CORNER = 'bottom'
MAP_ENSURE_EDGE_INSIGHT_CORNER = 'bottom-right'
MAP_IS_ONE_TIME_STAGE = True
MAP_SWIPE_MULTIPLY = (1.229, 1.253)

View File

@@ -61,6 +61,9 @@ class ConfigModel:
CDN: Union[str, bool] = False
Run: Optional[str] = None
# Dynamic
GitOverCdn: bool = False
class DeployConfig(ConfigModel):
def __init__(self, file=DEPLOY_CONFIG):
@@ -79,6 +82,18 @@ class DeployConfig(ConfigModel):
'https://git.saarcenter.com/LmeSzinc/AzurLaneAutoScript.git',
]:
self.Repository = 'git://git.lyoko.io/AzurLaneAutoScript'
# Bypass webui.config.DeployConfig.__setattr__()
# Don't write these into deploy.yaml
super().__setattr__(
'GitOverCdn',
self.Repository == 'git://git.lyoko.io/AzurLaneAutoScript' and self.Branch == 'master'
)
if self.Repository in ['global']:
super().__setattr__('Repository', 'https://github.com/LmeSzinc/AzurLaneAutoScript')
if self.Repository in ['cn']:
super().__setattr__('Repository', 'git://git.lyoko.io/AzurLaneAutoScript')
self.write()
self.show_config()
@@ -87,7 +102,7 @@ class DeployConfig(ConfigModel):
for k, v in self.config.items():
if k in ("Password", "SSHUser"):
continue
if self.config_template[k] == v:
if self.config_template.get(k) == v:
continue
logger.info(f"{k}: {v}")

View File

@@ -1,6 +1,5 @@
import os
from deploy.config import DeployConfig
from deploy.git_over_cdn.client import GitOverCdnClient
from deploy.logger import logger
from deploy.utils import *
@@ -78,6 +77,18 @@ class GitManager(DeployConfig):
logger.hr('Show Version', 1)
self.execute(f'"{self.git}" --no-pager log --no-merges -1')
@property
def goc_client(self):
client = GitOverCdnClient(
url='https://vip.123pan.cn/1818706573/pack/LmeSzinc_AzurLaneAutoScript_master',
folder=self.root_filepath,
source='origin',
branch='master',
git=self.git,
)
client.logger = logger
return client
def git_install(self):
logger.hr('Update Alas', 0)
@@ -85,6 +96,10 @@ class GitManager(DeployConfig):
logger.info('AutoUpdate is disabled, skip')
return
if self.GitOverCdn:
if self.goc_client.update(keep_changes=self.KeepLocalChanges):
return
self.git_repository_init(
repo=self.Repository,
source='origin',

View File

@@ -0,0 +1,263 @@
import io
import json
import os
import re
import shutil
import subprocess
import zipfile
from typing import Callable, Generic, TypeVar
import requests
from requests.adapters import HTTPAdapter
T = TypeVar("T")
TEMPLATE_FILE = './config/template.yaml'
class cached_property(Generic[T]):
"""
cached-property from https://github.com/pydanny/cached-property
Add typing support
A property that is only computed once per instance and then replaces itself
with an ordinary attribute. Deleting the attribute resets the property.
Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76
"""
def __init__(self, func: Callable[..., T]):
self.func = func
def __get__(self, obj, cls) -> T:
if obj is None:
return self
value = obj.__dict__[self.func.__name__] = self.func(obj)
return value
class PrintLogger:
info = print
warning = print
error = print
@staticmethod
def attr(name, text):
print(f'[{name}] {text}')
class GitOverCdnClient:
logger = PrintLogger()
def __init__(self, url, folder, source='origin', branch='master', git='git'):
"""
Args:
url: http://127.0.0.1:22251/pack/LmeSzinc_AzurLaneAutoScript_master/
folder: D:/AzurLaneAutoScript
"""
self.url = url.strip('/')
self.folder = folder.replace('\\', '/')
self.source = source
self.branch = branch
self.git = git
def filepath(self, path):
path = os.path.join(self.folder, '.git', path)
return os.path.abspath(path).replace('\\', '/')
def urlpath(self, path):
return f'{self.url}{path}'
@cached_property
def current_commit(self) -> str:
for file in [
f'./refs/remotes/{self.source}/{self.branch}',
f'./refs/heads/{self.branch}',
'ORIG_HEAD',
]:
file = self.filepath(file)
try:
with open(file, 'r', encoding='utf-8') as f:
commit = f.read()
res = re.search(r'([0-9a-f]{40})', commit)
if res:
commit = res.group(1)
self.logger.attr('CurrentCommit', commit)
return commit
except FileNotFoundError as e:
self.logger.error(f'Failed to get local commit: {e}')
except Exception as e:
self.logger.error(f'Failed to get local commit: {e}')
return ''
@property
def session(self):
session = requests.Session()
session.trust_env = False
session.mount('http://', HTTPAdapter(max_retries=3))
session.mount('https://', HTTPAdapter(max_retries=3))
return session
@cached_property
def latest_commit(self) -> str:
try:
url = self.urlpath('/latest.json')
self.logger.info(f'Fetch url: {url}')
resp = self.session.get(url, timeout=3)
except Exception as e:
self.logger.error(f'Failed to get remote commit: {e}')
return ''
if resp.status_code == 200:
try:
info = json.loads(resp.text)
commit = info['commit']
self.logger.attr('LatestCommit', commit)
return commit
except json.JSONDecodeError:
self.logger.error(f'Failed to get remote commit, response is not a json: {resp.text}')
return ''
except KeyError:
self.logger.error(f'Failed to get remote commit, key "commit" is not found: {resp.text}')
return ''
else:
self.logger.error(f'Failed to get remote commit, status={resp.status_code}, text={resp.text}')
return ''
def download_pack(self):
try:
url = self.urlpath(f'/{self.latest_commit}/{self.current_commit}.zip')
self.logger.info(f'Fetch url: {url}')
resp = self.session.get(url, timeout=20)
except Exception as e:
self.logger.error(f'Failed to download pack: {e}')
return False
if resp.status_code == 200:
try:
zipped = zipfile.ZipFile(io.BytesIO(resp.content))
for file in [f'pack-{self.latest_commit}.pack', f'pack-{self.latest_commit}.idx']:
self.logger.info(f'Unzip {file}')
member = zipped.getinfo(file)
tmp = self.filepath(f'./objects/pack/{file}.tmp')
out = self.filepath(f'./objects/pack/{file}')
with zipped.open(member) as source, open(tmp, "wb") as target:
shutil.copyfileobj(source, target)
os.replace(tmp, out)
return True
except zipfile.BadZipFile as e:
# File is not a zip file
self.logger.error(e)
return False
except KeyError as e:
# There is no item named 'xxx.idx' in the archive
self.logger.error(e)
return False
except Exception as e:
self.logger.error(e)
return False
elif resp.status_code == 404:
self.logger.error(f'Failed to download pack, status={resp.status_code}, no such pack files provided')
return False
else:
self.logger.error(f'Failed to download pack, status={resp.status_code}, text={resp.text}')
return False
def update_refs(self):
file = self.filepath(f'./refs/remotes/{self.source}/{self.branch}')
text = f'{self.latest_commit}\n'
self.logger.info(f'Update refs: {file}')
os.makedirs(os.path.dirname(file), exist_ok=True)
try:
with open(file, 'w', encoding='utf-8', newline='') as f:
f.write(text)
return True
except FileNotFoundError as e:
self.logger.error(f'Failed to get local commit: {e}')
except Exception as e:
self.logger.error(f'Failed to get local commit: {e}')
return False
def git_command(self, *args, timeout=300):
"""
Execute ADB commands in a subprocess,
usually to be used when pulling or pushing large files.
Args:
timeout (int):
Returns:
str:
"""
os.chdir(self.folder)
cmd = list(map(str, args))
cmd = [self.git] + cmd
self.logger.info(f'Execute: {cmd}')
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=False)
try:
stdout, stderr = process.communicate(timeout=timeout)
except subprocess.TimeoutExpired:
process.kill()
stdout, stderr = process.communicate()
self.logger.warning(f'TimeoutExpired when calling {cmd}, stdout={stdout}, stderr={stderr}')
return stdout.decode()
def git_reset(self, keep_changes=False):
"""
git reset --hard <commit>
"""
if keep_changes:
self.git_command('stash')
self.git_command('reset', '--hard', f'{self.source}/{self.branch}')
self.git_command('stash', 'pop')
else:
self.git_command('reset', '--hard', f'{self.source}/{self.branch}')
def is_uptodate(self):
"""
Returns:
bool: If repo is up-to-date
"""
_ = self.current_commit
_ = self.latest_commit
if not self.current_commit:
self.logger.error('Failed to get current commit')
return False
if not self.latest_commit:
self.logger.error('Failed to get latest commit')
return False
if self.current_commit == self.latest_commit:
self.logger.info('Already up to date')
return True
return False
def update(self, keep_changes=False):
"""
Args:
keep_changes:
Returns:
bool: If repo is up-to-date
"""
_ = self.current_commit
_ = self.latest_commit
if not self.current_commit:
self.logger.error('Failed to get current commit')
return False
if not self.latest_commit:
self.logger.error('Failed to get latest commit')
return False
if self.current_commit == self.latest_commit:
self.logger.info('Already up to date')
self.git_reset(keep_changes=keep_changes)
return True
if not self.download_pack():
return False
if not self.update_refs():
return False
self.git_reset(keep_changes=keep_changes)
self.logger.info('Update success')
return True

View File

@@ -33,4 +33,9 @@ def hr(title, level=3):
logger.info(f"<<< {title} >>>")
def attr(name, text):
print(f'[{name}] {text}')
logger.hr = hr
logger.attr = attr

View File

@@ -172,16 +172,19 @@ class ModuleBase:
logger.warning(f'wait_until_stable({button}) timeout')
break
def image_crop(self, button):
def image_crop(self, button, copy=True):
"""Extract the area from image.
Args:
button(Button, tuple): Button instance or area tuple.
copy:
"""
if isinstance(button, Button):
return crop(self.device.image, button.area)
return crop(self.device.image, button.area, copy=copy)
elif hasattr(button, 'area'):
return crop(self.device.image, button.area, copy=copy)
else:
return crop(self.device.image, button)
return crop(self.device.image, button, copy=copy)
def image_color_count(self, button, color, threshold=221, count=50):
"""
@@ -194,9 +197,14 @@ class ModuleBase:
Returns:
bool:
"""
image = self.image_crop(button)
mask = color_similarity_2d(image, color=color) > threshold
return np.sum(mask) > count
if isinstance(button, np.ndarray):
image = button
else:
image = self.image_crop(button, copy=False)
mask = color_similarity_2d(image, color=color)
cv2.inRange(mask, threshold, 255, dst=mask)
sum_ = cv2.countNonZero(mask)
return sum_ > count
def image_color_button(self, area, color, color_threshold=250, encourage=5, name='COLOR_BUTTON'):
"""

View File

@@ -6,8 +6,8 @@ from module.base.template import Template
AUTO_SEARCH_MAP_OPTION_OFF = Button(area={'cn': (1205, 549, 1275, 566), 'en': (1203, 552, 1277, 564), 'jp': (1204, 547, 1276, 568), 'tw': (1205, 546, 1275, 567)}, color={'cn': (196, 169, 169), 'en': (151, 132, 138), 'jp': (179, 153, 156), 'tw': (153, 132, 137)}, button={'cn': (1205, 549, 1275, 566), 'en': (1203, 552, 1277, 564), 'jp': (1204, 547, 1276, 568), 'tw': (1205, 546, 1275, 567)}, file={'cn': './assets/cn/handler/AUTO_SEARCH_MAP_OPTION_OFF.png', 'en': './assets/en/handler/AUTO_SEARCH_MAP_OPTION_OFF.png', 'jp': './assets/jp/handler/AUTO_SEARCH_MAP_OPTION_OFF.png', 'tw': './assets/tw/handler/AUTO_SEARCH_MAP_OPTION_OFF.png'})
AUTO_SEARCH_MAP_OPTION_ON = Button(area={'cn': (1205, 549, 1275, 566), 'en': (1203, 552, 1277, 564), 'jp': (1203, 547, 1276, 568), 'tw': (1204, 546, 1276, 567)}, color={'cn': (149, 176, 193), 'en': (113, 135, 157), 'jp': (132, 158, 177), 'tw': (110, 133, 156)}, button={'cn': (1205, 549, 1275, 566), 'en': (1203, 552, 1277, 564), 'jp': (1203, 547, 1276, 568), 'tw': (1204, 546, 1276, 567)}, file={'cn': './assets/cn/handler/AUTO_SEARCH_MAP_OPTION_ON.png', 'en': './assets/en/handler/AUTO_SEARCH_MAP_OPTION_ON.png', 'jp': './assets/jp/handler/AUTO_SEARCH_MAP_OPTION_ON.png', 'tw': './assets/tw/handler/AUTO_SEARCH_MAP_OPTION_ON.png'})
AUTO_SEARCH_MENU_CONTINUE = Button(area={'cn': (789, 610, 903, 638), 'en': (851, 612, 981, 634), 'jp': (859, 610, 973, 638), 'tw': (790, 610, 903, 638)}, color={'cn': (147, 182, 224), 'en': (161, 188, 225), 'jp': (139, 173, 218), 'tw': (148, 181, 222)}, button={'cn': (773, 598, 919, 646), 'en': (772, 597, 920, 648), 'jp': (845, 597, 990, 646), 'tw': (776, 601, 919, 645)}, file={'cn': './assets/cn/handler/AUTO_SEARCH_MENU_CONTINUE.png', 'en': './assets/en/handler/AUTO_SEARCH_MENU_CONTINUE.png', 'jp': './assets/jp/handler/AUTO_SEARCH_MENU_CONTINUE.png', 'tw': './assets/tw/handler/AUTO_SEARCH_MENU_CONTINUE.png'})
AUTO_SEARCH_MENU_EXIT = Button(area={'cn': (419, 609, 475, 637), 'en': (352, 612, 402, 633), 'jp': (348, 609, 401, 636), 'tw': (414, 609, 477, 637)}, color={'cn': (198, 199, 201), 'en': (212, 212, 213), 'jp': (184, 184, 187), 'tw': (204, 204, 206)}, button={'cn': (373, 598, 520, 647), 'en': (372, 597, 521, 648), 'jp': (305, 597, 451, 645), 'tw': (393, 604, 498, 644)}, file={'cn': './assets/cn/handler/AUTO_SEARCH_MENU_EXIT.png', 'en': './assets/en/handler/AUTO_SEARCH_MENU_EXIT.png', 'jp': './assets/jp/handler/AUTO_SEARCH_MENU_EXIT.png', 'tw': './assets/tw/handler/AUTO_SEARCH_MENU_EXIT.png'})
AUTO_SEARCH_MENU_CONTINUE = Button(area={'cn': (789, 610, 903, 638), 'en': (781, 612, 910, 634), 'jp': (859, 610, 973, 638), 'tw': (790, 610, 903, 638)}, color={'cn': (147, 182, 224), 'en': (159, 187, 224), 'jp': (139, 173, 218), 'tw': (148, 181, 222)}, button={'cn': (773, 598, 919, 646), 'en': (773, 598, 919, 647), 'jp': (845, 597, 990, 646), 'tw': (776, 601, 919, 645)}, file={'cn': './assets/cn/handler/AUTO_SEARCH_MENU_CONTINUE.png', 'en': './assets/en/handler/AUTO_SEARCH_MENU_CONTINUE.png', 'jp': './assets/jp/handler/AUTO_SEARCH_MENU_CONTINUE.png', 'tw': './assets/tw/handler/AUTO_SEARCH_MENU_CONTINUE.png'})
AUTO_SEARCH_MENU_EXIT = Button(area={'cn': (419, 609, 475, 637), 'en': (421, 611, 472, 633), 'jp': (348, 609, 401, 636), 'tw': (414, 609, 477, 637)}, color={'cn': (198, 199, 201), 'en': (210, 210, 212), 'jp': (184, 184, 187), 'tw': (204, 204, 206)}, button={'cn': (373, 598, 520, 647), 'en': (373, 598, 520, 647), 'jp': (305, 597, 451, 645), 'tw': (393, 604, 498, 644)}, file={'cn': './assets/cn/handler/AUTO_SEARCH_MENU_EXIT.png', 'en': './assets/en/handler/AUTO_SEARCH_MENU_EXIT.png', 'jp': './assets/jp/handler/AUTO_SEARCH_MENU_EXIT.png', 'tw': './assets/tw/handler/AUTO_SEARCH_MENU_EXIT.png'})
AUTO_SEARCH_OFF = Button(area={'cn': (867, 588, 883, 604), 'en': (830, 588, 846, 604), 'jp': (849, 588, 865, 604), 'tw': (867, 588, 883, 604)}, color={'cn': (94, 92, 94), 'en': (90, 89, 92), 'jp': (99, 99, 109), 'tw': (94, 92, 94)}, button={'cn': (867, 588, 883, 604), 'en': (830, 588, 846, 604), 'jp': (849, 588, 865, 604), 'tw': (867, 588, 883, 604)}, file={'cn': './assets/cn/handler/AUTO_SEARCH_OFF.png', 'en': './assets/en/handler/AUTO_SEARCH_OFF.png', 'jp': './assets/jp/handler/AUTO_SEARCH_OFF.png', 'tw': './assets/tw/handler/AUTO_SEARCH_OFF.png'})
AUTO_SEARCH_ON = Button(area={'cn': (867, 588, 883, 604), 'en': (830, 588, 846, 604), 'jp': (849, 588, 865, 604), 'tw': (867, 588, 883, 604)}, color={'cn': (140, 167, 120), 'en': (139, 168, 112), 'jp': (140, 167, 122), 'tw': (140, 167, 120)}, button={'cn': (867, 588, 883, 604), 'en': (830, 588, 846, 604), 'jp': (849, 588, 865, 604), 'tw': (867, 588, 883, 604)}, file={'cn': './assets/cn/handler/AUTO_SEARCH_ON.png', 'en': './assets/en/handler/AUTO_SEARCH_ON.png', 'jp': './assets/jp/handler/AUTO_SEARCH_ON.png', 'tw': './assets/tw/handler/AUTO_SEARCH_ON.png'})
AUTO_SEARCH_SET_ALL = Button(area={'cn': (830, 189, 849, 207), 'en': (814, 192, 832, 209), 'jp': (830, 189, 849, 207), 'tw': (830, 189, 849, 207)}, color={'cn': (38, 39, 40), 'en': (33, 33, 35), 'jp': (43, 41, 42), 'tw': (42, 41, 43)}, button={'cn': (830, 189, 849, 207), 'en': (814, 192, 832, 209), 'jp': (830, 189, 849, 207), 'tw': (830, 189, 849, 207)}, file={'cn': './assets/cn/handler/AUTO_SEARCH_SET_ALL.png', 'en': './assets/en/handler/AUTO_SEARCH_SET_ALL.png', 'jp': './assets/jp/handler/AUTO_SEARCH_SET_ALL.png', 'tw': './assets/tw/handler/AUTO_SEARCH_SET_ALL.png'})

View File

@@ -316,7 +316,8 @@ class InfoHandler(ModuleBase):
image = color_similarity_2d(self.image_crop(story_detect_area), color=story_option_color)
line = cv2.reduce(image, 1, cv2.REDUCE_AVG).flatten()
line[line < 128] = 0
line[line < 200] = 0
line[line >= 200] = 255
parameters = {
# Option is 300`320px x 50~52px.

View File

@@ -40,7 +40,7 @@ class OSShopHandler(OSStatus, MapEventHandler):
ItemGrid:
"""
shop_grid = ButtonGrid(
origin=(238, 220), delta=(188, 225), button_shape=(98, 98), grid_shape=(4, 2), name='SHOP_GRID')
origin=(233, 224), delta=(193, 228), button_shape=(98, 98), grid_shape=(4, 2), name='SHOP_GRID')
shop_items = ItemGrid(
shop_grid, templates={}, amount_area=(60, 74, 96, 95), price_area=(52, 132, 132, 165))
shop_items.price_ocr = OSShopPrice([], letter=(255, 223, 57), threshold=32, name='Price_ocr')

View File

@@ -68,6 +68,15 @@ class Updater(DeployConfig, GitManager, PipManager):
def _check_update(self) -> bool:
self.state = "checking"
if State.deploy_config.GitOverCdn:
if self.goc_client.is_uptodate():
logger.info(f"No update")
return False
else:
logger.info(f"New update available")
return True
source = "origin"
for _ in range(3):
if self.execute(