Compare commits
38 Commits
6ef18ed8c0
...
sync_src
| Author | SHA1 | Date | |
|---|---|---|---|
|
baac90ecf0
|
|||
|
53ec298fed
|
|||
|
b4f18f78ff
|
|||
|
eb9af42f38
|
|||
|
c29d972c6c
|
|||
|
92b34d4760
|
|||
|
04853b6c31
|
|||
|
9604e8962a
|
|||
|
03380b2d71
|
|||
|
c27bd74050
|
|||
|
c3e9945b15
|
|||
|
1dd100ac04
|
|||
|
77dca70af1
|
|||
|
b8ecd0c9d6
|
|||
|
ceb24283f3
|
|||
|
d0c591af3a
|
|||
|
589b0b08ec
|
|||
|
299bd6c687
|
|||
|
e61afaf43b
|
|||
|
30e8c8b21b
|
|||
|
f7b165f589
|
|||
|
08959e5f1c
|
|||
|
e929a1efb1
|
|||
|
8e29d7d2c0
|
|||
|
d3a1a77d6a
|
|||
|
930c741de6
|
|||
|
2c19afdf26
|
|||
|
bbf3bf7c36
|
|||
|
5e4abc147e
|
|||
|
eb8048ccd6
|
|||
|
8bec814b8d
|
|||
|
5fb2810bdc
|
|||
|
8ea95dc340
|
|||
|
9d3e581321
|
|||
|
d47e463365
|
|||
|
0a697e9398
|
|||
|
143f519adb
|
|||
|
af71e797db
|
@@ -15,6 +15,8 @@ The script is still under active development. The following features have been i
|
||||
- [x] **Cafe** Claim rewards / Interact / Second floor
|
||||
- [x] **Club** Claim AP
|
||||
- [x] **Mailbox** Claim rewards
|
||||
- [x] **Bounty** Auto sweep
|
||||
- [x] **Scrimmage** Auto sweep
|
||||
- [x] **Tactical Challenge** Claim rewards / Auto battle
|
||||
|
||||
Supported servers:
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
- [x] **咖啡厅** 领取奖励 / 互动 / 第二咖啡厅
|
||||
- [x] **公会** 领取体力
|
||||
- [x] **邮箱** 领取奖励
|
||||
- [x] **悬赏通缉** 自动扫荡
|
||||
- [x] **学院交流会** 自动扫荡
|
||||
- [x] **战术对抗赛** 领取奖励 / 自动战斗
|
||||
|
||||
目前支持的服务器:
|
||||
@@ -22,6 +24,14 @@
|
||||
- [x] 日服
|
||||
- [x] 国际服 - 全球
|
||||
|
||||
## 已知问题
|
||||
|
||||
若愿意提供其他服务器支持,请开 PR 或 Issue。
|
||||
|
||||
- **大小月卡**:未实现自动领取,~~因为没买过~~,可能不影响使用。愿意提供图片的请开 Issue
|
||||
- **月卡的额外悬赏券和学院交流券**:不太清楚月卡领取额外券的机制,~~因为没买过~~,可能影响相关任务使用券和体力的计算。愿意提供相关信息的请开
|
||||
Issue
|
||||
|
||||
## 相关项目
|
||||
|
||||
- [AzurLaneAutoScript](https://github.com/LmeSzinc/AzurLaneAutoScript): 碧蓝航线自动化脚本
|
||||
|
||||
@@ -34,6 +34,14 @@ class ArisuAutoSweeper(AzurLaneAutoScript):
|
||||
from tasks.mail.mail import Mail
|
||||
Mail(config=self.config, device=self.device).run()
|
||||
|
||||
def bounty(self):
|
||||
from tasks.bounty.bounty import Bounty
|
||||
Bounty(config=self.config, device=self.device).run()
|
||||
|
||||
def scrimmage(self):
|
||||
from tasks.scrimmage.scrimmage import Scrimmage
|
||||
Scrimmage(config=self.config, device=self.device).run()
|
||||
|
||||
def tactical_challenge(self):
|
||||
from tasks.tactical_challenge.tactical_challenge import TacticalChallenge
|
||||
TacticalChallenge(config=self.config, device=self.device).run()
|
||||
|
||||
@@ -285,7 +285,7 @@ pre.rich-traceback-code {
|
||||
*[style*="--header-icon--"] {
|
||||
margin: .25rem auto .25rem;
|
||||
border-radius: 1.5rem;
|
||||
height: 3.5em;
|
||||
height: 3em;
|
||||
}
|
||||
|
||||
*[style*="--header-text--"] {
|
||||
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
@@ -63,6 +63,52 @@
|
||||
"ServerUpdate": "04:00"
|
||||
}
|
||||
},
|
||||
"Bounty": {
|
||||
"Scheduler": {
|
||||
"Enable": true,
|
||||
"NextRun": "2020-01-01 00:00:00",
|
||||
"Command": "Bounty",
|
||||
"ServerUpdate": "04:00"
|
||||
},
|
||||
"Bounty": {
|
||||
"OnError": "skip"
|
||||
},
|
||||
"Highway": {
|
||||
"Stage": 1,
|
||||
"Count": 2
|
||||
},
|
||||
"DesertRailroad": {
|
||||
"Stage": 1,
|
||||
"Count": 2
|
||||
},
|
||||
"Schoolhouse": {
|
||||
"Stage": 1,
|
||||
"Count": 2
|
||||
}
|
||||
},
|
||||
"Scrimmage": {
|
||||
"Scheduler": {
|
||||
"Enable": true,
|
||||
"NextRun": "2020-01-01 00:00:00",
|
||||
"Command": "Scrimmage",
|
||||
"ServerUpdate": "04:00"
|
||||
},
|
||||
"Scrimmage": {
|
||||
"OnError": "skip"
|
||||
},
|
||||
"Trinity": {
|
||||
"Stage": 1,
|
||||
"Count": 2
|
||||
},
|
||||
"Gehenna": {
|
||||
"Stage": 1,
|
||||
"Count": 2
|
||||
},
|
||||
"Millennium": {
|
||||
"Stage": 1,
|
||||
"Count": 2
|
||||
}
|
||||
},
|
||||
"TacticalChallenge": {
|
||||
"Scheduler": {
|
||||
"Enable": true,
|
||||
@@ -84,7 +130,10 @@
|
||||
"ItemStorage": {
|
||||
"AP": {},
|
||||
"Credit": {},
|
||||
"Pyroxene": {}
|
||||
"Pyroxene": {},
|
||||
"BountyTicket": {},
|
||||
"ScrimmageTicket": {},
|
||||
"TacticalChallengeTicket": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,20 +179,29 @@ def iter_assets():
|
||||
if image.attr != '':
|
||||
row = deep_get(data, keys=[image.module, image.assets, image.server, image.frame])
|
||||
row.load_image(image)
|
||||
# Apply `search` of the first frame to all
|
||||
# Set `search`
|
||||
for path, frames in deep_iter(data, depth=3):
|
||||
print(path, frames)
|
||||
# If `search` attribute is set in the first frame, apply to all
|
||||
first = frames[1]
|
||||
search = first.search if first.search else DataAssets.area_to_search(first.area)
|
||||
for frame in frames.values():
|
||||
frame.search = search
|
||||
if first.search:
|
||||
for frame in frames.values():
|
||||
frame.search = first.search
|
||||
else:
|
||||
for frame in frames.values():
|
||||
if frame.search:
|
||||
# Follow frame specific `search`
|
||||
pass
|
||||
else:
|
||||
# Generate `search` from `area`
|
||||
frame.search = DataAssets.area_to_search(frame.area)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def generate_code():
|
||||
all = iter_assets()
|
||||
for module, module_data in all.items():
|
||||
all_assets = iter_assets()
|
||||
for module, module_data in all_assets.items():
|
||||
path = os.path.join(AzurLaneConfig.ASSETS_MODULE, module.split('/', maxsplit=1)[0])
|
||||
output = os.path.join(path, 'assets.py')
|
||||
if os.path.exists(output):
|
||||
@@ -204,7 +213,7 @@ def generate_code():
|
||||
continue
|
||||
os.remove(prev)
|
||||
|
||||
for module, module_data in all.items():
|
||||
for module, module_data in all_assets.items():
|
||||
path = os.path.join(AzurLaneConfig.ASSETS_MODULE, module.split('/', maxsplit=1)[0])
|
||||
output = os.path.join(path, 'assets')
|
||||
gen = CodeGenerator()
|
||||
|
||||
@@ -74,7 +74,7 @@ class Button(Resource):
|
||||
threshold=threshold
|
||||
)
|
||||
|
||||
def match_template(self, image, similarity=0.85) -> bool:
|
||||
def match_template(self, image, similarity=0.85, direct_match=False) -> bool:
|
||||
"""
|
||||
Detects assets by template matching.
|
||||
|
||||
@@ -83,18 +83,45 @@ class Button(Resource):
|
||||
Args:
|
||||
image: Screenshot.
|
||||
similarity (float): 0-1.
|
||||
direct_match: True to ignore `self.search`
|
||||
|
||||
Returns:
|
||||
bool.
|
||||
"""
|
||||
image = crop(image, self.search, copy=False)
|
||||
if not direct_match:
|
||||
image = crop(image, self.search, copy=False)
|
||||
res = cv2.matchTemplate(self.image, image, cv2.TM_CCOEFF_NORMED)
|
||||
_, sim, _, point = cv2.minMaxLoc(res)
|
||||
|
||||
self._button_offset = np.array(point) + self.search[:2] - self.area[:2]
|
||||
return sim > similarity
|
||||
|
||||
def match_template_color(self, image, similarity=0.85, threshold=30) -> bool:
|
||||
def match_multi_template(self, image, similarity=0.85, direct_match=False):
|
||||
"""
|
||||
Detects assets by template matching, return multiple reults
|
||||
|
||||
Args:
|
||||
image: Screenshot.
|
||||
similarity (float): 0-1.
|
||||
direct_match: True to ignore `self.search`
|
||||
|
||||
Returns:
|
||||
list:
|
||||
"""
|
||||
if not direct_match:
|
||||
image = crop(image, self.search, copy=False)
|
||||
res = cv2.matchTemplate(self.image, image, cv2.TM_CCOEFF_NORMED)
|
||||
res = cv2.inRange(res, similarity, 1.)
|
||||
try:
|
||||
points = np.array(cv2.findNonZero(res))[:, 0, :]
|
||||
points += self.search[:2]
|
||||
return points.tolist()
|
||||
except IndexError:
|
||||
# Empty result
|
||||
# IndexError: too many indices for array: array is 0-dimensional, but 3 were indexed
|
||||
return []
|
||||
|
||||
def match_template_color(self, image, similarity=0.85, threshold=30, direct_match=False) -> bool:
|
||||
"""
|
||||
Template match first, color match then
|
||||
|
||||
@@ -102,11 +129,12 @@ class Button(Resource):
|
||||
image: Screenshot.
|
||||
similarity (float): 0-1.
|
||||
threshold (int): Default to 10.
|
||||
direct_match: True to ignore `self.search`
|
||||
|
||||
Returns:
|
||||
|
||||
bool.
|
||||
"""
|
||||
matched = self.match_template(image, similarity=similarity)
|
||||
matched = self.match_template(image, similarity=similarity, direct_match=direct_match)
|
||||
if not matched:
|
||||
return False
|
||||
|
||||
@@ -124,10 +152,10 @@ class ButtonWrapper(Resource):
|
||||
self.name = name
|
||||
self.data_buttons = kwargs
|
||||
self._matched_button: t.Optional[Button] = None
|
||||
self.resource_add(self.name)
|
||||
self.resource_add(f'{name}:{next(self.iter_buttons(), None)}')
|
||||
|
||||
def resource_release(self):
|
||||
del_cached_property(self, 'assets')
|
||||
del_cached_property(self, 'buttons')
|
||||
self._matched_button = None
|
||||
|
||||
def __str__(self):
|
||||
@@ -144,16 +172,25 @@ class ButtonWrapper(Resource):
|
||||
def __bool__(self):
|
||||
return True
|
||||
|
||||
def iter_buttons(self) -> t.Iterator[Button]:
|
||||
for _, assets in self.data_buttons.items():
|
||||
if isinstance(assets, Button):
|
||||
yield assets
|
||||
elif isinstance(assets, list):
|
||||
for asset in assets:
|
||||
yield asset
|
||||
|
||||
@cached_property
|
||||
def buttons(self) -> t.List[Button]:
|
||||
# for trial in [server.lang, 'share', 'cn']:
|
||||
for trial in [server.lang, 'share', 'jp']:
|
||||
assets = self.data_buttons.get(trial, None)
|
||||
if assets is not None:
|
||||
for trial in [server.lang, 'share', 'cn']:
|
||||
try:
|
||||
assets = self.data_buttons[trial]
|
||||
if isinstance(assets, Button):
|
||||
return [assets]
|
||||
elif isinstance(assets, list):
|
||||
return assets
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
raise ScriptError(f'ButtonWrapper({self}) on server {server.lang} has no fallback button')
|
||||
|
||||
@@ -164,16 +201,45 @@ class ButtonWrapper(Resource):
|
||||
return True
|
||||
return False
|
||||
|
||||
def match_template(self, image, similarity=0.85) -> bool:
|
||||
def match_template(self, image, similarity=0.85, direct_match=False) -> bool:
|
||||
for assets in self.buttons:
|
||||
if assets.match_template(image, similarity=similarity):
|
||||
if assets.match_template(image, similarity=similarity, direct_match=direct_match):
|
||||
self._matched_button = assets
|
||||
return True
|
||||
return False
|
||||
|
||||
def match_template_color(self, image, similarity=0.85, threshold=30) -> bool:
|
||||
def match_multi_template(self, image, similarity=0.85, threshold=5, direct_match=False):
|
||||
"""
|
||||
Detects assets by template matching, return multiple results
|
||||
|
||||
Args:
|
||||
image: Screenshot.
|
||||
similarity (float): 0-1.
|
||||
threshold:
|
||||
direct_match: True to ignore `self.search`
|
||||
|
||||
Returns:
|
||||
list[ClickButton]:
|
||||
"""
|
||||
ps = []
|
||||
for assets in self.buttons:
|
||||
if assets.match_template_color(image, similarity=similarity, threshold=threshold):
|
||||
ps += assets.match_multi_template(image, similarity=similarity, direct_match=direct_match)
|
||||
if not ps:
|
||||
return []
|
||||
|
||||
from module.base.utils.points import Points
|
||||
ps = Points(ps).group(threshold=threshold)
|
||||
area_list = [area_offset(self.area, p - self.area[:2]) for p in ps]
|
||||
button_list = [area_offset(self.button, p - self.area[:2]) for p in ps]
|
||||
return [
|
||||
ClickButton(area=info[0], button=info[1], name=f'{self.name}_result{i}')
|
||||
for i, info in enumerate(zip(area_list, button_list))
|
||||
]
|
||||
|
||||
def match_template_color(self, image, similarity=0.85, threshold=30, direct_match=False) -> bool:
|
||||
for assets in self.buttons:
|
||||
if assets.match_template_color(
|
||||
image, similarity=similarity, threshold=threshold, direct_match=direct_match):
|
||||
self._matched_button = assets
|
||||
return True
|
||||
return False
|
||||
@@ -222,18 +288,32 @@ class ButtonWrapper(Resource):
|
||||
"""
|
||||
if isinstance(button, ButtonWrapper):
|
||||
button = button.matched_button
|
||||
for b in self.buttons:
|
||||
for b in self.iter_buttons():
|
||||
b.load_offset(button)
|
||||
|
||||
def clear_offset(self):
|
||||
for b in self.buttons:
|
||||
for b in self.iter_buttons():
|
||||
b.clear_offset()
|
||||
|
||||
def load_search(self, area):
|
||||
"""
|
||||
Set `search` attribute.
|
||||
Note that this method is irreversible.
|
||||
|
||||
Args:
|
||||
area:
|
||||
"""
|
||||
for b in self.iter_buttons():
|
||||
b.search = area
|
||||
|
||||
|
||||
class ClickButton:
|
||||
def __init__(self, button, name='CLICK_BUTTON'):
|
||||
self.area = button
|
||||
self.button = button
|
||||
def __init__(self, area, button=None, name='CLICK_BUTTON'):
|
||||
self.area = area
|
||||
if button is None:
|
||||
self.button = area
|
||||
else:
|
||||
self.button = button
|
||||
self.name = name
|
||||
|
||||
def __str__(self):
|
||||
@@ -265,4 +345,4 @@ def match_template(image, template, similarity=0.85):
|
||||
"""
|
||||
res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
|
||||
_, sim, _, point = cv2.minMaxLoc(res)
|
||||
return sim > similarity
|
||||
return sim > similarity
|
||||
@@ -1,11 +1,11 @@
|
||||
import re
|
||||
|
||||
import module.config.server as server
|
||||
from module.base.decorator import cached_property, del_cached_property
|
||||
from module.base.decorator import cached_property
|
||||
|
||||
|
||||
def get_assets_from_file(file, regex):
|
||||
def get_assets_from_file(file):
|
||||
assets = set()
|
||||
regex = re.compile(r"file='(.*?)'")
|
||||
with open(file, 'r', encoding='utf-8') as f:
|
||||
for row in f.readlines():
|
||||
result = regex.search(row)
|
||||
@@ -20,11 +20,9 @@ class PreservedAssets:
|
||||
assets = set()
|
||||
assets |= get_assets_from_file(
|
||||
file='./tasks/base/assets/assets_base_page.py',
|
||||
regex=re.compile(r'^([A-Za-z][A-Za-z0-9_]+) = ')
|
||||
)
|
||||
assets |= get_assets_from_file(
|
||||
file='./tasks/base/assets/assets_base_popup.py',
|
||||
regex=re.compile(r'^([A-Za-z][A-Za-z0-9_]+) = ')
|
||||
)
|
||||
return assets
|
||||
|
||||
@@ -44,11 +42,13 @@ class Resource:
|
||||
|
||||
@classmethod
|
||||
def is_loaded(cls, obj):
|
||||
if hasattr(obj, '_image') and obj._image is None:
|
||||
return False
|
||||
elif hasattr(obj, 'image') and obj.image is None:
|
||||
return False
|
||||
return True
|
||||
if hasattr(obj, '_image') and obj._image is not None:
|
||||
return True
|
||||
if hasattr(obj, 'image') and obj.image is not None:
|
||||
return True
|
||||
if hasattr(obj, 'buttons') and obj.buttons is not None:
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def resource_show(cls):
|
||||
@@ -56,11 +56,16 @@ class Resource:
|
||||
logger.hr('Show resource')
|
||||
for key, obj in cls.instances.items():
|
||||
if cls.is_loaded(obj):
|
||||
continue
|
||||
logger.info(f'{obj}: {key}')
|
||||
logger.info(f'{obj}: {key}')
|
||||
|
||||
|
||||
def release_resources(next_task=''):
|
||||
# Release all OCR models
|
||||
# det models take 400MB
|
||||
if not next_task:
|
||||
from module.ocr.models import OCR_MODEL
|
||||
OCR_MODEL.resource_release()
|
||||
|
||||
# Release assets cache
|
||||
# module.ui has about 80 assets and takes about 3MB
|
||||
# Alas has about 800 assets, but they are not all loaded.
|
||||
@@ -74,4 +79,4 @@ def release_resources(next_task=''):
|
||||
obj.resource_release()
|
||||
|
||||
# Useless in most cases, but just call it
|
||||
# gc.collect()
|
||||
# gc.collect()
|
||||
@@ -257,6 +257,191 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Bounty": {
|
||||
"Scheduler": {
|
||||
"Enable": {
|
||||
"type": "checkbox",
|
||||
"value": true,
|
||||
"option": [
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"NextRun": {
|
||||
"type": "datetime",
|
||||
"value": "2020-01-01 00:00:00",
|
||||
"validate": "datetime"
|
||||
},
|
||||
"Command": {
|
||||
"type": "input",
|
||||
"value": "Bounty",
|
||||
"display": "hide"
|
||||
},
|
||||
"ServerUpdate": {
|
||||
"type": "input",
|
||||
"value": "04:00",
|
||||
"display": "hide"
|
||||
}
|
||||
},
|
||||
"Bounty": {
|
||||
"OnError": {
|
||||
"type": "select",
|
||||
"value": "skip",
|
||||
"option": [
|
||||
"stop",
|
||||
"skip"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Highway": {
|
||||
"Stage": {
|
||||
"type": "select",
|
||||
"value": 1,
|
||||
"option": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9
|
||||
]
|
||||
},
|
||||
"Count": {
|
||||
"type": "input",
|
||||
"value": 2
|
||||
}
|
||||
},
|
||||
"DesertRailroad": {
|
||||
"Stage": {
|
||||
"type": "select",
|
||||
"value": 1,
|
||||
"option": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9
|
||||
]
|
||||
},
|
||||
"Count": {
|
||||
"type": "input",
|
||||
"value": 2
|
||||
}
|
||||
},
|
||||
"Schoolhouse": {
|
||||
"Stage": {
|
||||
"type": "select",
|
||||
"value": 1,
|
||||
"option": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9
|
||||
]
|
||||
},
|
||||
"Count": {
|
||||
"type": "input",
|
||||
"value": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"Scrimmage": {
|
||||
"Scheduler": {
|
||||
"Enable": {
|
||||
"type": "checkbox",
|
||||
"value": true,
|
||||
"option": [
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"NextRun": {
|
||||
"type": "datetime",
|
||||
"value": "2020-01-01 00:00:00",
|
||||
"validate": "datetime"
|
||||
},
|
||||
"Command": {
|
||||
"type": "input",
|
||||
"value": "Scrimmage",
|
||||
"display": "hide"
|
||||
},
|
||||
"ServerUpdate": {
|
||||
"type": "input",
|
||||
"value": "04:00",
|
||||
"display": "hide"
|
||||
}
|
||||
},
|
||||
"Scrimmage": {
|
||||
"OnError": {
|
||||
"type": "select",
|
||||
"value": "skip",
|
||||
"option": [
|
||||
"stop",
|
||||
"skip"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Trinity": {
|
||||
"Stage": {
|
||||
"type": "select",
|
||||
"value": 1,
|
||||
"option": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
]
|
||||
},
|
||||
"Count": {
|
||||
"type": "input",
|
||||
"value": 2
|
||||
}
|
||||
},
|
||||
"Gehenna": {
|
||||
"Stage": {
|
||||
"type": "select",
|
||||
"value": 1,
|
||||
"option": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
]
|
||||
},
|
||||
"Count": {
|
||||
"type": "input",
|
||||
"value": 2
|
||||
}
|
||||
},
|
||||
"Millennium": {
|
||||
"Stage": {
|
||||
"type": "select",
|
||||
"value": 1,
|
||||
"option": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
]
|
||||
},
|
||||
"Count": {
|
||||
"type": "input",
|
||||
"value": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"TacticalChallenge": {
|
||||
"Scheduler": {
|
||||
"Enable": {
|
||||
@@ -348,6 +533,30 @@
|
||||
"stored": "StoredInt",
|
||||
"order": 3,
|
||||
"color": "#21befc"
|
||||
},
|
||||
"BountyTicket": {
|
||||
"type": "stored",
|
||||
"value": {},
|
||||
"display": "hide",
|
||||
"stored": "StoredBountyTicket",
|
||||
"order": 4,
|
||||
"color": "#94cb44"
|
||||
},
|
||||
"ScrimmageTicket": {
|
||||
"type": "stored",
|
||||
"value": {},
|
||||
"display": "hide",
|
||||
"stored": "StoredScrimmageTicket",
|
||||
"order": 5,
|
||||
"color": "#f86c6a"
|
||||
},
|
||||
"TacticalChallengeTicket": {
|
||||
"type": "stored",
|
||||
"value": {},
|
||||
"display": "hide",
|
||||
"stored": "StoredTacticalChallengeTicket",
|
||||
"order": 6,
|
||||
"color": "#7ac8e5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,48 @@ Cafe:
|
||||
AutoAdjust: true
|
||||
SecondCafe: true
|
||||
|
||||
Bounty:
|
||||
OnError:
|
||||
value: skip
|
||||
option: [ stop, skip ]
|
||||
|
||||
Highway:
|
||||
Stage:
|
||||
value: 1
|
||||
option: [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
|
||||
Count: 2
|
||||
DesertRailroad:
|
||||
Stage:
|
||||
value: 1
|
||||
option: [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
|
||||
Count: 2
|
||||
Schoolhouse:
|
||||
Stage:
|
||||
value: 1
|
||||
option: [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
|
||||
Count: 2
|
||||
|
||||
Scrimmage:
|
||||
OnError:
|
||||
value: skip
|
||||
option: [ stop, skip ]
|
||||
|
||||
Trinity:
|
||||
Stage:
|
||||
value: 1
|
||||
option: [ 1, 2, 3, 4 ]
|
||||
Count: 2
|
||||
Gehenna:
|
||||
Stage:
|
||||
value: 1
|
||||
option: [ 1, 2, 3, 4 ]
|
||||
Count: 2
|
||||
Millennium:
|
||||
Stage:
|
||||
value: 1
|
||||
option: [ 1, 2, 3, 4 ]
|
||||
Count: 2
|
||||
|
||||
TacticalChallenge:
|
||||
PlayerSelect:
|
||||
value: 0
|
||||
@@ -99,3 +141,15 @@ ItemStorage:
|
||||
stored: StoredInt
|
||||
order: 3
|
||||
color: "#21befc"
|
||||
BountyTicket:
|
||||
stored: StoredBountyTicket
|
||||
order: 4
|
||||
color: "#94cb44"
|
||||
ScrimmageTicket:
|
||||
stored: StoredScrimmageTicket
|
||||
order: 5
|
||||
color: "#f86c6a"
|
||||
TacticalChallengeTicket:
|
||||
stored: StoredTacticalChallengeTicket
|
||||
order: 6
|
||||
color: "#7ac8e5"
|
||||
|
||||
@@ -13,6 +13,12 @@ Mail:
|
||||
Circle:
|
||||
Scheduler:
|
||||
Enable: true
|
||||
Bounty:
|
||||
Scheduler:
|
||||
Enable: true
|
||||
Scrimmage:
|
||||
Scheduler:
|
||||
Enable: true
|
||||
TacticalChallenge:
|
||||
Scheduler:
|
||||
Enable: true
|
||||
@@ -14,6 +14,8 @@
|
||||
"Cafe",
|
||||
"Mail",
|
||||
"Circle",
|
||||
"Bounty",
|
||||
"Scrimmage",
|
||||
"TacticalChallenge",
|
||||
"DataUpdate"
|
||||
]
|
||||
|
||||
@@ -35,5 +35,44 @@
|
||||
},
|
||||
"order": 3,
|
||||
"color": "#21befc"
|
||||
},
|
||||
"BountyTicket": {
|
||||
"name": "BountyTicket",
|
||||
"path": "DataUpdate.ItemStorage.BountyTicket",
|
||||
"i18n": "ItemStorage.BountyTicket.name",
|
||||
"stored": "StoredBountyTicket",
|
||||
"attrs": {
|
||||
"time": "2020-01-01 00:00:00",
|
||||
"total": 6,
|
||||
"value": 0
|
||||
},
|
||||
"order": 4,
|
||||
"color": "#94cb44"
|
||||
},
|
||||
"ScrimmageTicket": {
|
||||
"name": "ScrimmageTicket",
|
||||
"path": "DataUpdate.ItemStorage.ScrimmageTicket",
|
||||
"i18n": "ItemStorage.ScrimmageTicket.name",
|
||||
"stored": "StoredScrimmageTicket",
|
||||
"attrs": {
|
||||
"time": "2020-01-01 00:00:00",
|
||||
"total": 6,
|
||||
"value": 0
|
||||
},
|
||||
"order": 5,
|
||||
"color": "#f86c6a"
|
||||
},
|
||||
"TacticalChallengeTicket": {
|
||||
"name": "TacticalChallengeTicket",
|
||||
"path": "DataUpdate.ItemStorage.TacticalChallengeTicket",
|
||||
"i18n": "ItemStorage.TacticalChallengeTicket.name",
|
||||
"stored": "StoredTacticalChallengeTicket",
|
||||
"attrs": {
|
||||
"time": "2020-01-01 00:00:00",
|
||||
"total": 5,
|
||||
"value": 0
|
||||
},
|
||||
"order": 6,
|
||||
"color": "#7ac8e5"
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,18 @@ Daily:
|
||||
- Scheduler
|
||||
Circle:
|
||||
- Scheduler
|
||||
Bounty:
|
||||
- Scheduler
|
||||
- Bounty
|
||||
- Highway
|
||||
- DesertRailroad
|
||||
- Schoolhouse
|
||||
Scrimmage:
|
||||
- Scheduler
|
||||
- Scrimmage
|
||||
- Trinity
|
||||
- Gehenna
|
||||
- Millennium
|
||||
TacticalChallenge:
|
||||
- Scheduler
|
||||
- TacticalChallenge
|
||||
|
||||
@@ -499,19 +499,6 @@ class AzurLaneConfig(ConfigUpdater, ManualConfig, GeneratedConfig, ConfigWatcher
|
||||
self.task_call('DailyQuest')
|
||||
self.task_stop()
|
||||
|
||||
def update_battle_pass_quests(self):
|
||||
"""
|
||||
Raises:
|
||||
TaskEnd: Call task `BattlePass` and stop current task
|
||||
"""
|
||||
if self.stored.BattlePassTodayQuest.is_expired():
|
||||
if self.stored.BattlePassLevel.is_full():
|
||||
logger.info('BattlePassLevel full, no updates')
|
||||
else:
|
||||
logger.info('BattlePassTodayQuest expired, call task to update')
|
||||
self.task_call('BattlePass')
|
||||
self.task_stop()
|
||||
|
||||
@property
|
||||
def DEVICE_SCREENSHOT_METHOD(self):
|
||||
return self.Emulator_ScreenshotMethod
|
||||
|
||||
@@ -45,6 +45,36 @@ class GeneratedConfig:
|
||||
Cafe_AutoAdjust = True
|
||||
Cafe_SecondCafe = True
|
||||
|
||||
# Group `Bounty`
|
||||
Bounty_OnError = 'skip' # stop, skip
|
||||
|
||||
# Group `Highway`
|
||||
Highway_Stage = 1 # 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
Highway_Count = 2
|
||||
|
||||
# Group `DesertRailroad`
|
||||
DesertRailroad_Stage = 1 # 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
DesertRailroad_Count = 2
|
||||
|
||||
# Group `Schoolhouse`
|
||||
Schoolhouse_Stage = 1 # 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
Schoolhouse_Count = 2
|
||||
|
||||
# Group `Scrimmage`
|
||||
Scrimmage_OnError = 'skip' # stop, skip
|
||||
|
||||
# Group `Trinity`
|
||||
Trinity_Stage = 1 # 1, 2, 3, 4
|
||||
Trinity_Count = 2
|
||||
|
||||
# Group `Gehenna`
|
||||
Gehenna_Stage = 1 # 1, 2, 3, 4
|
||||
Gehenna_Count = 2
|
||||
|
||||
# Group `Millennium`
|
||||
Millennium_Stage = 1 # 1, 2, 3, 4
|
||||
Millennium_Count = 2
|
||||
|
||||
# Group `TacticalChallenge`
|
||||
TacticalChallenge_PlayerSelect = 0 # 0, 1, 2, 3
|
||||
|
||||
@@ -52,3 +82,6 @@ class GeneratedConfig:
|
||||
ItemStorage_AP = {}
|
||||
ItemStorage_Credit = {}
|
||||
ItemStorage_Pyroxene = {}
|
||||
ItemStorage_BountyTicket = {}
|
||||
ItemStorage_ScrimmageTicket = {}
|
||||
ItemStorage_TacticalChallengeTicket = {}
|
||||
|
||||
@@ -8,7 +8,8 @@ class ManualConfig:
|
||||
|
||||
SCHEDULER_PRIORITY = """
|
||||
Restart
|
||||
> Cafe > Circle > Mail > DataUpdate > TacticalChallenge
|
||||
> Cafe > Circle > Mail > DataUpdate > Bounty
|
||||
> Scrimmage > TacticalChallenge
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
@@ -458,7 +458,8 @@ class ConfigUpdater:
|
||||
value = deep_get(old, keys=keys, default=data['value'])
|
||||
typ = data['type']
|
||||
display = data.get('display')
|
||||
if is_template or value is None or value == '' or typ == 'lock' or (display == 'hide' and typ != 'stored'):
|
||||
if (is_template or value is None or value == ''
|
||||
or typ in ['lock', 'state'] or (display == 'hide' and typ != 'stored')):
|
||||
value = data['value']
|
||||
value = parse_value(value, data=data)
|
||||
deep_set(new, keys=keys, value=value)
|
||||
|
||||
@@ -30,6 +30,14 @@
|
||||
"name": "Club",
|
||||
"help": ""
|
||||
},
|
||||
"Bounty": {
|
||||
"name": "Bounty",
|
||||
"help": ""
|
||||
},
|
||||
"Scrimmage": {
|
||||
"name": "Scrimmage",
|
||||
"help": ""
|
||||
},
|
||||
"TacticalChallenge": {
|
||||
"name": "Tactical Challenge",
|
||||
"help": ""
|
||||
@@ -209,6 +217,153 @@
|
||||
"help": "JP server only\nEnable auto switch to second floor and perform interaction"
|
||||
}
|
||||
},
|
||||
"Bounty": {
|
||||
"_info": {
|
||||
"name": "Bounty Settings",
|
||||
"help": ""
|
||||
},
|
||||
"OnError": {
|
||||
"name": "Error Handling",
|
||||
"help": "Perform the selected action when an error occurs (ticket not enough or any invalid setting)",
|
||||
"stop": "Stop script",
|
||||
"skip": "Skip current task"
|
||||
}
|
||||
},
|
||||
"Highway": {
|
||||
"_info": {
|
||||
"name": "Overpass",
|
||||
"help": ""
|
||||
},
|
||||
"Stage": {
|
||||
"name": "Select Stage",
|
||||
"help": "",
|
||||
"1": "01 - Overpass A",
|
||||
"2": "02 - Overpass B",
|
||||
"3": "03 - Overpass C",
|
||||
"4": "04 - Overpass D",
|
||||
"5": "05 - Overpass E",
|
||||
"6": "06 - Overpass F",
|
||||
"7": "07 - Overpass G",
|
||||
"8": "08 - Overpass H",
|
||||
"9": "09 - Overpass I"
|
||||
},
|
||||
"Count": {
|
||||
"name": "Sweep X times",
|
||||
"help": "Sweep the selected stage for X times"
|
||||
}
|
||||
},
|
||||
"DesertRailroad": {
|
||||
"_info": {
|
||||
"name": "Desert Railroad",
|
||||
"help": ""
|
||||
},
|
||||
"Stage": {
|
||||
"name": "Select Stage",
|
||||
"help": "",
|
||||
"1": "01 - Abandoned Train A",
|
||||
"2": "02 - Abandoned Train B",
|
||||
"3": "03 - Abandoned Train C",
|
||||
"4": "04 - Abandoned Train D",
|
||||
"5": "05 - Abandoned Train E",
|
||||
"6": "06 - Abandoned Train F",
|
||||
"7": "07 - Abandoned Train G",
|
||||
"8": "08 - Abandoned Train H",
|
||||
"9": "09 - Abandoned Train I"
|
||||
},
|
||||
"Count": {
|
||||
"name": "Sweep X times",
|
||||
"help": "Sweep the selected stage for X times"
|
||||
}
|
||||
},
|
||||
"Schoolhouse": {
|
||||
"_info": {
|
||||
"name": "Classroom",
|
||||
"help": ""
|
||||
},
|
||||
"Stage": {
|
||||
"name": "Select Stage",
|
||||
"help": "",
|
||||
"1": "01 - Besieged Classroom A",
|
||||
"2": "02 - Besieged Classroom B",
|
||||
"3": "03 - Besieged Classroom C",
|
||||
"4": "04 - Besieged Classroom D",
|
||||
"5": "05 - Besieged Classroom E",
|
||||
"6": "06 - Besieged Classroom F",
|
||||
"7": "07 - Besieged Classroom G",
|
||||
"8": "08 - Besieged Classroom H",
|
||||
"9": "09 - Besieged Classroom I"
|
||||
},
|
||||
"Count": {
|
||||
"name": "Sweep X times",
|
||||
"help": "Sweep the selected stage for X times"
|
||||
}
|
||||
},
|
||||
"Scrimmage": {
|
||||
"_info": {
|
||||
"name": "Scrimmage Settings",
|
||||
"help": ""
|
||||
},
|
||||
"OnError": {
|
||||
"name": "Error Handling",
|
||||
"help": "Perform the selected action when an error occurs (ticket not enough or any invalid setting)",
|
||||
"stop": "Stop script",
|
||||
"skip": "Skip current task"
|
||||
}
|
||||
},
|
||||
"Trinity": {
|
||||
"_info": {
|
||||
"name": "Trinity",
|
||||
"help": ""
|
||||
},
|
||||
"Stage": {
|
||||
"name": "Select Stage",
|
||||
"help": "",
|
||||
"1": "01 - Trinity A",
|
||||
"2": "02 - Trinity B",
|
||||
"3": "03 - Trinity C",
|
||||
"4": "04 - Trinity D"
|
||||
},
|
||||
"Count": {
|
||||
"name": "Sweep X times",
|
||||
"help": "Sweep the selected stage for X times"
|
||||
}
|
||||
},
|
||||
"Gehenna": {
|
||||
"_info": {
|
||||
"name": "Gehenna",
|
||||
"help": ""
|
||||
},
|
||||
"Stage": {
|
||||
"name": "Select Stage",
|
||||
"help": "",
|
||||
"1": "01 - Gehenna A",
|
||||
"2": "02 - Gehenna B",
|
||||
"3": "03 - Gehenna C",
|
||||
"4": "04 - Gehenna D"
|
||||
},
|
||||
"Count": {
|
||||
"name": "Sweep X times",
|
||||
"help": "Sweep the selected stage for X times"
|
||||
}
|
||||
},
|
||||
"Millennium": {
|
||||
"_info": {
|
||||
"name": "Millennium",
|
||||
"help": ""
|
||||
},
|
||||
"Stage": {
|
||||
"name": "Select Stage",
|
||||
"help": "",
|
||||
"1": "01 - Millennium A",
|
||||
"2": "02 - Millennium B",
|
||||
"3": "03 - Millennium C",
|
||||
"4": "04 - Millennium D"
|
||||
},
|
||||
"Count": {
|
||||
"name": "Sweep X times",
|
||||
"help": "Sweep the selected stage for X times"
|
||||
}
|
||||
},
|
||||
"TacticalChallenge": {
|
||||
"_info": {
|
||||
"name": "Tactical Challenge Settings",
|
||||
@@ -239,6 +394,18 @@
|
||||
"Pyroxene": {
|
||||
"name": "Pyroxene",
|
||||
"help": ""
|
||||
},
|
||||
"BountyTicket": {
|
||||
"name": "Bounty",
|
||||
"help": ""
|
||||
},
|
||||
"ScrimmageTicket": {
|
||||
"name": "Scrimmage",
|
||||
"help": ""
|
||||
},
|
||||
"TacticalChallengeTicket": {
|
||||
"name": "Tactical Challenge",
|
||||
"help": ""
|
||||
}
|
||||
},
|
||||
"Gui": {
|
||||
|
||||
@@ -30,6 +30,14 @@
|
||||
"name": "公会",
|
||||
"help": "社团 / 小组"
|
||||
},
|
||||
"Bounty": {
|
||||
"name": "悬赏通缉",
|
||||
"help": ""
|
||||
},
|
||||
"Scrimmage": {
|
||||
"name": "学院交流会",
|
||||
"help": ""
|
||||
},
|
||||
"TacticalChallenge": {
|
||||
"name": "战术对抗赛",
|
||||
"help": "战术大赛 / 竞技场"
|
||||
@@ -209,6 +217,153 @@
|
||||
"help": "仅支持日服\n自动切换第二咖啡厅进行互动点击"
|
||||
}
|
||||
},
|
||||
"Bounty": {
|
||||
"_info": {
|
||||
"name": "悬赏通缉设置",
|
||||
"help": ""
|
||||
},
|
||||
"OnError": {
|
||||
"name": "错误处理",
|
||||
"help": "当出现票券不足或设置有误等错误时,执行该操作",
|
||||
"stop": "停止脚本运行",
|
||||
"skip": "跳过当前任务"
|
||||
}
|
||||
},
|
||||
"Highway": {
|
||||
"_info": {
|
||||
"name": "高架公路",
|
||||
"help": ""
|
||||
},
|
||||
"Stage": {
|
||||
"name": "选择关卡",
|
||||
"help": "",
|
||||
"1": "01 - 高架公路 A",
|
||||
"2": "02 - 高架公路 B",
|
||||
"3": "03 - 高架公路 C",
|
||||
"4": "04 - 高架公路 D",
|
||||
"5": "05 - 高架公路 E",
|
||||
"6": "06 - 高架公路 F",
|
||||
"7": "07 - 高架公路 G",
|
||||
"8": "08 - 高架公路 H",
|
||||
"9": "09 - 高架公路 I"
|
||||
},
|
||||
"Count": {
|
||||
"name": "扫荡次数",
|
||||
"help": "扫荡所选择关卡 X 次"
|
||||
}
|
||||
},
|
||||
"DesertRailroad": {
|
||||
"_info": {
|
||||
"name": "沙漠铁路",
|
||||
"help": ""
|
||||
},
|
||||
"Stage": {
|
||||
"name": "选择关卡",
|
||||
"help": "",
|
||||
"1": "01 - 被遗弃的列车 A",
|
||||
"2": "02 - 被遗弃的列车 B",
|
||||
"3": "03 - 被遗弃的列车 C",
|
||||
"4": "04 - 被遗弃的列车 D",
|
||||
"5": "05 - 被遗弃的列车 E",
|
||||
"6": "06 - 被遗弃的列车 F",
|
||||
"7": "07 - 被遗弃的列车 G",
|
||||
"8": "08 - 被遗弃的列车 H",
|
||||
"9": "09 - 被遗弃的列车 I"
|
||||
},
|
||||
"Count": {
|
||||
"name": "扫荡次数",
|
||||
"help": "扫荡所选择关卡 X 次"
|
||||
}
|
||||
},
|
||||
"Schoolhouse": {
|
||||
"_info": {
|
||||
"name": "教室",
|
||||
"help": ""
|
||||
},
|
||||
"Stage": {
|
||||
"name": "选择关卡",
|
||||
"help": "",
|
||||
"1": "01 - 被袭击的教室 A",
|
||||
"2": "02 - 被袭击的教室 B",
|
||||
"3": "03 - 被袭击的教室 C",
|
||||
"4": "04 - 被袭击的教室 D",
|
||||
"5": "05 - 被袭击的教室 E",
|
||||
"6": "06 - 被袭击的教室 F",
|
||||
"7": "07 - 被袭击的教室 G",
|
||||
"8": "08 - 被袭击的教室 H",
|
||||
"9": "09 - 被袭击的教室 I"
|
||||
},
|
||||
"Count": {
|
||||
"name": "扫荡次数",
|
||||
"help": "扫荡所选择关卡 X 次"
|
||||
}
|
||||
},
|
||||
"Scrimmage": {
|
||||
"_info": {
|
||||
"name": "学院交流会设置",
|
||||
"help": ""
|
||||
},
|
||||
"OnError": {
|
||||
"name": "错误处理",
|
||||
"help": "当出现票券不足或设置有误等错误时,执行该操作",
|
||||
"stop": "停止脚本运行",
|
||||
"skip": "跳过当前任务"
|
||||
}
|
||||
},
|
||||
"Trinity": {
|
||||
"_info": {
|
||||
"name": "三一",
|
||||
"help": ""
|
||||
},
|
||||
"Stage": {
|
||||
"name": "选择关卡",
|
||||
"help": "",
|
||||
"1": "01 - 三一 A",
|
||||
"2": "02 - 三一 B",
|
||||
"3": "03 - 三一 C",
|
||||
"4": "04 - 三一 D"
|
||||
},
|
||||
"Count": {
|
||||
"name": "扫荡次数",
|
||||
"help": "扫荡所选择关卡 X 次"
|
||||
}
|
||||
},
|
||||
"Gehenna": {
|
||||
"_info": {
|
||||
"name": "格黑娜",
|
||||
"help": ""
|
||||
},
|
||||
"Stage": {
|
||||
"name": "选择关卡",
|
||||
"help": "",
|
||||
"1": "01 - 格黑娜 A",
|
||||
"2": "02 - 格黑娜 B",
|
||||
"3": "03 - 格黑娜 C",
|
||||
"4": "04 - 格黑娜 D"
|
||||
},
|
||||
"Count": {
|
||||
"name": "扫荡次数",
|
||||
"help": "扫荡所选择关卡 X 次"
|
||||
}
|
||||
},
|
||||
"Millennium": {
|
||||
"_info": {
|
||||
"name": "千年",
|
||||
"help": ""
|
||||
},
|
||||
"Stage": {
|
||||
"name": "选择关卡",
|
||||
"help": "",
|
||||
"1": "01 - 千年 A",
|
||||
"2": "02 - 千年 B",
|
||||
"3": "03 - 千年 C",
|
||||
"4": "04 - 千年 D"
|
||||
},
|
||||
"Count": {
|
||||
"name": "扫荡次数",
|
||||
"help": "扫荡所选择关卡 X 次"
|
||||
}
|
||||
},
|
||||
"TacticalChallenge": {
|
||||
"_info": {
|
||||
"name": "战术对抗赛设置",
|
||||
@@ -239,6 +394,18 @@
|
||||
"Pyroxene": {
|
||||
"name": "青辉石",
|
||||
"help": ""
|
||||
},
|
||||
"BountyTicket": {
|
||||
"name": "悬赏通缉",
|
||||
"help": ""
|
||||
},
|
||||
"ScrimmageTicket": {
|
||||
"name": "学院交流会",
|
||||
"help": ""
|
||||
},
|
||||
"TacticalChallengeTicket": {
|
||||
"name": "战术对抗赛",
|
||||
"help": ""
|
||||
}
|
||||
},
|
||||
"Gui": {
|
||||
|
||||
@@ -3,6 +3,8 @@ from functools import cached_property as functools_cached_property
|
||||
|
||||
from module.base.decorator import cached_property
|
||||
from module.config.utils import DEFAULT_TIME, deep_get, get_server_last_monday_update, get_server_last_update
|
||||
|
||||
|
||||
# from module.exception import ScriptError
|
||||
|
||||
|
||||
@@ -173,6 +175,20 @@ class StoredCounter(StoredBase):
|
||||
class StoredAP(StoredCounter):
|
||||
pass
|
||||
|
||||
|
||||
class StoredBountyTicket(StoredCounter, StoredExpiredAt0400):
|
||||
FIXED_TOTAL = 6
|
||||
pass
|
||||
|
||||
|
||||
class StoredScrimmageTicket(StoredCounter, StoredExpiredAt0400):
|
||||
FIXED_TOTAL = 6
|
||||
|
||||
|
||||
class StoredTacticalChallengeTicket(StoredCounter, StoredExpiredAt0400):
|
||||
FIXED_TOTAL = 5
|
||||
|
||||
|
||||
# class StoredDailyActivity(StoredCounter, StoredExpiredAt0400):
|
||||
# FIXED_TOTAL = 500
|
||||
#
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
from module.config.stored.classes import (
|
||||
StoredAP,
|
||||
StoredBase,
|
||||
StoredCounter,
|
||||
StoredExpiredAt0400,
|
||||
StoredExpiredAtMonday0400,
|
||||
StoredBountyTicket,
|
||||
StoredInt,
|
||||
StoredScrimmageTicket,
|
||||
StoredTacticalChallengeTicket,
|
||||
)
|
||||
|
||||
|
||||
@@ -15,3 +14,6 @@ class StoredGenerated:
|
||||
AP = StoredAP("DataUpdate.ItemStorage.AP")
|
||||
Credit = StoredInt("DataUpdate.ItemStorage.Credit")
|
||||
Pyroxene = StoredInt("DataUpdate.ItemStorage.Pyroxene")
|
||||
BountyTicket = StoredBountyTicket("DataUpdate.ItemStorage.BountyTicket")
|
||||
ScrimmageTicket = StoredScrimmageTicket("DataUpdate.ItemStorage.ScrimmageTicket")
|
||||
TacticalChallengeTicket = StoredTacticalChallengeTicket("DataUpdate.ItemStorage.TacticalChallengeTicket")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from pponnxcr import TextSystem as TextSystem_
|
||||
|
||||
from module.base.decorator import cached_property
|
||||
from module.base.decorator import cached_property, del_cached_property
|
||||
from module.exception import ScriptError
|
||||
|
||||
DIC_LANG_TO_MODEL = {
|
||||
@@ -56,6 +56,12 @@ class OcrModel:
|
||||
except AttributeError:
|
||||
raise ScriptError(f'OCR model under lang "{lang}" does not exists')
|
||||
|
||||
def resource_release(self):
|
||||
del_cached_property(self, 'zhs')
|
||||
del_cached_property(self, 'en')
|
||||
del_cached_property(self, 'ja')
|
||||
del_cached_property(self, 'zht')
|
||||
|
||||
@cached_property
|
||||
def zhs(self):
|
||||
return TextSystem('zhs')
|
||||
|
||||
@@ -89,12 +89,15 @@ def readable_time(before: str) -> str:
|
||||
elif diff < 60:
|
||||
# < 1 min
|
||||
return t("Gui.Dashboard.JustNow")
|
||||
elif diff < 3600:
|
||||
elif diff < 5400:
|
||||
# < 90 min
|
||||
return t("Gui.Dashboard.MinutesAgo", time=int(diff // 60))
|
||||
elif diff < 86400:
|
||||
elif diff < 129600:
|
||||
# < 36 hours
|
||||
return t("Gui.Dashboard.HoursAgo", time=int(diff // 3600))
|
||||
elif diff < 1296000:
|
||||
# < 15 days
|
||||
return t("Gui.Dashboard.DaysAgo", time=int(diff // 86400))
|
||||
else:
|
||||
# > 15 days
|
||||
# >= 15 days
|
||||
return t("Gui.Dashboard.LongTimeAgo")
|
||||
|
||||
@@ -9,17 +9,17 @@ from queue import Queue
|
||||
from typing import Callable, Generator, List
|
||||
|
||||
import pywebio
|
||||
from module.config.utils import deep_iter
|
||||
from module.logger import logger
|
||||
from module.webui.setting import State
|
||||
from pywebio.input import PASSWORD, input
|
||||
from pywebio.output import PopupSize, popup, put_html, toast
|
||||
from pywebio.session import eval_js
|
||||
from pywebio.session import info as session_info
|
||||
from pywebio.session import register_thread, run_js
|
||||
from rich.console import Console, ConsoleOptions
|
||||
from rich.console import Console
|
||||
from rich.terminal_theme import TerminalTheme
|
||||
|
||||
from module.config.utils import deep_iter
|
||||
from module.logger import logger
|
||||
from module.webui.setting import State
|
||||
|
||||
RE_DATETIME = (
|
||||
r"\d{4}\-(0\d|1[0-2])\-([0-2]\d|[3][0-1]) "
|
||||
@@ -455,7 +455,11 @@ def get_localstorage(key):
|
||||
|
||||
def re_fullmatch(pattern, string):
|
||||
if pattern == "datetime":
|
||||
pattern = RE_DATETIME
|
||||
try:
|
||||
datetime.datetime.fromisoformat(string)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
# elif:
|
||||
return re.fullmatch(pattern=pattern, string=string)
|
||||
|
||||
|
||||
@@ -3,6 +3,17 @@ 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 ```
|
||||
|
||||
CHECK_BOUNTY = ButtonWrapper(
|
||||
name='CHECK_BOUNTY',
|
||||
jp=Button(
|
||||
file='./assets/jp/bounty/CHECK_BOUNTY.png',
|
||||
area=(654, 87, 889, 129),
|
||||
search=(634, 67, 909, 149),
|
||||
color=(88, 113, 139),
|
||||
button=(654, 87, 889, 129),
|
||||
),
|
||||
en=None,
|
||||
)
|
||||
CHECK_DESERT_RAILROAD = ButtonWrapper(
|
||||
name='CHECK_DESERT_RAILROAD',
|
||||
jp=Button(
|
||||
@@ -36,6 +47,17 @@ CHECK_SCHOOLHOUSE = ButtonWrapper(
|
||||
),
|
||||
en=None,
|
||||
)
|
||||
OCR_TICKET = ButtonWrapper(
|
||||
name='OCR_TICKET',
|
||||
jp=Button(
|
||||
file='./assets/jp/bounty/OCR_TICKET.png',
|
||||
area=(195, 85, 237, 113),
|
||||
search=(175, 65, 257, 133),
|
||||
color=(197, 206, 213),
|
||||
button=(195, 85, 237, 113),
|
||||
),
|
||||
en=None,
|
||||
)
|
||||
SELECT_DESERT_RAILROAD = ButtonWrapper(
|
||||
name='SELECT_DESERT_RAILROAD',
|
||||
jp=Button(
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
from enum import Flag
|
||||
|
||||
from module.base.timer import Timer
|
||||
from module.exception import RequestHumanTakeover
|
||||
from module.logger import logger
|
||||
from tasks.base.assets.assets_base_page import BACK
|
||||
from tasks.base.page import page_bounty
|
||||
from tasks.bounty.assets.assets_bounty import *
|
||||
from tasks.bounty.ui import BountyUI
|
||||
|
||||
|
||||
class BountyStatus(Flag):
|
||||
OCR = 0
|
||||
SELECT = 1
|
||||
ENTER = 2
|
||||
SWEEP = 3
|
||||
END = 4
|
||||
FINISH = 5
|
||||
|
||||
|
||||
class Bounty(BountyUI):
|
||||
@property
|
||||
def bounty_info(self):
|
||||
bounty = (SELECT_HIGHWAY, SELECT_DESERT_RAILROAD, SELECT_SCHOOLHOUSE)
|
||||
check = (CHECK_HIGHWAY, CHECK_DESERT_RAILROAD, CHECK_SCHOOLHOUSE)
|
||||
stage = (self.config.Highway_Stage, self.config.DesertRailroad_Stage, self.config.Schoolhouse_Stage)
|
||||
count = (self.config.Highway_Count, self.config.DesertRailroad_Count, self.config.Schoolhouse_Count)
|
||||
info = zip(bounty, check, stage, count)
|
||||
return filter(lambda x: x[3] > 0, info)
|
||||
|
||||
@property
|
||||
def valid_task(self) -> list:
|
||||
task = list(self.bounty_info)
|
||||
if not task:
|
||||
logger.warning('Bounty enabled but no task set')
|
||||
self.error_handler()
|
||||
return task
|
||||
|
||||
def error_handler(self):
|
||||
action = self.config.Bounty_OnError
|
||||
if action == 'stop':
|
||||
raise RequestHumanTakeover
|
||||
elif action == 'skip':
|
||||
self.config.task_delay(server_update=True)
|
||||
self.config.task_stop()
|
||||
|
||||
@property
|
||||
def is_ticket_enough(self) -> bool:
|
||||
return self.current_ticket >= self.current_count
|
||||
|
||||
@property
|
||||
def current_bounty(self):
|
||||
return self.task[0][:2]
|
||||
|
||||
@property
|
||||
def current_stage(self):
|
||||
return self.task[0][2]
|
||||
|
||||
@property
|
||||
def current_count(self):
|
||||
return self.task[0][3]
|
||||
|
||||
@property
|
||||
def current_ticket(self):
|
||||
return self.config.stored.BountyTicket.value
|
||||
|
||||
def handle_bounty(self, status):
|
||||
match status:
|
||||
case BountyStatus.OCR:
|
||||
if self.get_ticket():
|
||||
if self.current_ticket == 0 or not self.task:
|
||||
return BountyStatus.FINISH
|
||||
return BountyStatus.SELECT
|
||||
case BountyStatus.SELECT:
|
||||
if not self.is_ticket_enough:
|
||||
logger.warning('Bounty ticket not enough')
|
||||
self.error_handler()
|
||||
if self.select_bounty(*self.current_bounty):
|
||||
return BountyStatus.ENTER
|
||||
case BountyStatus.ENTER:
|
||||
if self.enter_stage(self.current_stage):
|
||||
return BountyStatus.SWEEP
|
||||
else:
|
||||
self.error_handler()
|
||||
case BountyStatus.SWEEP:
|
||||
if self.do_sweep(self.current_count):
|
||||
self.task.pop(0)
|
||||
return BountyStatus.END
|
||||
return BountyStatus.ENTER
|
||||
case BountyStatus.END:
|
||||
if self.appear(CHECK_BOUNTY):
|
||||
return BountyStatus.OCR
|
||||
self.click_with_interval(BACK, interval=2)
|
||||
case BountyStatus.FINISH:
|
||||
return status
|
||||
case _:
|
||||
logger.warning(f'Invalid status: {status}')
|
||||
return status
|
||||
|
||||
def run(self):
|
||||
self.ui_ensure(page_bounty)
|
||||
self.task = self.valid_task
|
||||
action_timer = Timer(0.5, 1)
|
||||
status = BountyStatus.OCR
|
||||
|
||||
while 1:
|
||||
self.device.screenshot()
|
||||
|
||||
if self.ui_additional():
|
||||
continue
|
||||
|
||||
if action_timer.reached_and_reset():
|
||||
logger.attr('Status', status)
|
||||
status = self.handle_bounty(status)
|
||||
|
||||
if status == BountyStatus.FINISH:
|
||||
break
|
||||
|
||||
self.config.task_delay(server_update=True)
|
||||
@@ -0,0 +1,48 @@
|
||||
from module.base.timer import Timer
|
||||
from module.logger import logger
|
||||
from module.ocr.ocr import DigitCounter
|
||||
from tasks.base.ui import UI
|
||||
from tasks.bounty.assets.assets_bounty import *
|
||||
from tasks.stage.list import StageList
|
||||
from tasks.stage.sweep import StageSweep
|
||||
|
||||
BOUNTY_LIST = StageList('BountyList')
|
||||
BOUNTY_SWEEP = StageSweep('BountySweep', 6)
|
||||
|
||||
|
||||
class BountyUI(UI):
|
||||
def select_bounty(self, dest_enter: ButtonWrapper, dest_check: ButtonWrapper):
|
||||
timer = Timer(5, 10).start()
|
||||
while 1:
|
||||
self.device.screenshot()
|
||||
self.appear_then_click(dest_enter, interval=1)
|
||||
if self.appear(dest_check):
|
||||
return True
|
||||
if timer.reached():
|
||||
return False
|
||||
|
||||
def enter_stage(self, index: int) -> bool:
|
||||
if BOUNTY_LIST.select_index_enter(self, index):
|
||||
return True
|
||||
return False
|
||||
|
||||
def do_sweep(self, num: int) -> bool:
|
||||
if BOUNTY_SWEEP.do_sweep(self, num=num):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_ticket(self):
|
||||
"""
|
||||
Page:
|
||||
in: page_bounty
|
||||
"""
|
||||
if not self.appear(CHECK_BOUNTY):
|
||||
logger.warning('OCR failed due to invalid page')
|
||||
return False
|
||||
ticket, _, total = DigitCounter(OCR_TICKET).ocr_single_line(self.device.image)
|
||||
if total == 0:
|
||||
logger.warning('Invalid ticket')
|
||||
return False
|
||||
logger.attr('BountyTicket', ticket)
|
||||
self.config.stored.BountyTicket.set(ticket)
|
||||
return True
|
||||
@@ -30,16 +30,16 @@ class Login(UI):
|
||||
return True
|
||||
return False
|
||||
|
||||
@Config.when(Emulator_GameLanguage='jp')
|
||||
def _handle_survey():
|
||||
pass
|
||||
|
||||
@Config.when(Emulator_GameLanguage='en')
|
||||
def _handle_survey():
|
||||
def _handle_survey(self):
|
||||
if self.appear_then_click(SURVEY):
|
||||
return True
|
||||
return False
|
||||
|
||||
@Config.when(Emulator_GameLanguage=None)
|
||||
def _handle_survey(self):
|
||||
pass
|
||||
|
||||
logger.hr('App login')
|
||||
orientation_timer = Timer(5)
|
||||
startup_timer = Timer(5).start()
|
||||
@@ -89,7 +89,7 @@ class Login(UI):
|
||||
# continue
|
||||
if self.appear_then_click(UPDATE):
|
||||
continue
|
||||
if _handle_survey():
|
||||
if _handle_survey(self):
|
||||
continue
|
||||
if self.ui_additional():
|
||||
continue
|
||||
|
||||
@@ -6,7 +6,7 @@ from module.base.button import Button, ButtonWrapper
|
||||
CHECK_GEHENNA = ButtonWrapper(
|
||||
name='CHECK_GEHENNA',
|
||||
jp=Button(
|
||||
file='./assets/jp/school_exchange/CHECK_GEHENNA.png',
|
||||
file='./assets/jp/scrimmage/CHECK_GEHENNA.png',
|
||||
area=(109, 149, 195, 177),
|
||||
search=(89, 129, 215, 197),
|
||||
color=(201, 205, 206),
|
||||
@@ -17,7 +17,7 @@ CHECK_GEHENNA = ButtonWrapper(
|
||||
CHECK_MILLENNIUM = ButtonWrapper(
|
||||
name='CHECK_MILLENNIUM',
|
||||
jp=Button(
|
||||
file='./assets/jp/school_exchange/CHECK_MILLENNIUM.png',
|
||||
file='./assets/jp/scrimmage/CHECK_MILLENNIUM.png',
|
||||
area=(108, 148, 254, 178),
|
||||
search=(88, 128, 274, 198),
|
||||
color=(202, 206, 208),
|
||||
@@ -25,10 +25,21 @@ CHECK_MILLENNIUM = ButtonWrapper(
|
||||
),
|
||||
en=None,
|
||||
)
|
||||
CHECK_SCRIMMAGE = ButtonWrapper(
|
||||
name='CHECK_SCRIMMAGE',
|
||||
jp=Button(
|
||||
file='./assets/jp/scrimmage/CHECK_SCRIMMAGE.png',
|
||||
area=(651, 90, 889, 130),
|
||||
search=(631, 70, 909, 150),
|
||||
color=(96, 119, 144),
|
||||
button=(651, 90, 889, 130),
|
||||
),
|
||||
en=None,
|
||||
)
|
||||
CHECK_TRINITY = ButtonWrapper(
|
||||
name='CHECK_TRINITY',
|
||||
jp=Button(
|
||||
file='./assets/jp/school_exchange/CHECK_TRINITY.png',
|
||||
file='./assets/jp/scrimmage/CHECK_TRINITY.png',
|
||||
area=(115, 148, 250, 178),
|
||||
search=(95, 128, 270, 198),
|
||||
color=(204, 208, 210),
|
||||
@@ -36,10 +47,21 @@ CHECK_TRINITY = ButtonWrapper(
|
||||
),
|
||||
en=None,
|
||||
)
|
||||
OCR_TICKET = ButtonWrapper(
|
||||
name='OCR_TICKET',
|
||||
jp=Button(
|
||||
file='./assets/jp/scrimmage/OCR_TICKET.png',
|
||||
area=(195, 85, 235, 113),
|
||||
search=(175, 65, 255, 133),
|
||||
color=(206, 211, 215),
|
||||
button=(195, 85, 235, 113),
|
||||
),
|
||||
en=None,
|
||||
)
|
||||
SELECT_GEHENNA = ButtonWrapper(
|
||||
name='SELECT_GEHENNA',
|
||||
jp=Button(
|
||||
file='./assets/jp/school_exchange/SELECT_GEHENNA.png',
|
||||
file='./assets/jp/scrimmage/SELECT_GEHENNA.png',
|
||||
area=(1125, 275, 1224, 310),
|
||||
search=(1105, 255, 1244, 330),
|
||||
color=(207, 217, 225),
|
||||
@@ -50,7 +72,7 @@ SELECT_GEHENNA = ButtonWrapper(
|
||||
SELECT_MILLENNIUM = ButtonWrapper(
|
||||
name='SELECT_MILLENNIUM',
|
||||
jp=Button(
|
||||
file='./assets/jp/school_exchange/SELECT_MILLENNIUM.png',
|
||||
file='./assets/jp/scrimmage/SELECT_MILLENNIUM.png',
|
||||
area=(1069, 381, 1217, 417),
|
||||
search=(1049, 361, 1237, 437),
|
||||
color=(206, 214, 222),
|
||||
@@ -61,7 +83,7 @@ SELECT_MILLENNIUM = ButtonWrapper(
|
||||
SELECT_TRINITY = ButtonWrapper(
|
||||
name='SELECT_TRINITY',
|
||||
jp=Button(
|
||||
file='./assets/jp/school_exchange/SELECT_TRINITY.png',
|
||||
file='./assets/jp/scrimmage/SELECT_TRINITY.png',
|
||||
area=(1074, 165, 1221, 204),
|
||||
search=(1054, 145, 1241, 224),
|
||||
color=(213, 221, 228),
|
||||
@@ -0,0 +1,144 @@
|
||||
from enum import Enum
|
||||
|
||||
from module.base.timer import Timer
|
||||
from module.exception import RequestHumanTakeover
|
||||
from module.logger import logger
|
||||
from tasks.base.assets.assets_base_page import BACK
|
||||
from tasks.base.page import page_school_exchange
|
||||
from tasks.scrimmage.assets.assets_scrimmage import *
|
||||
from tasks.scrimmage.ui import ScrimmageUI
|
||||
|
||||
|
||||
class ScrimmageStatus(Enum):
|
||||
OCR = 0
|
||||
SELECT = 1
|
||||
ENTER = 2
|
||||
SWEEP = 3
|
||||
END = 4
|
||||
FINISH = 5
|
||||
|
||||
|
||||
class Scrimmage(ScrimmageUI):
|
||||
@property
|
||||
def scrimmage_info(self):
|
||||
bounty = (SELECT_TRINITY, SELECT_GEHENNA, SELECT_MILLENNIUM)
|
||||
check = (CHECK_TRINITY, CHECK_GEHENNA, CHECK_MILLENNIUM)
|
||||
stage = (self.config.Trinity_Stage, self.config.Gehenna_Stage, self.config.Millennium_Stage)
|
||||
count = (self.config.Trinity_Count, self.config.Gehenna_Count, self.config.Millennium_Count)
|
||||
ap = (10 if stage == 1 else 15 for stage in stage)
|
||||
info = zip(bounty, check, stage, count, ap)
|
||||
return filter(lambda x: x[3] > 0, info)
|
||||
|
||||
@property
|
||||
def valid_task(self) -> list:
|
||||
task = list(self.scrimmage_info)
|
||||
if not task:
|
||||
logger.warning('Scrimmage enabled but no task set')
|
||||
self.error_handler()
|
||||
return task
|
||||
|
||||
def error_handler(self):
|
||||
action = self.config.Bounty_OnError
|
||||
if action == 'stop':
|
||||
raise RequestHumanTakeover
|
||||
elif action == 'skip':
|
||||
self.config.task_delay(server_update=True)
|
||||
self.config.task_stop()
|
||||
|
||||
@property
|
||||
def is_ticket_enough(self) -> bool:
|
||||
return self.current_ticket >= self.current_count
|
||||
|
||||
@property
|
||||
def is_ap_enough(self) -> bool:
|
||||
return self.current_ap >= self.current_task_ap
|
||||
|
||||
@property
|
||||
def current_scrimmage(self):
|
||||
return self.task[0][:2]
|
||||
|
||||
@property
|
||||
def current_stage(self):
|
||||
return self.task[0][2]
|
||||
|
||||
@property
|
||||
def current_count(self):
|
||||
return self.task[0][3]
|
||||
|
||||
@property
|
||||
def current_task_ap(self):
|
||||
return self.task[0][4]
|
||||
|
||||
@property
|
||||
def current_ticket(self):
|
||||
return self.config.stored.ScrimmageTicket.value
|
||||
|
||||
@property
|
||||
def current_ap(self):
|
||||
return self.config.stored.AP.value
|
||||
|
||||
def update_ap(self):
|
||||
ap = self.config.stored.AP
|
||||
ap_old = ap.value
|
||||
ap_new = ap_old - self.current_task_ap
|
||||
ap.set(ap_new, ap.total)
|
||||
logger.info(f'Set AP: {ap_old} -> {ap_new}')
|
||||
|
||||
def handle_scrimmage(self, status):
|
||||
match status:
|
||||
case ScrimmageStatus.OCR:
|
||||
if self.get_ticket():
|
||||
if self.current_ticket == 0 or not self.task:
|
||||
return ScrimmageStatus.FINISH
|
||||
return ScrimmageStatus.SELECT
|
||||
case ScrimmageStatus.SELECT:
|
||||
if not self.is_ticket_enough:
|
||||
logger.warning('Scrimmage ticket not enough')
|
||||
self.error_handler()
|
||||
if not self.is_ap_enough:
|
||||
logger.warning('AP not enough')
|
||||
self.error_handler()
|
||||
if self.select_scrimmage(*self.current_scrimmage):
|
||||
return ScrimmageStatus.ENTER
|
||||
case ScrimmageStatus.ENTER:
|
||||
if self.enter_stage(self.current_stage):
|
||||
return ScrimmageStatus.SWEEP
|
||||
else:
|
||||
self.error_handler()
|
||||
case ScrimmageStatus.SWEEP:
|
||||
if self.do_sweep(self.current_count):
|
||||
self.update_ap()
|
||||
self.task.pop(0)
|
||||
return ScrimmageStatus.END
|
||||
return ScrimmageStatus.ENTER
|
||||
case ScrimmageStatus.END:
|
||||
if self.appear(CHECK_SCRIMMAGE):
|
||||
return ScrimmageStatus.OCR
|
||||
self.click_with_interval(BACK, interval=2)
|
||||
case ScrimmageStatus.FINISH:
|
||||
return status
|
||||
case _:
|
||||
logger.warning(f'Invalid status: {status}')
|
||||
return status
|
||||
|
||||
def run(self):
|
||||
self.ui_ensure(page_school_exchange)
|
||||
self.task = self.valid_task
|
||||
action_timer = Timer(0.5, 1)
|
||||
status = ScrimmageStatus.OCR
|
||||
|
||||
while 1:
|
||||
self.device.screenshot()
|
||||
|
||||
if self.ui_additional():
|
||||
continue
|
||||
|
||||
if action_timer.reached_and_reset():
|
||||
logger.attr('Status', status)
|
||||
status = self.handle_scrimmage(status)
|
||||
|
||||
if status == ScrimmageStatus.FINISH:
|
||||
break
|
||||
|
||||
self.config.task_delay(server_update=True)
|
||||
self.config.task_call('DataUpdate')
|
||||
@@ -0,0 +1,48 @@
|
||||
from module.base.timer import Timer
|
||||
from module.logger import logger
|
||||
from module.ocr.ocr import DigitCounter
|
||||
from tasks.base.ui import UI
|
||||
from tasks.scrimmage.assets.assets_scrimmage import *
|
||||
from tasks.stage.list import StageList
|
||||
from tasks.stage.sweep import StageSweep
|
||||
|
||||
SCRIMMAGE_LIST = StageList('ScrimmageList')
|
||||
SCRIMMAGE_SWEEP = StageSweep('ScrimmageSweep', 6)
|
||||
|
||||
|
||||
class ScrimmageUI(UI):
|
||||
def select_scrimmage(self, dest_enter: ButtonWrapper, dest_check: ButtonWrapper):
|
||||
timer = Timer(5, 10).start()
|
||||
while 1:
|
||||
self.device.screenshot()
|
||||
self.appear_then_click(dest_enter, interval=1)
|
||||
if self.appear(dest_check):
|
||||
return True
|
||||
if timer.reached():
|
||||
return False
|
||||
|
||||
def enter_stage(self, index: int) -> bool:
|
||||
if SCRIMMAGE_LIST.select_index_enter(self, index, insight=False):
|
||||
return True
|
||||
return False
|
||||
|
||||
def do_sweep(self, num: int) -> bool:
|
||||
if SCRIMMAGE_SWEEP.do_sweep(self, num=num):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_ticket(self):
|
||||
"""
|
||||
Page:
|
||||
in: page_bounty
|
||||
"""
|
||||
if not self.appear(CHECK_SCRIMMAGE):
|
||||
logger.warning('OCR failed due to invalid page')
|
||||
return False
|
||||
ticket, _, total = DigitCounter(OCR_TICKET).ocr_single_line(self.device.image)
|
||||
if total == 0:
|
||||
logger.warning('Invalid ticket')
|
||||
return False
|
||||
logger.attr('ScrimmageTicket', ticket)
|
||||
self.config.stored.ScrimmageTicket.set(ticket)
|
||||
return True
|
||||
@@ -148,33 +148,37 @@ class StageList:
|
||||
|
||||
def select_index_enter(
|
||||
self,
|
||||
index: int,
|
||||
main: ModuleBase,
|
||||
index: int,
|
||||
insight: bool = True,
|
||||
sweepable: bool = True,
|
||||
offset: tuple[int, int] = (-20, -15),
|
||||
skip_first_screenshot: bool = True,
|
||||
interval: int = 2
|
||||
interval: int = 1.5
|
||||
) -> bool:
|
||||
# insight index, if failed, return False
|
||||
if insight and not self.insight_index(index, main, skip_first_screenshot):
|
||||
return False
|
||||
logger.info(f'Select index: {index}')
|
||||
click_interval = Timer(interval)
|
||||
load_index_interval = Timer(1)
|
||||
timeout = Timer(15, 10).start()
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
main.device.screenshot()
|
||||
|
||||
if load_index_interval.reached_and_reset():
|
||||
# load index if not insight
|
||||
if load_index_interval.reached_and_reset() and not insight:
|
||||
self.load_stage_indexes(main=main)
|
||||
|
||||
# find box of index
|
||||
index_box = next(filter(lambda x: int(x.ocr_text) == index, self.current_indexes), None)
|
||||
|
||||
if index_box is None:
|
||||
logger.warning(f'No index {index} in {self.index_ocr.name}')
|
||||
return False
|
||||
continue
|
||||
|
||||
stage_item_box = area_pad((*offset, *area_size(self.stage_item)))
|
||||
search_box = area_offset(stage_item_box, index_box.box[:2])
|
||||
@@ -188,7 +192,7 @@ class StageList:
|
||||
|
||||
if not points:
|
||||
logger.warning(f'No clickable {self.enter.name}')
|
||||
return False
|
||||
continue
|
||||
|
||||
point = area_offset((0, 0, *area_size(self.enter.button)), points[0])
|
||||
click_button = ClickButton(area_offset(point, search_box[:2]), name=self.enter.name)
|
||||
@@ -196,3 +200,7 @@ class StageList:
|
||||
if click_interval.reached_and_reset():
|
||||
main.device.click(click_button)
|
||||
return True
|
||||
|
||||
if timeout.reached():
|
||||
logger.warning(f'{self.enter.name} failed')
|
||||
return False
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from enum import Enum
|
||||
|
||||
from module.base.base import ModuleBase
|
||||
from module.base.timer import Timer
|
||||
from module.logger import logger
|
||||
from module.ocr.ocr import Digit
|
||||
from enum import Enum
|
||||
from tasks.stage.assets.assets_stage_sweep import *
|
||||
|
||||
|
||||
@@ -19,11 +20,10 @@ class StageSweep:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
sweep_num: int,
|
||||
max_sweep: int,
|
||||
):
|
||||
self.name = name
|
||||
self.sweep_num = sweep_num
|
||||
self.sweep_num = None
|
||||
|
||||
self.check: ButtonWrapper = None
|
||||
self.num: Digit = None
|
||||
@@ -45,7 +45,6 @@ class StageSweep:
|
||||
self.current_sweep = 0
|
||||
|
||||
self.sweep_method = None
|
||||
self.set_mode()
|
||||
|
||||
def __str__(self):
|
||||
return f'StageSweep({self.name})'
|
||||
@@ -88,9 +87,10 @@ class StageSweep:
|
||||
self.skip_ok_upper = button_skip_ok_upper if button_skip_ok_upper else SKIP_OK_UPPER
|
||||
self.skip_ok_lower = button_skip_ok_lower if button_skip_ok_lower else SKIP_OK_LOWER
|
||||
|
||||
def set_mode(self, mode: str = None):
|
||||
if mode is None:
|
||||
match self.sweep_num:
|
||||
def set_mode(self, mode: str = None, num: int = None) -> bool:
|
||||
if num is not None:
|
||||
self.sweep_num = num
|
||||
match num:
|
||||
case 0:
|
||||
self.sweep_method = self.set_sweep_min
|
||||
case -1:
|
||||
@@ -98,15 +98,19 @@ class StageSweep:
|
||||
case x if x > 0:
|
||||
self.sweep_method = self.set_sweep_num
|
||||
case _:
|
||||
logger.warning(f'Invalid sweep num: {self.sweep_num}')
|
||||
return
|
||||
match mode:
|
||||
case 'max':
|
||||
self.sweep_method = self.set_sweep_max
|
||||
case 'min':
|
||||
self.sweep_method = self.set_sweep_min
|
||||
case _:
|
||||
logger.warning(f'Invalid sweep mode: {mode}')
|
||||
logger.warning(f'Invalid sweep num: {num}')
|
||||
return True
|
||||
if mode is not None:
|
||||
match mode:
|
||||
case 'max':
|
||||
self.sweep_method = self.set_sweep_max
|
||||
case 'min':
|
||||
self.sweep_method = self.set_sweep_min
|
||||
case _:
|
||||
logger.warning(f'Invalid sweep mode: {mode}')
|
||||
return True
|
||||
logger.warning(f'Invalid sweep setting')
|
||||
return False
|
||||
|
||||
def check_sweep(self, main: ModuleBase):
|
||||
return main.appear(self.check)
|
||||
@@ -115,9 +119,12 @@ class StageSweep:
|
||||
return main.appear(self.skip_skip) or main.appear(self.skip_ok_upper) or main.appear(self.skip_ok_lower)
|
||||
|
||||
def load_sweep_num(self, main: ModuleBase):
|
||||
timer = Timer(0.5, 2).start()
|
||||
while 1:
|
||||
main.device.screenshot()
|
||||
ocr_result = self.num.detect_and_ocr(main.device.image)
|
||||
if not timer.reached_and_reset():
|
||||
continue
|
||||
ocr_result = list(filter(lambda x: x.ocr_text.isdigit(), self.num.detect_and_ocr(main.device.image)))
|
||||
if not ocr_result:
|
||||
logger.warning(f'No valid num in {self.num.name}')
|
||||
continue
|
||||
@@ -204,7 +211,9 @@ class StageSweep:
|
||||
if retry.reached_and_reset():
|
||||
main.click_with_interval(self.min, interval=0)
|
||||
|
||||
def do_sweep(self, main: ModuleBase, skip_first_screenshot=True) -> bool:
|
||||
def do_sweep(self, main: ModuleBase, mode: str = None, num: int = None, skip_first_screenshot=True) -> bool:
|
||||
if not self.set_mode(mode, num):
|
||||
return False
|
||||
timer = Timer(0.5, 1)
|
||||
timer_stable = Timer(0.5, 1).start()
|
||||
status = SweepStatus.SELECT
|
||||
|
||||
@@ -3,15 +3,9 @@ from enum import Enum
|
||||
|
||||
from module.base.timer import Timer
|
||||
from module.logger import logger
|
||||
from module.ocr.ocr import DigitCounter
|
||||
from module.ui.switch import Switch
|
||||
from tasks.base.page import page_tactical_challenge
|
||||
from tasks.base.ui import UI
|
||||
from tasks.tactical_challenge.assets.assets_tactical_challenge import *
|
||||
|
||||
SWITCH_SKIP = Switch('Skip_switch')
|
||||
SWITCH_SKIP.add_state('on', SKIP_ON)
|
||||
SWITCH_SKIP.add_state('off', SKIP_OFF)
|
||||
from tasks.tactical_challenge.ui import TacticalChallengeUI
|
||||
|
||||
|
||||
class TCStatus(Enum):
|
||||
@@ -31,50 +25,12 @@ class TCStatus(Enum):
|
||||
FINISHED = -1
|
||||
|
||||
|
||||
class TacticalChallenge(UI):
|
||||
class TacticalChallenge(TacticalChallengeUI):
|
||||
select_players = (PLAYER_SELECT_FIRST, PLAYER_SELECT_SECOND, PLAYER_SELECT_THIRD)
|
||||
|
||||
def _get_ticket(self):
|
||||
"""
|
||||
Page:
|
||||
in: page_tactical_challenge
|
||||
"""
|
||||
ocr = DigitCounter(OCR_TICKET).ocr_single_line(self.device.image)
|
||||
# number of tickets remaining
|
||||
ticket, _, total = ocr
|
||||
if total == 0:
|
||||
logger.warning('Invalid ticket')
|
||||
return False, 5
|
||||
logger.attr('Ticket', ticket)
|
||||
|
||||
return True, ticket
|
||||
|
||||
def _get_reward(self):
|
||||
if self.match_color(GET_REWARD_DAILY):
|
||||
self.device.click(GET_REWARD_DAILY)
|
||||
logger.info('Get tc daily reward')
|
||||
return True
|
||||
if self.match_color(GET_REWARD_CREDIT):
|
||||
self.device.click(GET_REWARD_CREDIT)
|
||||
logger.info('Get tc credit reward')
|
||||
return True
|
||||
if self.match_color(GOT_REWARD_DAILY) and self.match_color(GOT_REWARD_CREDIT):
|
||||
logger.info('Both tc reward got')
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _set_skip(self):
|
||||
"""
|
||||
Set skip switch to on
|
||||
:returns: True if switch is set, False if switch not found
|
||||
"""
|
||||
if not SWITCH_SKIP.appear(main=self):
|
||||
logger.info('Skip switch not found')
|
||||
return False
|
||||
SWITCH_SKIP.set('on', main=self)
|
||||
|
||||
return True
|
||||
@property
|
||||
def current_ticket(self):
|
||||
return self.config.stored.TacticalChallengeTicket.value
|
||||
|
||||
def _player_select(self, select):
|
||||
if select:
|
||||
@@ -85,15 +41,13 @@ class TacticalChallenge(UI):
|
||||
def _handle_challenge(self, status):
|
||||
match status:
|
||||
case TCStatus.REWARD:
|
||||
if self._get_reward():
|
||||
if self.get_reward():
|
||||
return TCStatus.OCR
|
||||
case TCStatus.OCR:
|
||||
is_valid, ticket = self._get_ticket()
|
||||
if not is_valid:
|
||||
return status
|
||||
if ticket == 0:
|
||||
return TCStatus.FINISHED
|
||||
return TCStatus.SELECT
|
||||
if self.get_ticket():
|
||||
if self.current_ticket == 0:
|
||||
return TCStatus.FINISHED
|
||||
return TCStatus.SELECT
|
||||
case TCStatus.SELECT:
|
||||
self.appear_then_click(self.select)
|
||||
if self.appear(PREPARE_CHALLENGE):
|
||||
@@ -103,7 +57,7 @@ class TacticalChallenge(UI):
|
||||
if not self.appear(PREPARE_CHALLENGE):
|
||||
return TCStatus.SKIP
|
||||
case TCStatus.SKIP:
|
||||
if not self._set_skip():
|
||||
if not self.set_skip():
|
||||
return TCStatus.SKIP
|
||||
return TCStatus.START
|
||||
case TCStatus.START:
|
||||
@@ -118,12 +72,10 @@ class TacticalChallenge(UI):
|
||||
case TCStatus.WIN | TCStatus.LOSE:
|
||||
if self.appear_then_click(CHALLENGE_WIN) or self.appear_then_click(CHALLENGE_LOSE):
|
||||
return status
|
||||
is_valid, ticket = self._get_ticket()
|
||||
if not is_valid:
|
||||
return status
|
||||
if ticket == 0:
|
||||
return TCStatus.FINISHED
|
||||
return TCStatus.FINAL
|
||||
if self.get_ticket():
|
||||
if self.current_ticket == 0:
|
||||
return TCStatus.FINISHED
|
||||
return TCStatus.FINAL
|
||||
case TCStatus.FINAL | TCStatus.FINISHED:
|
||||
return status
|
||||
case _:
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
from module.base.timer import Timer
|
||||
from module.logger import logger
|
||||
from module.ocr.ocr import DigitCounter
|
||||
from module.ui.switch import Switch
|
||||
from tasks.base.ui import UI
|
||||
from tasks.tactical_challenge.assets.assets_tactical_challenge import *
|
||||
|
||||
SWITCH_SKIP = Switch('Skip_switch')
|
||||
SWITCH_SKIP.add_state('on', SKIP_ON)
|
||||
SWITCH_SKIP.add_state('off', SKIP_OFF)
|
||||
|
||||
|
||||
class TacticalChallengeUI(UI):
|
||||
def get_ticket(self):
|
||||
"""
|
||||
Page:
|
||||
in: page_tactical_challenge
|
||||
"""
|
||||
ticket, _, total = DigitCounter(OCR_TICKET).ocr_single_line(self.device.image)
|
||||
if total == 0:
|
||||
logger.warning('Invalid ticket')
|
||||
return False
|
||||
logger.attr('TacticalChallengeTicket', ticket)
|
||||
self.config.stored.TacticalChallengeTicket.set(ticket)
|
||||
return True
|
||||
|
||||
def get_reward(self):
|
||||
timer = Timer(10, 10).start()
|
||||
while 1:
|
||||
self.device.screenshot()
|
||||
self.ui_additional()
|
||||
if self.match_color(GOT_REWARD_DAILY) and self.match_color(GOT_REWARD_CREDIT):
|
||||
return True
|
||||
if self.match_color(GET_REWARD_DAILY):
|
||||
self.device.click(GET_REWARD_DAILY)
|
||||
logger.info('Get daily reward')
|
||||
if self.match_color(GET_REWARD_CREDIT):
|
||||
self.device.click(GET_REWARD_CREDIT)
|
||||
logger.info('Get credit reward')
|
||||
if timer.reached():
|
||||
return False
|
||||
|
||||
def set_skip(self):
|
||||
"""
|
||||
Set skip switch to on
|
||||
Returns:
|
||||
True if switch is set, False if switch not found
|
||||
"""
|
||||
if not SWITCH_SKIP.appear(main=self):
|
||||
logger.info('Skip switch not found')
|
||||
return False
|
||||
SWITCH_SKIP.set('on', main=self)
|
||||
|
||||
return True
|
||||