Compare commits
No commits in common. "15bf77da3d98eb159b807f17b3fa202e949ef4d2" and "0a697e939805782ad6649d0f206a67e18e2bd8f9" have entirely different histories.
15bf77da3d
...
0a697e9398
@ -15,19 +15,12 @@ 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
|
- [x] OVERSEA - Global
|
||||||
|
|
||||||
Supported in-game languages:
|
|
||||||
|
|
||||||
- [x] Japanese
|
|
||||||
- [x] English
|
|
||||||
|
|
||||||
## Relative projects
|
## Relative projects
|
||||||
|
|
||||||
|
|||||||
18
README.md
@ -15,28 +15,12 @@
|
|||||||
- [x] **咖啡厅** 领取奖励 / 互动 / 第二咖啡厅
|
- [x] **咖啡厅** 领取奖励 / 互动 / 第二咖啡厅
|
||||||
- [x] **公会** 领取体力
|
- [x] **公会** 领取体力
|
||||||
- [x] **邮箱** 领取奖励
|
- [x] **邮箱** 领取奖励
|
||||||
- [x] **悬赏通缉** 自动扫荡
|
|
||||||
- [x] **学院交流会** 自动扫荡
|
|
||||||
- [x] **战术对抗赛** 领取奖励 / 自动战斗
|
- [x] **战术对抗赛** 领取奖励 / 自动战斗
|
||||||
|
|
||||||
目前支持的服务器:
|
目前支持的服务器:
|
||||||
|
|
||||||
- [x] 日服
|
- [x] 日服
|
||||||
- [x] 国际服
|
- [x] 国际服 - 全球
|
||||||
|
|
||||||
目前支持的游戏内语言:
|
|
||||||
|
|
||||||
- [x] 日语
|
|
||||||
- [x] 英语
|
|
||||||
|
|
||||||
## 已知问题
|
|
||||||
|
|
||||||
若愿意提供其他语言或国服支持,请开 PR 或 Issue。
|
|
||||||
|
|
||||||
- **国际服登录的全屏通知**:未实现自动关闭,正在研究中
|
|
||||||
- **大小月卡**:未实现自动领取,~~因为没买过~~,可能不影响使用。愿意提供图片的请开 Issue
|
|
||||||
- **月卡的额外悬赏券和学院交流券**:不太清楚月卡领取额外券的机制,~~因为没买过~~,可能影响相关任务使用券和体力的计算。愿意提供相关信息的请开
|
|
||||||
Issue
|
|
||||||
|
|
||||||
## 相关项目
|
## 相关项目
|
||||||
|
|
||||||
|
|||||||
8
aas.py
@ -34,14 +34,6 @@ 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()
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 905 KiB After Width: | Height: | Size: 905 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
@ -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: 3em;
|
height: 3.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
*[style*="--header-text--"] {
|
*[style*="--header-text--"] {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 5.1 KiB |
BIN
assets/jp/base/page/MAIN_GO_TO_CAFE.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
assets/jp/base/page/MAIN_GO_TO_CIRCLE.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.5 KiB |
BIN
assets/jp/base/page/MAIN_GO_TO_GACHA.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 5.0 KiB |
BIN
assets/jp/base/page/MAIN_GO_TO_MOMOTALK.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
assets/jp/base/page/MAIN_GO_TO_PURCHASE.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
assets/jp/base/page/MAIN_GO_TO_SCHEDULE.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
assets/jp/base/page/MAIN_GO_TO_SHOP.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.9 KiB |
BIN
assets/jp/base/page/MOMOTALK_GO_TO_MAIN.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
assets/jp/base/popup/DAILY_NEWS.BUTTON.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
assets/jp/base/popup/DAILY_NEWS.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
assets/jp/base/popup/DAILY_REWARD.BUTTON.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/jp/base/popup/DAILY_REWARD.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
assets/jp/base/popup/GET_NEW_STUDENT.BUTTON.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
assets/jp/base/popup/GET_NEW_STUDENT.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 14 KiB |
BIN
assets/jp/cafe/BOX_CAFE.png
Normal file
|
After Width: | Height: | Size: 905 KiB |
BIN
assets/jp/cafe/CLICKABLE_TEMPLATE.png
Normal file
|
After Width: | Height: | Size: 3.9 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 |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 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 |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 1016 KiB |
@ -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 23467
|
# [In most cases] Default to 22367
|
||||||
WebuiPort: 23467
|
WebuiPort: 22367
|
||||||
# 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
|
||||||
|
|||||||
@ -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 23467
|
# [In most cases] Default to 22367
|
||||||
WebuiPort: 23467
|
WebuiPort: 22367
|
||||||
# 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
|
||||||
|
|||||||
@ -63,52 +63,6 @@
|
|||||||
"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,
|
||||||
@ -130,10 +84,7 @@
|
|||||||
"ItemStorage": {
|
"ItemStorage": {
|
||||||
"AP": {},
|
"AP": {},
|
||||||
"Credit": {},
|
"Credit": {},
|
||||||
"Pyroxene": {},
|
"Pyroxene": {}
|
||||||
"BountyTicket": {},
|
|
||||||
"ScrimmageTicket": {},
|
|
||||||
"TacticalChallengeTicket": {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,7 +55,7 @@ class ConfigModel:
|
|||||||
|
|
||||||
# Webui
|
# Webui
|
||||||
WebuiHost: str = "0.0.0.0"
|
WebuiHost: str = "0.0.0.0"
|
||||||
WebuiPort: int = 23467
|
WebuiPort: int = 22367
|
||||||
Language: str = "en-US"
|
Language: str = "en-US"
|
||||||
Theme: str = "default"
|
Theme: str = "default"
|
||||||
DpiScaling: bool = True
|
DpiScaling: bool = True
|
||||||
@ -77,12 +77,6 @@ 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':
|
||||||
@ -90,6 +84,9 @@ 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():
|
||||||
|
|||||||
@ -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 23467
|
# [In most cases] Default to 22367
|
||||||
WebuiPort: 23467
|
WebuiPort: 22367
|
||||||
# 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
|
||||||
|
|||||||
@ -179,29 +179,20 @@ 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)
|
||||||
# Set `search`
|
# Apply `search` of the first frame to all
|
||||||
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]
|
||||||
if first.search:
|
search = first.search if first.search else DataAssets.area_to_search(first.area)
|
||||||
for frame in frames.values():
|
for frame in frames.values():
|
||||||
frame.search = first.search
|
frame.search = 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_assets = iter_assets()
|
all = iter_assets()
|
||||||
for module, module_data in all_assets.items():
|
for module, module_data in all.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):
|
||||||
@ -213,7 +204,7 @@ def generate_code():
|
|||||||
continue
|
continue
|
||||||
os.remove(prev)
|
os.remove(prev)
|
||||||
|
|
||||||
for module, module_data in all_assets.items():
|
for module, module_data in all.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
@ -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 23467
|
port = args.port or int(State.deploy_config.WebuiPort) or 22367
|
||||||
State.electron = args.electron
|
State.electron = args.electron
|
||||||
|
|
||||||
logger.hr("Launcher config")
|
logger.hr("Launcher config")
|
||||||
|
|||||||
@ -74,7 +74,7 @@ class Button(Resource):
|
|||||||
threshold=threshold
|
threshold=threshold
|
||||||
)
|
)
|
||||||
|
|
||||||
def match_template(self, image, similarity=0.85, direct_match=False) -> bool:
|
def match_template(self, image, similarity=0.85) -> bool:
|
||||||
"""
|
"""
|
||||||
Detects assets by template matching.
|
Detects assets by template matching.
|
||||||
|
|
||||||
@ -83,12 +83,10 @@ 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.
|
||||||
"""
|
"""
|
||||||
if not direct_match:
|
|
||||||
image = crop(image, self.search, copy=False)
|
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)
|
||||||
@ -96,32 +94,7 @@ class Button(Resource):
|
|||||||
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_multi_template(self, image, similarity=0.85, direct_match=False):
|
def match_template_color(self, image, similarity=0.85, threshold=30) -> bool:
|
||||||
"""
|
|
||||||
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
|
||||||
|
|
||||||
@ -129,12 +102,11 @@ 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, direct_match=direct_match)
|
matched = self.match_template(image, similarity=similarity)
|
||||||
if not matched:
|
if not matched:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -152,10 +124,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(f'{name}:{next(self.iter_buttons(), None)}')
|
self.resource_add(self.name)
|
||||||
|
|
||||||
def resource_release(self):
|
def resource_release(self):
|
||||||
del_cached_property(self, 'buttons')
|
del_cached_property(self, 'assets')
|
||||||
self._matched_button = None
|
self._matched_button = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -172,25 +144,16 @@ 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']:
|
||||||
try:
|
for trial in [server.lang, 'share', 'jp']:
|
||||||
assets = self.data_buttons[trial]
|
assets = self.data_buttons.get(trial, None)
|
||||||
|
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')
|
||||||
|
|
||||||
@ -201,45 +164,16 @@ class ButtonWrapper(Resource):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def match_template(self, image, similarity=0.85, direct_match=False) -> bool:
|
def match_template(self, image, similarity=0.85) -> bool:
|
||||||
for assets in self.buttons:
|
for assets in self.buttons:
|
||||||
if assets.match_template(image, similarity=similarity, direct_match=direct_match):
|
if assets.match_template(image, similarity=similarity):
|
||||||
self._matched_button = assets
|
self._matched_button = assets
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def match_multi_template(self, image, similarity=0.85, threshold=5, direct_match=False):
|
def match_template_color(self, image, similarity=0.85, threshold=30) -> bool:
|
||||||
"""
|
|
||||||
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:
|
||||||
ps += assets.match_multi_template(image, similarity=similarity, direct_match=direct_match)
|
if assets.match_template_color(image, similarity=similarity, threshold=threshold):
|
||||||
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
|
||||||
@ -288,31 +222,17 @@ class ButtonWrapper(Resource):
|
|||||||
"""
|
"""
|
||||||
if isinstance(button, ButtonWrapper):
|
if isinstance(button, ButtonWrapper):
|
||||||
button = button.matched_button
|
button = button.matched_button
|
||||||
for b in self.iter_buttons():
|
for b in self.buttons:
|
||||||
b.load_offset(button)
|
b.load_offset(button)
|
||||||
|
|
||||||
def clear_offset(self):
|
def clear_offset(self):
|
||||||
for b in self.iter_buttons():
|
for b in self.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, area, button=None, name='CLICK_BUTTON'):
|
def __init__(self, button, name='CLICK_BUTTON'):
|
||||||
self.area = area
|
self.area = button
|
||||||
if button is None:
|
|
||||||
self.button = area
|
|
||||||
else:
|
|
||||||
self.button = button
|
self.button = button
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from module.base.decorator import cached_property
|
import module.config.server as server
|
||||||
|
from module.base.decorator import cached_property, del_cached_property
|
||||||
|
|
||||||
|
|
||||||
def get_assets_from_file(file):
|
def get_assets_from_file(file, regex):
|
||||||
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,9 +20,11 @@ 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
|
||||||
|
|
||||||
@ -42,13 +44,11 @@ class Resource:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_loaded(cls, obj):
|
def is_loaded(cls, obj):
|
||||||
if hasattr(obj, '_image') and obj._image is not None:
|
if hasattr(obj, '_image') and obj._image is 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
|
return False
|
||||||
|
elif hasattr(obj, 'image') and obj.image is None:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resource_show(cls):
|
def resource_show(cls):
|
||||||
@ -56,16 +56,11 @@ 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.
|
||||||
|
|||||||
@ -12,10 +12,6 @@
|
|||||||
"option": [
|
"option": [
|
||||||
"auto",
|
"auto",
|
||||||
"JP-Official",
|
"JP-Official",
|
||||||
"OVERSEA-TWHKMO",
|
|
||||||
"OVERSEA-Korea",
|
|
||||||
"OVERSEA-Asia",
|
|
||||||
"OVERSEA-America",
|
|
||||||
"OVERSEA-Global"
|
"OVERSEA-Global"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -261,191 +257,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"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": {
|
||||||
@ -537,30 +348,6 @@
|
|||||||
"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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -81,48 +81,6 @@ 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
|
||||||
@ -141,15 +99,3 @@ 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"
|
|
||||||
|
|||||||