1
0
mirror of https://github.com/TheFunny/ArisuAutoSweeper synced 2026-02-06 08:55:13 +00:00

Compare commits

..

53 Commits

Author SHA1 Message Date
15bf77da3d
fix(cafe): expand search box 2023-11-23 16:21:58 +08:00
4ce8073096
perf(sweep): simplify sweep num ocr 2023-11-23 16:08:51 +08:00
aa872c890d
doc: update readme 2023-11-23 14:45:31 +08:00
25e0559171
feat: add oversea servers 2023-11-23 14:23:52 +08:00
df6da1f77a
fix(cafe): adjust property of second cafe setting 2023-11-23 14:06:38 +08:00
fc49adc859
doc: update readme 2023-11-22 22:23:09 +08:00
4582406ef2
perf(tc): improve status check stability 2023-11-22 22:04:50 +08:00
256dc96598
fix(tc): restrict count frequency of claim reward 2023-11-22 22:01:07 +08:00
04744d6f8c
fix(scrimmage): add missing multiply of ap count 2023-11-22 22:01:06 +08:00
8fe578615d
fix: change webui port 2023-11-22 21:02:00 +08:00
5e9615542c
fix: set repo when update 2023-11-22 20:47:18 +08:00
36c5f60eb3
fix(cafe): adjust property of second cafe setting 2023-11-22 19:05:36 +08:00
91650cc584
feat: add en assets for bounty scrimmage and sweep 2023-11-22 19:04:50 +08:00
67881568dd
perf(button): combine shared assets 2023-11-22 13:13:39 +08:00
f8404edd9e
fix: set repo of build-in update 2023-11-21 23:15:06 +08:00
ff3ec041d2
refactor(cafe): separate ui operation and simplify template extraction 2023-11-21 22:18:28 +08:00
99074a1575
feat(cafe): add template search area 2023-11-21 21:31:01 +08:00
7862fa6cb8
fix(scrimmage): fix typo 2023-11-21 20:56:39 +08:00
baac90ecf0
fix(resource): remove non-existing assets loading 2023-11-21 20:42:24 +08:00
53ec298fed
fix(alas): validate datetime instead of using regex 2023-11-21 20:29:26 +08:00
b4f18f78ff
perf: use more friendly record time on dashboard 2023-11-21 20:27:19 +08:00
eb9af42f38
fix: ignore value in state type args 2023-11-21 20:16:40 +08:00
c29d972c6c
feat: support direct_match and match_multi_template 2023-11-21 20:07:45 +08:00
92b34d4760
perf: release resource when free 2023-11-21 19:59:32 +08:00
04853b6c31
feat: support load search for buttons 2023-11-21 19:53:02 +08:00
9604e8962a
fix: accept area attr in ClickButton 2023-11-21 19:20:04 +08:00
03380b2d71
fix: use distinctive search attr for each button frame 2023-11-21 19:17:06 +08:00
c27bd74050
fix: adjust icon css 2023-11-21 19:00:14 +08:00
c3e9945b15
lang: use shorter description for items 2023-11-21 18:43:04 +08:00
1dd100ac04
lang: fix typo 2023-11-21 15:28:08 +08:00
77dca70af1
doc: update readme 2023-11-21 15:25:41 +08:00
b8ecd0c9d6
feat: support scrimmage 2023-11-21 15:18:07 +08:00
ceb24283f3
feat: add scrimmage gui option 2023-11-21 15:17:49 +08:00
d0c591af3a
fix(bounty): add error handler when enter sweep failed 2023-11-21 14:21:22 +08:00
589b0b08ec
perf(sweep): improve sweep list stability 2023-11-21 14:20:02 +08:00
299bd6c687
refactor(sweep): change parameter order 2023-11-21 13:54:59 +08:00
e61afaf43b
fix(sweep): filter non-digit ocr text 2023-11-21 13:14:30 +08:00
30e8c8b21b
fix(tc): add reward handler 2023-11-21 12:55:16 +08:00
f7b165f589
perf(tc): improve get reward method 2023-11-20 22:34:01 +08:00
08959e5f1c
feat(tc): update storage of tc ticket 2023-11-20 22:26:24 +08:00
e929a1efb1
refactor(tc): separate ui operation 2023-11-20 22:10:40 +08:00
8e29d7d2c0
feat: add scrimmage config 2023-11-20 21:43:11 +08:00
d3a1a77d6a
feat: add scrimmage assets 2023-11-20 21:28:34 +08:00
930c741de6
doc: update readme 2023-11-20 20:08:26 +08:00
2c19afdf26
feat(bounty): add error handler choice 2023-11-20 17:28:55 +08:00
bbf3bf7c36
perf(bounty): improve ending logic 2023-11-20 17:06:08 +08:00
5e4abc147e
feat(bounty): detect ticket and end when zero 2023-11-20 16:50:18 +08:00
eb8048ccd6
doc: update readme 2023-11-20 16:31:49 +08:00
8bec814b8d
feat: support bounty 2023-11-20 16:25:06 +08:00
5fb2810bdc
feat: add bounty assets 2023-11-20 16:23:27 +08:00
8ea95dc340
feat: add bounty gui option 2023-11-20 16:21:40 +08:00
9d3e581321
feat: add ticket storage in config and gui 2023-11-20 16:20:02 +08:00
d47e463365
perf(sweep): improve sweep flexibility 2023-11-19 23:59:01 +08:00
138 changed files with 1953 additions and 572 deletions

View File

@ -15,12 +15,19 @@ The script is still under active development. The following features have been i
- [x] **Cafe** Claim rewards / Interact / Second floor - [x] **Cafe** Claim rewards / Interact / Second floor
- [x] **Club** Claim AP - [x] **Club** Claim AP
- [x] **Mailbox** Claim rewards - [x] **Mailbox** Claim rewards
- [x] **Bounty** Auto sweep
- [x] **Scrimmage** Auto sweep
- [x] **Tactical Challenge** Claim rewards / Auto battle - [x] **Tactical Challenge** Claim rewards / Auto battle
Supported servers: Supported servers:
- [x] JP - [x] JP
- [x] OVERSEA - Global - [x] OVERSEA
Supported in-game languages:
- [x] Japanese
- [x] English
## Relative projects ## Relative projects

View File

@ -15,12 +15,28 @@
- [x] **咖啡厅** 领取奖励 / 互动 / 第二咖啡厅 - [x] **咖啡厅** 领取奖励 / 互动 / 第二咖啡厅
- [x] **公会** 领取体力 - [x] **公会** 领取体力
- [x] **邮箱** 领取奖励 - [x] **邮箱** 领取奖励
- [x] **悬赏通缉** 自动扫荡
- [x] **学院交流会** 自动扫荡
- [x] **战术对抗赛** 领取奖励 / 自动战斗 - [x] **战术对抗赛** 领取奖励 / 自动战斗
目前支持的服务器: 目前支持的服务器:
- [x] 日服 - [x] 日服
- [x] 国际服 - 全球 - [x] 国际服
目前支持的游戏内语言:
- [x] 日语
- [x] 英语
## 已知问题
若愿意提供其他语言或国服支持,请开 PR 或 Issue。
- **国际服登录的全屏通知**:未实现自动关闭,正在研究中
- **大小月卡**:未实现自动领取,~~因为没买过~~,可能不影响使用。愿意提供图片的请开 Issue
- **月卡的额外悬赏券和学院交流券**:不太清楚月卡领取额外券的机制,~~因为没买过~~,可能影响相关任务使用券和体力的计算。愿意提供相关信息的请开
Issue
## 相关项目 ## 相关项目

8
aas.py
View File

@ -34,6 +34,14 @@ class ArisuAutoSweeper(AzurLaneAutoScript):
from tasks.mail.mail import Mail from tasks.mail.mail import Mail
Mail(config=self.config, device=self.device).run() 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): def tactical_challenge(self):
from tasks.tactical_challenge.tactical_challenge import TacticalChallenge from tasks.tactical_challenge.tactical_challenge import TacticalChallenge
TacticalChallenge(config=self.config, device=self.device).run() TacticalChallenge(config=self.config, device=self.device).run()

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -285,7 +285,7 @@ pre.rich-traceback-code {
*[style*="--header-icon--"] { *[style*="--header-icon--"] {
margin: .25rem auto .25rem; margin: .25rem auto .25rem;
border-radius: 1.5rem; border-radius: 1.5rem;
height: 3.5em; height: 3em;
} }
*[style*="--header-text--"] { *[style*="--header-text--"] {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 905 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

Before

Width:  |  Height:  |  Size: 905 KiB

After

Width:  |  Height:  |  Size: 905 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1016 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -128,8 +128,8 @@ Deploy:
WebuiHost: 0.0.0.0 WebuiHost: 0.0.0.0
# --port. Port to listen # --port. Port to listen
# You will be able to access webui via `http://{host}:{port}` # You will be able to access webui via `http://{host}:{port}`
# [In most cases] Default to 22367 # [In most cases] Default to 23467
WebuiPort: 22367 WebuiPort: 23467
# Language to use on web ui # Language to use on web ui
# 'zh-CN' for Chinese simplified # 'zh-CN' for Chinese simplified
# 'en-US' for English # 'en-US' for English

View File

@ -128,8 +128,8 @@ Deploy:
WebuiHost: 0.0.0.0 WebuiHost: 0.0.0.0
# --port. Port to listen # --port. Port to listen
# You will be able to access webui via `http://{host}:{port}` # You will be able to access webui via `http://{host}:{port}`
# [In most cases] Default to 22367 # [In most cases] Default to 23467
WebuiPort: 22367 WebuiPort: 23467
# Language to use on web ui # Language to use on web ui
# 'zh-CN' for Chinese simplified # 'zh-CN' for Chinese simplified
# 'en-US' for English # 'en-US' for English

View File

@ -63,6 +63,52 @@
"ServerUpdate": "04:00" "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": { "TacticalChallenge": {
"Scheduler": { "Scheduler": {
"Enable": true, "Enable": true,
@ -84,7 +130,10 @@
"ItemStorage": { "ItemStorage": {
"AP": {}, "AP": {},
"Credit": {}, "Credit": {},
"Pyroxene": {} "Pyroxene": {},
"BountyTicket": {},
"ScrimmageTicket": {},
"TacticalChallengeTicket": {}
} }
} }
} }

View File

@ -55,7 +55,7 @@ class ConfigModel:
# Webui # Webui
WebuiHost: str = "0.0.0.0" WebuiHost: str = "0.0.0.0"
WebuiPort: int = 22367 WebuiPort: int = 23467
Language: str = "en-US" Language: str = "en-US"
Theme: str = "default" Theme: str = "default"
DpiScaling: bool = True DpiScaling: bool = True
@ -77,6 +77,12 @@ class DeployConfig(ConfigModel):
self.config_template = {} self.config_template = {}
self.read() self.read()
self.set_repo()
self.write()
self.show_config()
def set_repo(self):
# Bypass webui.config.DeployConfig.__setattr__() # Bypass webui.config.DeployConfig.__setattr__()
# Don't write these into deploy.yaml # Don't write these into deploy.yaml
if self.Repository == 'cn': if self.Repository == 'cn':
@ -84,9 +90,6 @@ class DeployConfig(ConfigModel):
if self.Repository == 'global': if self.Repository == 'global':
super().__setattr__('Repository', 'https://github.com/TheFunny/ArisuAutoSweeper') super().__setattr__('Repository', 'https://github.com/TheFunny/ArisuAutoSweeper')
self.write()
self.show_config()
def show_config(self): def show_config(self):
logger.hr("Show deploy config", 1) logger.hr("Show deploy config", 1)
for k, v in self.config.items(): for k, v in self.config.items():

View File

@ -128,8 +128,8 @@ Deploy:
WebuiHost: 0.0.0.0 WebuiHost: 0.0.0.0
# --port. Port to listen # --port. Port to listen
# You will be able to access webui via `http://{host}:{port}` # You will be able to access webui via `http://{host}:{port}`
# [In most cases] Default to 22367 # [In most cases] Default to 23467
WebuiPort: 22367 WebuiPort: 23467
# Language to use on web ui # Language to use on web ui
# 'zh-CN' for Chinese simplified # 'zh-CN' for Chinese simplified
# 'en-US' for English # 'en-US' for English

View File

@ -179,20 +179,29 @@ def iter_assets():
if image.attr != '': if image.attr != '':
row = deep_get(data, keys=[image.module, image.assets, image.server, image.frame]) row = deep_get(data, keys=[image.module, image.assets, image.server, image.frame])
row.load_image(image) row.load_image(image)
# Apply `search` of the first frame to all # Set `search`
for path, frames in deep_iter(data, depth=3): for path, frames in deep_iter(data, depth=3):
print(path, frames) print(path, frames)
# If `search` attribute is set in the first frame, apply to all
first = frames[1] first = frames[1]
search = first.search if first.search else DataAssets.area_to_search(first.area) if first.search:
for frame in frames.values(): for frame in frames.values():
frame.search = search 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 return data
def generate_code(): def generate_code():
all = iter_assets() all_assets = iter_assets()
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]) path = os.path.join(AzurLaneConfig.ASSETS_MODULE, module.split('/', maxsplit=1)[0])
output = os.path.join(path, 'assets.py') output = os.path.join(path, 'assets.py')
if os.path.exists(output): if os.path.exists(output):
@ -204,7 +213,7 @@ def generate_code():
continue continue
os.remove(prev) 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]) path = os.path.join(AzurLaneConfig.ASSETS_MODULE, module.split('/', maxsplit=1)[0])
output = os.path.join(path, 'assets') output = os.path.join(path, 'assets')
gen = CodeGenerator() gen = CodeGenerator()

2
gui.py
View File

@ -49,7 +49,7 @@ def func(ev: threading.Event):
args, _ = parser.parse_known_args() args, _ = parser.parse_known_args()
host = args.host or State.deploy_config.WebuiHost or "0.0.0.0" host = args.host or State.deploy_config.WebuiHost or "0.0.0.0"
port = args.port or int(State.deploy_config.WebuiPort) or 22367 port = args.port or int(State.deploy_config.WebuiPort) or 23467
State.electron = args.electron State.electron = args.electron
logger.hr("Launcher config") logger.hr("Launcher config")

View File

@ -74,7 +74,7 @@ class Button(Resource):
threshold=threshold 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. Detects assets by template matching.
@ -83,18 +83,45 @@ class Button(Resource):
Args: Args:
image: Screenshot. image: Screenshot.
similarity (float): 0-1. similarity (float): 0-1.
direct_match: True to ignore `self.search`
Returns: Returns:
bool. 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) res = cv2.matchTemplate(self.image, image, cv2.TM_CCOEFF_NORMED)
_, sim, _, point = cv2.minMaxLoc(res) _, sim, _, point = cv2.minMaxLoc(res)
self._button_offset = np.array(point) + self.search[:2] - self.area[:2] self._button_offset = np.array(point) + self.search[:2] - self.area[:2]
return sim > similarity 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 Template match first, color match then
@ -102,11 +129,12 @@ class Button(Resource):
image: Screenshot. image: Screenshot.
similarity (float): 0-1. similarity (float): 0-1.
threshold (int): Default to 10. threshold (int): Default to 10.
direct_match: True to ignore `self.search`
Returns: Returns:
bool.
""" """
matched = self.match_template(image, similarity=similarity) matched = self.match_template(image, similarity=similarity, direct_match=direct_match)
if not matched: if not matched:
return False return False
@ -124,10 +152,10 @@ class ButtonWrapper(Resource):
self.name = name self.name = name
self.data_buttons = kwargs self.data_buttons = kwargs
self._matched_button: t.Optional[Button] = None 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): def resource_release(self):
del_cached_property(self, 'assets') del_cached_property(self, 'buttons')
self._matched_button = None self._matched_button = None
def __str__(self): def __str__(self):
@ -144,16 +172,25 @@ class ButtonWrapper(Resource):
def __bool__(self): def __bool__(self):
return True 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 @cached_property
def buttons(self) -> t.List[Button]: def buttons(self) -> t.List[Button]:
# for trial in [server.lang, 'share', 'cn']: for trial in [server.lang, 'share', 'cn']:
for trial in [server.lang, 'share', 'jp']: try:
assets = self.data_buttons.get(trial, None) assets = self.data_buttons[trial]
if assets is not None:
if isinstance(assets, Button): if isinstance(assets, Button):
return [assets] return [assets]
elif isinstance(assets, list): elif isinstance(assets, list):
return assets return assets
except KeyError:
pass
raise ScriptError(f'ButtonWrapper({self}) on server {server.lang} has no fallback button') raise ScriptError(f'ButtonWrapper({self}) on server {server.lang} has no fallback button')
@ -164,16 +201,45 @@ class ButtonWrapper(Resource):
return True return True
return False 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: 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 self._matched_button = assets
return True return True
return False 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: 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 self._matched_button = assets
return True return True
return False return False
@ -222,18 +288,32 @@ class ButtonWrapper(Resource):
""" """
if isinstance(button, ButtonWrapper): if isinstance(button, ButtonWrapper):
button = button.matched_button button = button.matched_button
for b in self.buttons: for b in self.iter_buttons():
b.load_offset(button) b.load_offset(button)
def clear_offset(self): def clear_offset(self):
for b in self.buttons: for b in self.iter_buttons():
b.clear_offset() 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: class ClickButton:
def __init__(self, button, name='CLICK_BUTTON'): def __init__(self, area, button=None, name='CLICK_BUTTON'):
self.area = button self.area = area
self.button = button if button is None:
self.button = area
else:
self.button = button
self.name = name self.name = name
def __str__(self): def __str__(self):
@ -265,4 +345,4 @@ def match_template(image, template, similarity=0.85):
""" """
res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED) res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
_, sim, _, point = cv2.minMaxLoc(res) _, sim, _, point = cv2.minMaxLoc(res)
return sim > similarity return sim > similarity

View File

@ -1,11 +1,11 @@
import re import re
import module.config.server as server from module.base.decorator import cached_property
from module.base.decorator import cached_property, del_cached_property
def get_assets_from_file(file, regex): def get_assets_from_file(file):
assets = set() assets = set()
regex = re.compile(r"file='(.*?)'")
with open(file, 'r', encoding='utf-8') as f: with open(file, 'r', encoding='utf-8') as f:
for row in f.readlines(): for row in f.readlines():
result = regex.search(row) result = regex.search(row)
@ -20,11 +20,9 @@ class PreservedAssets:
assets = set() assets = set()
assets |= get_assets_from_file( assets |= get_assets_from_file(
file='./tasks/base/assets/assets_base_page.py', file='./tasks/base/assets/assets_base_page.py',
regex=re.compile(r'^([A-Za-z][A-Za-z0-9_]+) = ')
) )
assets |= get_assets_from_file( assets |= get_assets_from_file(
file='./tasks/base/assets/assets_base_popup.py', file='./tasks/base/assets/assets_base_popup.py',
regex=re.compile(r'^([A-Za-z][A-Za-z0-9_]+) = ')
) )
return assets return assets
@ -44,11 +42,13 @@ class Resource:
@classmethod @classmethod
def is_loaded(cls, obj): def is_loaded(cls, obj):
if hasattr(obj, '_image') and obj._image is None: if hasattr(obj, '_image') and obj._image is not None:
return False return True
elif hasattr(obj, 'image') and obj.image is None: if hasattr(obj, 'image') and obj.image is not None:
return False return True
return True if hasattr(obj, 'buttons') and obj.buttons is not None:
return True
return False
@classmethod @classmethod
def resource_show(cls): def resource_show(cls):
@ -56,11 +56,16 @@ class Resource:
logger.hr('Show resource') logger.hr('Show resource')
for key, obj in cls.instances.items(): for key, obj in cls.instances.items():
if cls.is_loaded(obj): if cls.is_loaded(obj):
continue logger.info(f'{obj}: {key}')
logger.info(f'{obj}: {key}')
def release_resources(next_task=''): 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 # Release assets cache
# module.ui has about 80 assets and takes about 3MB # module.ui has about 80 assets and takes about 3MB
# Alas has about 800 assets, but they are not all loaded. # Alas has about 800 assets, but they are not all loaded.
@ -74,4 +79,4 @@ def release_resources(next_task=''):
obj.resource_release() obj.resource_release()
# Useless in most cases, but just call it # Useless in most cases, but just call it
# gc.collect() # gc.collect()

View File

@ -12,6 +12,10 @@
"option": [ "option": [
"auto", "auto",
"JP-Official", "JP-Official",
"OVERSEA-TWHKMO",
"OVERSEA-Korea",
"OVERSEA-Asia",
"OVERSEA-America",
"OVERSEA-Global" "OVERSEA-Global"
] ]
}, },
@ -257,6 +261,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": { "TacticalChallenge": {
"Scheduler": { "Scheduler": {
"Enable": { "Enable": {
@ -348,6 +537,30 @@
"stored": "StoredInt", "stored": "StoredInt",
"order": 3, "order": 3,
"color": "#21befc" "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"
} }
} }
} }

View File

@ -81,6 +81,48 @@ Cafe:
AutoAdjust: true AutoAdjust: true
SecondCafe: 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: TacticalChallenge:
PlayerSelect: PlayerSelect:
value: 0 value: 0
@ -99,3 +141,15 @@ ItemStorage:
stored: StoredInt stored: StoredInt
order: 3 order: 3
color: "#21befc" color: "#21befc"
BountyTicket:
stored: StoredBountyTicket
order: 4
color: "#94cb44"
ScrimmageTicket:
stored: StoredScrimmageTicket
order: 5
color: "#f86c6a"
TacticalChallengeTicket:
stored: StoredTacticalChallengeTicket
order: 6
color: "#7ac8e5"

Some files were not shown because too many files have changed in this diff Show More