1
0
mirror of https://github.com/TheFunny/ArisuAutoSweeper synced 2026-06-26 11:25:04 +00:00

36 Commits

Author SHA1 Message Date
RedDeadDepresso ca1ef7b0ab Merge a41e49e30b into 0f6dd93608 2023-12-21 00:35:48 +00:00
RedDeadDepresso a41e49e30b feat: momotalk 2023-12-20 23:21:54 +00:00
RedDeadDepresso e17c0a0e44 feat: shop 2023-12-19 11:34:06 +00:00
RedDeadDepresso 83da63ff8e refactor: gui
added tree view Farm and Reward.
2023-12-17 00:39:01 +00:00
RedDeadDepresso 991831a9c6 feat: tasks
Added module tasks for EN
2023-12-16 23:46:31 +00:00
YoursFunny 0f6dd93608 fix(sweep): remove index if equal 0 2023-12-09 21:22:11 +08:00
YoursFunny f5cf0a7fbe fix(tc): add handle ui_additional 2023-12-07 18:24:09 +08:00
YoursFunny 16d78e1e16 fix(sweep): correct load index situation 2023-12-07 13:04:05 +08:00
YoursFunny 7b707be841 refactor(scrimmage): use stage ap count 2023-12-06 17:11:50 +08:00
YoursFunny 93bf1f73e2 feat: separate stage ap count 2023-12-05 16:21:19 +08:00
YoursFunny 71da6fd996 feat: add auto select for bounty and scrimmage 2023-11-30 14:21:19 +08:00
YoursFunny ecd4ba0a7c doc: update readme 2023-11-29 18:38:59 +08:00
YoursFunny d684fd79f6 feat(sweep): support finding max sweepable index 2023-11-29 14:55:55 +08:00
YoursFunny 6ae785634c refactor(sweep): separate sweepable check and search_box generate 2023-11-29 14:11:51 +08:00
YoursFunny a0d3fd75af refactor(sweep): change current_indexes 2023-11-29 14:05:50 +08:00
YoursFunny 257e092936 refactor(sweep): use regex for index match 2023-11-29 13:54:55 +08:00
YoursFunny 32cee3f6b5 fix(cafe): adjust name of invitation 2023-11-29 12:39:51 +08:00
YoursFunny 659f58db38 fix(cafe): add check of null invitation name 2023-11-28 21:46:21 +08:00
YoursFunny 6dac8a1de2 feat(cafe): add invitation assets for en 2023-11-28 20:53:24 +08:00
YoursFunny b1aeb64768 lang: change option description 2023-11-28 20:53:01 +08:00
YoursFunny 88e8a98b76 fix(cafe): apply config of choice only when not set before 2023-11-28 16:18:34 +08:00
YoursFunny 39d00ac549 fix(cafe): correct target name check 2023-11-28 16:11:50 +08:00
YoursFunny 11d2a6ef7e feat(cafe): add invitation condition check 2023-11-28 14:04:37 +08:00
YoursFunny 1f5b68d095 feat(webui): add invitation condition options 2023-11-28 14:04:07 +08:00
YoursFunny 5f3ff140dd feat(cafe): add invitation 2023-11-27 22:02:50 +08:00
YoursFunny 8698fa20c2 feat(webui): add config of invitation options 2023-11-27 20:48:37 +08:00
YoursFunny b426c6caac feat(cafe): add invitation assets for jp 2023-11-27 15:44:56 +08:00
YoursFunny 8d83ec1657 fix: add swipe name 2023-11-27 15:26:27 +08:00
YoursFunny 7fcda15329 perf(sweep): improve list insight logic 2023-11-27 13:17:29 +08:00
YoursFunny 54aa1bafb5 refactor(sweep): simplify list enter match 2023-11-26 18:54:26 +08:00
YoursFunny d756b0dc3f perf: reduce config save times 2023-11-25 15:28:57 +08:00
YoursFunny e71118c09e fix: improve data update stability 2023-11-25 15:28:07 +08:00
YoursFunny 44b6d5cdf8 fix: expand ticket ocr region 2023-11-25 14:27:01 +08:00
YoursFunny f92dba92c9 fix(sweep): use swipe instead of drag in list 2023-11-24 14:30:43 +08:00
YoursFunny 86ce04cff9 fix(sweep): extend timer stable 2023-11-24 13:59:39 +08:00
YoursFunny f2065507c2 fix(ui): change task order 2023-11-23 16:37:16 +08:00
98 changed files with 3053 additions and 580 deletions
+1 -1
View File
@@ -12,7 +12,7 @@
The script is still under active development. The following features have been implemented: The script is still under active development. The following features have been implemented:
- [x] **Cafe** Claim rewards / Interact / Second floor - [x] **Cafe** Claim rewards / Interact / Invitation / Second floor
- [x] **Club** Claim AP - [x] **Club** Claim AP
- [x] **Mailbox** Claim rewards - [x] **Mailbox** Claim rewards
- [x] **Bounty** Auto sweep - [x] **Bounty** Auto sweep
+1 -1
View File
@@ -12,7 +12,7 @@
当前脚本还在活跃开发中,已经实现的功能有: 当前脚本还在活跃开发中,已经实现的功能有:
- [x] **咖啡厅** 领取奖励 / 互动 / 第二咖啡厅 - [x] **咖啡厅** 领取奖励 / 互动 / 邀请 / 第二咖啡厅
- [x] **公会** 领取体力 - [x] **公会** 领取体力
- [x] **邮箱** 领取奖励 - [x] **邮箱** 领取奖励
- [x] **悬赏通缉** 自动扫荡 - [x] **悬赏通缉** 自动扫荡
+12 -1
View File
@@ -46,11 +46,22 @@ class ArisuAutoSweeper(AzurLaneAutoScript):
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()
def task(self):
from tasks.task.task import Task
Task(config=self.config, device=self.device).run()
def shop(self):
from tasks.shop.shop import Shop
Shop(config=self.config, device=self.device).run()
def momotalk(self):
from tasks.momotalk.momotalk import MomoTalk
MomoTalk(config=self.config, device=self.device).run()
def data_update(self): def data_update(self):
from tasks.item.data_update import DataUpdate from tasks.item.data_update import DataUpdate
DataUpdate(config=self.config, device=self.device).run() DataUpdate(config=self.config, device=self.device).run()
if __name__ == '__main__': if __name__ == '__main__':
aas = ArisuAutoSweeper('aas') aas = ArisuAutoSweeper('aas')
aas.loop() aas.loop()
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 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: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

+161 -87
View File
@@ -33,93 +33,6 @@
"ServerUpdate": "04:00" "ServerUpdate": "04:00"
} }
}, },
"Cafe": {
"Scheduler": {
"Enable": true,
"NextRun": "2020-01-01 00:00:00",
"Command": "Cafe",
"ServerUpdate": "04:00, 16:00"
},
"Cafe": {
"Reward": true,
"Touch": true,
"AutoAdjust": true,
"SecondCafe": true
}
},
"Mail": {
"Scheduler": {
"Enable": true,
"NextRun": "2020-01-01 00:00:00",
"Command": "Mail",
"ServerUpdate": "04:00"
}
},
"Circle": {
"Scheduler": {
"Enable": true,
"NextRun": "2020-01-01 00:00:00",
"Command": "Circle",
"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,
"NextRun": "2020-01-01 00:00:00",
"Command": "TacticalChallenge",
"ServerUpdate": "15:00"
},
"TacticalChallenge": {
"PlayerSelect": 0
}
},
"DataUpdate": { "DataUpdate": {
"Scheduler": { "Scheduler": {
"Enable": true, "Enable": true,
@@ -135,5 +48,166 @@
"ScrimmageTicket": {}, "ScrimmageTicket": {},
"TacticalChallengeTicket": {} "TacticalChallengeTicket": {}
} }
},
"Cafe": {
"Scheduler": {
"Enable": true,
"NextRun": "2020-01-01 00:00:00",
"Command": "Cafe",
"ServerUpdate": "04:00, 16:00"
},
"Cafe": {
"Reward": true,
"Touch": true,
"AutoAdjust": true,
"SecondCafe": true
},
"Invitation": {
"Enable": true,
"WaitingHour": 0,
"Choice": "list_top",
"Name": null,
"Substitute": false
}
},
"Shop": {
"Scheduler": {
"Enable": false,
"NextRun": "2020-01-01 00:00:00",
"Command": "Shop",
"ServerUpdate": "04:00"
},
"NormalShop": {
"Enable": false,
"Purchases": 1,
"1": false,
"2": false,
"3": false,
"4": false,
"5": false,
"6": false,
"7": false,
"8": false,
"9": false,
"10": false,
"11": false,
"12": false,
"13": false,
"14": false,
"15": false,
"16": false,
"17": false,
"18": false,
"19": false,
"20": false
},
"TacticalChallengeShop": {
"Enable": false,
"Purchases": 1,
"1": false,
"2": false,
"3": false,
"4": false,
"5": false,
"6": false,
"7": false,
"8": false,
"9": false,
"10": false,
"11": false,
"12": false,
"13": false,
"14": false,
"15": false
}
},
"Bounty": {
"Scheduler": {
"Enable": true,
"NextRun": "2020-01-01 00:00:00",
"Command": "Bounty",
"ServerUpdate": "04:00"
},
"Bounty": {
"OnError": "skip"
},
"Highway": {
"Stage": 0,
"Count": 2
},
"DesertRailroad": {
"Stage": 0,
"Count": 2
},
"Schoolhouse": {
"Stage": 0,
"Count": 2
}
},
"Scrimmage": {
"Scheduler": {
"Enable": true,
"NextRun": "2020-01-01 00:00:00",
"Command": "Scrimmage",
"ServerUpdate": "04:00"
},
"Scrimmage": {
"OnError": "skip"
},
"Trinity": {
"Stage": 0,
"Count": 2
},
"Gehenna": {
"Stage": 0,
"Count": 2
},
"Millennium": {
"Stage": 0,
"Count": 2
}
},
"TacticalChallenge": {
"Scheduler": {
"Enable": true,
"NextRun": "2020-01-01 00:00:00",
"Command": "TacticalChallenge",
"ServerUpdate": "15:00"
},
"TacticalChallenge": {
"PlayerSelect": 0
}
},
"Circle": {
"Scheduler": {
"Enable": true,
"NextRun": "2020-01-01 00:00:00",
"Command": "Circle",
"ServerUpdate": "04:00"
}
},
"Task": {
"Scheduler": {
"Enable": false,
"NextRun": "2020-01-01 00:00:00",
"Command": "Task",
"ServerUpdate": "04:00"
}
},
"Mail": {
"Scheduler": {
"Enable": true,
"NextRun": "2020-01-01 00:00:00",
"Command": "Mail",
"ServerUpdate": "04:00"
}
},
"Momotalk": {
"Scheduler": {
"Enable": false,
"NextRun": "2020-01-01 00:00:00",
"Command": "Momotalk",
"ServerUpdate": "04:00"
}
} }
} }
+614 -323
View File
@@ -162,329 +162,6 @@
} }
} }
}, },
"Cafe": {
"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": "Cafe",
"display": "hide"
},
"ServerUpdate": {
"type": "input",
"value": "04:00, 16:00",
"display": "hide"
}
},
"Cafe": {
"Reward": {
"type": "checkbox",
"value": true
},
"Touch": {
"type": "checkbox",
"value": true
},
"AutoAdjust": {
"type": "checkbox",
"value": true
},
"SecondCafe": {
"type": "checkbox",
"value": true
}
}
},
"Mail": {
"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": "Mail",
"display": "hide"
},
"ServerUpdate": {
"type": "input",
"value": "04:00",
"display": "hide"
}
}
},
"Circle": {
"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": "Circle",
"display": "hide"
},
"ServerUpdate": {
"type": "input",
"value": "04:00",
"display": "hide"
}
}
},
"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": {
"type": "checkbox",
"value": true,
"option": [
true,
false
]
},
"NextRun": {
"type": "datetime",
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "input",
"value": "TacticalChallenge",
"display": "hide"
},
"ServerUpdate": {
"type": "input",
"value": "15:00",
"display": "hide"
}
},
"TacticalChallenge": {
"PlayerSelect": {
"type": "select",
"value": 0,
"option": [
0,
1,
2,
3
]
}
}
},
"DataUpdate": { "DataUpdate": {
"Scheduler": { "Scheduler": {
"Enable": { "Enable": {
@@ -563,5 +240,619 @@
"color": "#7ac8e5" "color": "#7ac8e5"
} }
} }
},
"Cafe": {
"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": "Cafe",
"display": "hide"
},
"ServerUpdate": {
"type": "input",
"value": "04:00, 16:00",
"display": "hide"
}
},
"Cafe": {
"Reward": {
"type": "checkbox",
"value": true
},
"Touch": {
"type": "checkbox",
"value": true
},
"AutoAdjust": {
"type": "checkbox",
"value": true
},
"SecondCafe": {
"type": "checkbox",
"value": true
}
},
"Invitation": {
"Enable": {
"type": "checkbox",
"value": true
},
"WaitingHour": {
"type": "select",
"value": 0,
"option": [
0,
3,
6,
9
]
},
"Choice": {
"type": "select",
"value": "list_top",
"option": [
"list_top",
"by_name"
]
},
"Name": {
"type": "textarea",
"value": null
},
"Substitute": {
"type": "checkbox",
"value": false
}
}
},
"Shop": {
"Scheduler": {
"Enable": {
"type": "checkbox",
"value": false,
"option": [
true,
false
]
},
"NextRun": {
"type": "datetime",
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "input",
"value": "Shop",
"display": "hide"
},
"ServerUpdate": {
"type": "input",
"value": "04:00",
"display": "hide"
}
},
"NormalShop": {
"Enable": {
"type": "checkbox",
"value": false
},
"Purchases": {
"type": "select",
"value": 1,
"option": [
1,
2,
3,
4
]
},
"1": {
"type": "checkbox",
"value": false
},
"2": {
"type": "checkbox",
"value": false
},
"3": {
"type": "checkbox",
"value": false
},
"4": {
"type": "checkbox",
"value": false
},
"5": {
"type": "checkbox",
"value": false
},
"6": {
"type": "checkbox",
"value": false
},
"7": {
"type": "checkbox",
"value": false
},
"8": {
"type": "checkbox",
"value": false
},
"9": {
"type": "checkbox",
"value": false
},
"10": {
"type": "checkbox",
"value": false
},
"11": {
"type": "checkbox",
"value": false
},
"12": {
"type": "checkbox",
"value": false
},
"13": {
"type": "checkbox",
"value": false
},
"14": {
"type": "checkbox",
"value": false
},
"15": {
"type": "checkbox",
"value": false
},
"16": {
"type": "checkbox",
"value": false
},
"17": {
"type": "checkbox",
"value": false
},
"18": {
"type": "checkbox",
"value": false
},
"19": {
"type": "checkbox",
"value": false
},
"20": {
"type": "checkbox",
"value": false
}
},
"TacticalChallengeShop": {
"Enable": {
"type": "checkbox",
"value": false
},
"Purchases": {
"type": "select",
"value": 1,
"option": [
1,
2,
3,
4
]
},
"1": {
"type": "checkbox",
"value": false
},
"2": {
"type": "checkbox",
"value": false
},
"3": {
"type": "checkbox",
"value": false
},
"4": {
"type": "checkbox",
"value": false
},
"5": {
"type": "checkbox",
"value": false
},
"6": {
"type": "checkbox",
"value": false
},
"7": {
"type": "checkbox",
"value": false
},
"8": {
"type": "checkbox",
"value": false
},
"9": {
"type": "checkbox",
"value": false
},
"10": {
"type": "checkbox",
"value": false
},
"11": {
"type": "checkbox",
"value": false
},
"12": {
"type": "checkbox",
"value": false
},
"13": {
"type": "checkbox",
"value": false
},
"14": {
"type": "checkbox",
"value": false
},
"15": {
"type": "checkbox",
"value": false
}
}
},
"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": 0,
"option": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9
]
},
"Count": {
"type": "input",
"value": 2
}
},
"DesertRailroad": {
"Stage": {
"type": "select",
"value": 0,
"option": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9
]
},
"Count": {
"type": "input",
"value": 2
}
},
"Schoolhouse": {
"Stage": {
"type": "select",
"value": 0,
"option": [
0,
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": 0,
"option": [
0,
1,
2,
3,
4
]
},
"Count": {
"type": "input",
"value": 2
}
},
"Gehenna": {
"Stage": {
"type": "select",
"value": 0,
"option": [
0,
1,
2,
3,
4
]
},
"Count": {
"type": "input",
"value": 2
}
},
"Millennium": {
"Stage": {
"type": "select",
"value": 0,
"option": [
0,
1,
2,
3,
4
]
},
"Count": {
"type": "input",
"value": 2
}
}
},
"TacticalChallenge": {
"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": "TacticalChallenge",
"display": "hide"
},
"ServerUpdate": {
"type": "input",
"value": "15:00",
"display": "hide"
}
},
"TacticalChallenge": {
"PlayerSelect": {
"type": "select",
"value": 0,
"option": [
0,
1,
2,
3
]
}
}
},
"Circle": {
"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": "Circle",
"display": "hide"
},
"ServerUpdate": {
"type": "input",
"value": "04:00",
"display": "hide"
}
}
},
"Task": {
"Scheduler": {
"Enable": {
"type": "checkbox",
"value": false,
"option": [
true,
false
]
},
"NextRun": {
"type": "datetime",
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "input",
"value": "Task",
"display": "hide"
},
"ServerUpdate": {
"type": "input",
"value": "04:00",
"display": "hide"
}
}
},
"Mail": {
"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": "Mail",
"display": "hide"
},
"ServerUpdate": {
"type": "input",
"value": "04:00",
"display": "hide"
}
}
},
"Momotalk": {
"Scheduler": {
"Enable": {
"type": "checkbox",
"value": false,
"option": [
true,
false
]
},
"NextRun": {
"type": "datetime",
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "input",
"value": "Momotalk",
"display": "hide"
},
"ServerUpdate": {
"type": "input",
"value": "04:00",
"display": "hide"
}
}
} }
} }
+71 -12
View File
@@ -80,6 +80,18 @@ Cafe:
Touch: true Touch: true
AutoAdjust: true AutoAdjust: true
SecondCafe: true SecondCafe: true
Invitation:
Enable: true
WaitingHour:
value: 0
option: [ 0, 3, 6, 9 ]
Choice:
value: list_top
option: [ list_top, by_name ]
Name:
value: null
type: textarea
Substitute: false
Bounty: Bounty:
OnError: OnError:
@@ -88,18 +100,18 @@ Bounty:
Highway: Highway:
Stage: Stage:
value: 1 value: 0
option: [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] option: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
Count: 2 Count: 2
DesertRailroad: DesertRailroad:
Stage: Stage:
value: 1 value: 0
option: [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] option: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
Count: 2 Count: 2
Schoolhouse: Schoolhouse:
Stage: Stage:
value: 1 value: 0
option: [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] option: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
Count: 2 Count: 2
Scrimmage: Scrimmage:
@@ -109,18 +121,18 @@ Scrimmage:
Trinity: Trinity:
Stage: Stage:
value: 1 value: 0
option: [ 1, 2, 3, 4 ] option: [ 0, 1, 2, 3, 4 ]
Count: 2 Count: 2
Gehenna: Gehenna:
Stage: Stage:
value: 1 value: 0
option: [ 1, 2, 3, 4 ] option: [ 0, 1, 2, 3, 4 ]
Count: 2 Count: 2
Millennium: Millennium:
Stage: Stage:
value: 1 value: 0
option: [ 1, 2, 3, 4 ] option: [ 0, 1, 2, 3, 4 ]
Count: 2 Count: 2
TacticalChallenge: TacticalChallenge:
@@ -128,6 +140,53 @@ TacticalChallenge:
value: 0 value: 0
option: [ 0, 1, 2, 3 ] option: [ 0, 1, 2, 3 ]
NormalShop:
Enable: false
Purchases:
value: 1
option: [ 1, 2, 3, 4 ]
"1": false
"2": false
"3": false
"4": false
"5": false
"6": false
"7": false
"8": false
"9": false
"10": false
"11": false
"12": false
"13": false
"14": false
"15": false
"16": false
"17": false
"18": false
"19": false
"20": false
TacticalChallengeShop:
Enable: false
Purchases:
value: 1
option: [ 1, 2, 3, 4 ]
"1": false
"2": false
"3": false
"4": false
"5": false
"6": false
"7": false
"8": false
"9": false
"10": false
"11": false
"12": false
"13": false
"14": false
"15": false
ItemStorage: ItemStorage:
AP: AP:
stored: StoredAP stored: StoredAP
+21 -6
View File
@@ -4,20 +4,35 @@
"page": "setting", "page": "setting",
"tasks": [ "tasks": [
"Alas", "Alas",
"Restart" "Restart",
"DataUpdate"
] ]
}, },
"Daily": { "Daily": {
"menu": "list", "menu": "collapse",
"page": "setting", "page": "setting",
"tasks": [ "tasks": [
"Cafe", "Cafe",
"Mail", "Shop"
"Circle", ]
},
"Farm": {
"menu": "collapse",
"page": "setting",
"tasks": [
"Bounty", "Bounty",
"Scrimmage", "Scrimmage",
"TacticalChallenge", "TacticalChallenge"
"DataUpdate" ]
},
"Reward": {
"menu": "collapse",
"page": "setting",
"tasks": [
"Circle",
"Task",
"Mail",
"Momotalk"
] ]
} }
} }
+29 -6
View File
@@ -15,20 +15,31 @@ Alas:
- Optimization - Optimization
Restart: Restart:
- Scheduler - Scheduler
DataUpdate:
- Scheduler
- ItemStorage
# ==================== Daily ==================== # ==================== Daily ====================
Daily: Daily:
menu: 'list' menu: 'collapse'
page: 'setting' page: 'setting'
tasks: tasks:
Cafe: Cafe:
- Scheduler - Scheduler
- Cafe - Cafe
Mail: - Invitation
- Scheduler Shop:
Circle:
- Scheduler - Scheduler
- NormalShop
- TacticalChallengeShop
# ==================== Farm ====================
Farm:
menu: 'collapse'
page: 'setting'
tasks:
Bounty: Bounty:
- Scheduler - Scheduler
- Bounty - Bounty
@@ -44,6 +55,18 @@ Daily:
TacticalChallenge: TacticalChallenge:
- Scheduler - Scheduler
- TacticalChallenge - TacticalChallenge
DataUpdate:
# ==================== Rewards ====================
Reward:
menu: 'collapse'
page: 'setting'
tasks:
Circle:
- Scheduler
Task:
- Scheduler
Mail:
- Scheduler
Momotalk:
- Scheduler - Scheduler
- ItemStorage
+56 -6
View File
@@ -45,39 +45,89 @@ class GeneratedConfig:
Cafe_AutoAdjust = True Cafe_AutoAdjust = True
Cafe_SecondCafe = True Cafe_SecondCafe = True
# Group `Invitation`
Invitation_Enable = True
Invitation_WaitingHour = 0 # 0, 3, 6, 9
Invitation_Choice = 'list_top' # list_top, by_name
Invitation_Name = None
Invitation_Substitute = False
# Group `Bounty` # Group `Bounty`
Bounty_OnError = 'skip' # stop, skip Bounty_OnError = 'skip' # stop, skip
# Group `Highway` # Group `Highway`
Highway_Stage = 1 # 1, 2, 3, 4, 5, 6, 7, 8, 9 Highway_Stage = 0 # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
Highway_Count = 2 Highway_Count = 2
# Group `DesertRailroad` # Group `DesertRailroad`
DesertRailroad_Stage = 1 # 1, 2, 3, 4, 5, 6, 7, 8, 9 DesertRailroad_Stage = 0 # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
DesertRailroad_Count = 2 DesertRailroad_Count = 2
# Group `Schoolhouse` # Group `Schoolhouse`
Schoolhouse_Stage = 1 # 1, 2, 3, 4, 5, 6, 7, 8, 9 Schoolhouse_Stage = 0 # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
Schoolhouse_Count = 2 Schoolhouse_Count = 2
# Group `Scrimmage` # Group `Scrimmage`
Scrimmage_OnError = 'skip' # stop, skip Scrimmage_OnError = 'skip' # stop, skip
# Group `Trinity` # Group `Trinity`
Trinity_Stage = 1 # 1, 2, 3, 4 Trinity_Stage = 0 # 0, 1, 2, 3, 4
Trinity_Count = 2 Trinity_Count = 2
# Group `Gehenna` # Group `Gehenna`
Gehenna_Stage = 1 # 1, 2, 3, 4 Gehenna_Stage = 0 # 0, 1, 2, 3, 4
Gehenna_Count = 2 Gehenna_Count = 2
# Group `Millennium` # Group `Millennium`
Millennium_Stage = 1 # 1, 2, 3, 4 Millennium_Stage = 0 # 0, 1, 2, 3, 4
Millennium_Count = 2 Millennium_Count = 2
# Group `TacticalChallenge` # Group `TacticalChallenge`
TacticalChallenge_PlayerSelect = 0 # 0, 1, 2, 3 TacticalChallenge_PlayerSelect = 0 # 0, 1, 2, 3
# Group `NormalShop`
NormalShop_Enable = False
NormalShop_Purchases = 1 # 1, 2, 3, 4
NormalShop_1 = False
NormalShop_2 = False
NormalShop_3 = False
NormalShop_4 = False
NormalShop_5 = False
NormalShop_6 = False
NormalShop_7 = False
NormalShop_8 = False
NormalShop_9 = False
NormalShop_10 = False
NormalShop_11 = False
NormalShop_12 = False
NormalShop_13 = False
NormalShop_14 = False
NormalShop_15 = False
NormalShop_16 = False
NormalShop_17 = False
NormalShop_18 = False
NormalShop_19 = False
NormalShop_20 = False
# Group `TacticalChallengeShop`
TacticalChallengeShop_Enable = False
TacticalChallengeShop_Purchases = 1 # 1, 2, 3, 4
TacticalChallengeShop_1 = False
TacticalChallengeShop_2 = False
TacticalChallengeShop_3 = False
TacticalChallengeShop_4 = False
TacticalChallengeShop_5 = False
TacticalChallengeShop_6 = False
TacticalChallengeShop_7 = False
TacticalChallengeShop_8 = False
TacticalChallengeShop_9 = False
TacticalChallengeShop_10 = False
TacticalChallengeShop_11 = False
TacticalChallengeShop_12 = False
TacticalChallengeShop_13 = False
TacticalChallengeShop_14 = False
TacticalChallengeShop_15 = False
# Group `ItemStorage` # Group `ItemStorage`
ItemStorage_AP = {} ItemStorage_AP = {}
ItemStorage_Credit = {} ItemStorage_Credit = {}
+1 -1
View File
@@ -9,7 +9,7 @@ class ManualConfig:
SCHEDULER_PRIORITY = """ SCHEDULER_PRIORITY = """
Restart Restart
> Cafe > Circle > Mail > DataUpdate > Bounty > Cafe > Circle > Mail > DataUpdate > Bounty
> Scrimmage > TacticalChallenge > Scrimmage > TacticalChallenge > Task > Shop > Momotalk
""" """
""" """
+242 -8
View File
@@ -7,6 +7,14 @@
"Daily": { "Daily": {
"name": "Daily", "name": "Daily",
"help": "" "help": ""
},
"Farm": {
"name": "Farm",
"help": ""
},
"Reward": {
"name": "Reward",
"help": ""
} }
}, },
"Task": { "Task": {
@@ -18,16 +26,16 @@
"name": "Error Handling", "name": "Error Handling",
"help": "" "help": ""
}, },
"DataUpdate": {
"name": "Dashboard Upd",
"help": ""
},
"Cafe": { "Cafe": {
"name": "Cafe", "name": "Cafe",
"help": "" "help": ""
}, },
"Mail": { "Shop": {
"name": "Mailbox", "name": "Shop",
"help": ""
},
"Circle": {
"name": "Club",
"help": "" "help": ""
}, },
"Bounty": { "Bounty": {
@@ -42,8 +50,20 @@
"name": "Tactical Challenge", "name": "Tactical Challenge",
"help": "" "help": ""
}, },
"DataUpdate": { "Circle": {
"name": "Dashboard Upd", "name": "Club",
"help": ""
},
"Task": {
"name": "Tasks",
"help": ""
},
"Mail": {
"name": "Mailbox",
"help": ""
},
"Momotalk": {
"name": "MomoTalk",
"help": "" "help": ""
} }
}, },
@@ -221,6 +241,38 @@
"help": "JP server only\nEnable auto switch to second floor and perform interaction" "help": "JP server only\nEnable auto switch to second floor and perform interaction"
} }
}, },
"Invitation": {
"_info": {
"name": "Invitation Settings",
"help": ""
},
"Enable": {
"name": "Enable",
"help": "Enable cafe invitation"
},
"WaitingHour": {
"name": "Invitation Condition",
"help": "Choose whether to invite students based on the remaining time before next cafe refresh\nThis is for obtaining maximum student interation and maximum affection when AFK. Recommended option:\n24*7 AFK:\t\t Choose \"If 9 more hours remaining\"\nNormal usage:\t Choose \"Invite immediately\"",
"0": "Invite immediately",
"3": "If 3 more hours remaining",
"6": "If 6 more hours remaining",
"9": "If 9 more hours remaining"
},
"Choice": {
"name": "Choose Inviting Student",
"help": "Top first student of list: Invite the student at the top first of the list\nBy name: Invite the student with the name you set below",
"list_top": "Top first student of list",
"by_name": "By name"
},
"Name": {
"name": "Inviting Student Name",
"help": "Fill in the name of the student to be invited. Use in-game text language. Use > to connect multiple students. Example:\nJP: ホシノ(水着) > 御坂美琴 > ユズ\nOVERSEA: Hoshino(Swimsuit) > Yuuka > Kayoko(New Year)"
},
"Substitute": {
"name": "Replace existing student",
"help": "Whether to replace the existing student with their alt.\nIf not, try to match the next student"
}
},
"Bounty": { "Bounty": {
"_info": { "_info": {
"name": "Bounty Settings", "name": "Bounty Settings",
@@ -241,6 +293,7 @@
"Stage": { "Stage": {
"name": "Select Stage", "name": "Select Stage",
"help": "", "help": "",
"0": "Auto select",
"1": "01 - Overpass A", "1": "01 - Overpass A",
"2": "02 - Overpass B", "2": "02 - Overpass B",
"3": "03 - Overpass C", "3": "03 - Overpass C",
@@ -264,6 +317,7 @@
"Stage": { "Stage": {
"name": "Select Stage", "name": "Select Stage",
"help": "", "help": "",
"0": "Auto select",
"1": "01 - Abandoned Train A", "1": "01 - Abandoned Train A",
"2": "02 - Abandoned Train B", "2": "02 - Abandoned Train B",
"3": "03 - Abandoned Train C", "3": "03 - Abandoned Train C",
@@ -287,6 +341,7 @@
"Stage": { "Stage": {
"name": "Select Stage", "name": "Select Stage",
"help": "", "help": "",
"0": "Auto select",
"1": "01 - Besieged Classroom A", "1": "01 - Besieged Classroom A",
"2": "02 - Besieged Classroom B", "2": "02 - Besieged Classroom B",
"3": "03 - Besieged Classroom C", "3": "03 - Besieged Classroom C",
@@ -322,6 +377,7 @@
"Stage": { "Stage": {
"name": "Select Stage", "name": "Select Stage",
"help": "", "help": "",
"0": "Auto select",
"1": "01 - Trinity A", "1": "01 - Trinity A",
"2": "02 - Trinity B", "2": "02 - Trinity B",
"3": "03 - Trinity C", "3": "03 - Trinity C",
@@ -340,6 +396,7 @@
"Stage": { "Stage": {
"name": "Select Stage", "name": "Select Stage",
"help": "", "help": "",
"0": "Auto select",
"1": "01 - Gehenna A", "1": "01 - Gehenna A",
"2": "02 - Gehenna B", "2": "02 - Gehenna B",
"3": "03 - Gehenna C", "3": "03 - Gehenna C",
@@ -358,6 +415,7 @@
"Stage": { "Stage": {
"name": "Select Stage", "name": "Select Stage",
"help": "", "help": "",
"0": "Auto select",
"1": "01 - Millennium A", "1": "01 - Millennium A",
"2": "02 - Millennium B", "2": "02 - Millennium B",
"3": "03 - Millennium C", "3": "03 - Millennium C",
@@ -382,6 +440,182 @@
"3": "Third" "3": "Third"
} }
}, },
"NormalShop": {
"_info": {
"name": "Normal Shop Settings",
"help": ""
},
"Enable": {
"name": "Enable",
"help": ""
},
"Purchases": {
"name": "Number of Purchases",
"help": "Default can be purchased once + number of refreshes = number of purchases, for example, 2 purchases = 1 default purchase + 1 refresh purchase",
"1": "1",
"2": "2",
"3": "3",
"4": "4"
},
"1": {
"name": "1",
"help": "x5 Novice Activity Report - 12,500 Credits"
},
"2": {
"name": "2",
"help": "x5 Normal Activity Report - 125,000 Credits"
},
"3": {
"name": "3",
"help": "x3 Advanced Activity Report - 300,000 Credits"
},
"4": {
"name": "4",
"help": "x1 Superior Activity Report - 500,000 Credits"
},
"5": {
"name": "5",
"help": "x5 Lesser Enhancement Stone - 10,000 Credits"
},
"6": {
"name": "6",
"help": "x5 Normal Enhancement Stone - 40,000 Credits"
},
"7": {
"name": "7",
"help": "x3 Advanced Enhancement Stone - 96,000 Credits"
},
"8": {
"name": "8",
"help": "x1 Superior Enhancement Stone - 128,000 Credits"
},
"9": {
"name": "9",
"help": "x5 Lesser Enhancement Stone - 10,000 Credits"
},
"10": {
"name": "10",
"help": "x5 Normal Enhancement Stone - 40,000 Credits"
},
"11": {
"name": "11",
"help": "x3 Advanced Enhancement Stone - 96,000 Credits"
},
"12": {
"name": "12",
"help": "x1 Superior Enhancement Stone - 128,000 Credits"
},
"13": {
"name": "13",
"help": "x10 Lesser Enhancement Stone - 20,000 Credits"
},
"14": {
"name": "14",
"help": "x10 Normal Enhancement Stone - 80,000 Credits"
},
"15": {
"name": "15",
"help": "x6 Advanced Enhancement Stone - 192,000 Credits"
},
"16": {
"name": "16",
"help": "x2 Superior Enhancement Stone - 256,000 Credits"
},
"17": {
"name": "17",
"help": "x1 Random Selection - 8,000 Credits"
},
"18": {
"name": "18",
"help": "x1 Random Selection - 8,000 Credits"
},
"19": {
"name": "19",
"help": "x1 Random Selection - 25,000 Credits"
},
"20": {
"name": "20",
"help": "x1 Random Selection - 25,000 Credits"
}
},
"TacticalChallengeShop": {
"_info": {
"name": "Tactical Challenge Shop Settings",
"help": ""
},
"Enable": {
"name": "Enable",
"help": ""
},
"Purchases": {
"name": "Number of Purchases",
"help": "",
"1": "1",
"2": "2",
"3": "3",
"4": "4"
},
"1": {
"name": "1",
"help": "x5 Shizuko's Eleph - 50 Coins"
},
"2": {
"name": "2",
"help": "x5 Mashiro's Eleph - 50 Coins"
},
"3": {
"name": "3",
"help": "x5 Saya's Eleph - 50 Coins"
},
"4": {
"name": "4",
"help": "x5 Fuuka's Eleph - 50 Coins"
},
"5": {
"name": "5",
"help": "x5 Utaha's Eleph - 50 Coins"
},
"6": {
"name": "6",
"help": "x1 Lesser Drink(30 AP) - 15 Coins"
},
"7": {
"name": "7",
"help": "x1 Normal Drink(60 AP) - 30 Coins"
},
"8": {
"name": "8",
"help": "x10 Novice Activity Report - 5 Coins"
},
"9": {
"name": "9",
"help": "x5 Normal Activity Report - 25 Coins"
},
"10": {
"name": "10",
"help": "x3 Advanced Activity Report - 60 Coins"
},
"11": {
"name": "11",
"help": "x1 Superior Activity Report - 100 Coins"
},
"12": {
"name": "12",
"help": "x5000 Credits - 4 Coins"
},
"13": {
"name": "13",
"help": "x25k Credits - 20 Coins"
},
"14": {
"name": "14",
"help": "x75k Credits - 60 Coins"
},
"15": {
"name": "15",
"help": "x125k Credits - 100 Coins"
}
},
"ItemStorage": { "ItemStorage": {
"_info": { "_info": {
"name": "ItemStorage._info.name", "name": "ItemStorage._info.name",
+243 -9
View File
@@ -7,6 +7,14 @@
"Daily": { "Daily": {
"name": "每日", "name": "每日",
"help": "" "help": ""
},
"Farm": {
"name": "Menu.Farm.name",
"help": "Menu.Farm.help"
},
"Reward": {
"name": "Menu.Reward.name",
"help": "Menu.Reward.help"
} }
}, },
"Task": { "Task": {
@@ -18,17 +26,17 @@
"name": "异常处理", "name": "异常处理",
"help": "" "help": ""
}, },
"DataUpdate": {
"name": "仪表盘更新",
"help": ""
},
"Cafe": { "Cafe": {
"name": "咖啡厅", "name": "咖啡厅",
"help": "" "help": ""
}, },
"Mail": { "Shop": {
"name": "邮箱", "name": "Task.Shop.name",
"help": "" "help": "Task.Shop.help"
},
"Circle": {
"name": "公会",
"help": "社团 / 小组"
}, },
"Bounty": { "Bounty": {
"name": "悬赏通缉", "name": "悬赏通缉",
@@ -42,9 +50,21 @@
"name": "战术对抗赛", "name": "战术对抗赛",
"help": "战术大赛 / 竞技场" "help": "战术大赛 / 竞技场"
}, },
"DataUpdate": { "Circle": {
"name": "仪表盘更新", "name": "公会",
"help": "社团 / 小组"
},
"Task": {
"name": "Task.Task.name",
"help": "Task.Task.help"
},
"Mail": {
"name": "邮箱",
"help": "" "help": ""
},
"Momotalk": {
"name": "Task.Momotalk.name",
"help": "Task.Momotalk.help"
} }
}, },
"Scheduler": { "Scheduler": {
@@ -221,6 +241,38 @@
"help": "仅支持日服\n自动切换第二咖啡厅进行互动点击" "help": "仅支持日服\n自动切换第二咖啡厅进行互动点击"
} }
}, },
"Invitation": {
"_info": {
"name": "邀请设置",
"help": ""
},
"Enable": {
"name": "启用",
"help": "是否启用咖啡厅邀请功能"
},
"WaitingHour": {
"name": "邀请条件",
"help": "根据距离下一次咖啡厅刷新的时长,选择是否邀请学生\n此项设立是为了后台挂机最大化收益,使得邀请券能被最大化利用。推荐选项:\n常驻挂机:\t选择“离下次刷新 9 小时以上”\n非常驻挂机:\t选择“不等待刷新,立即邀请”,或根据自己挂机时长选择",
"0": "不等待刷新,立即邀请",
"3": "离下次刷新 3 小时以上",
"6": "离下次刷新 6 小时以上",
"9": "离下次刷新 9 小时以上"
},
"Choice": {
"name": "选择邀请学生",
"help": "按列表第一个:优先邀请列表中第一个学生,若失败则则尝试下一个\n按名字:优先邀请下方填写学生名的第一个,若失败则尝试下一个",
"list_top": "按列表第一个",
"by_name": "按名字"
},
"Name": {
"name": "邀请学生名",
"help": "填写要邀请的学生的名字,使用游戏内语言填写,多个学生使用 > 连接。例:\n日服:\tホシノ(水着) > 御坂美琴 > ユズ\n国际服:\tHoshino(Swimsuit) > Yuuka > Kayoko(New Year)"
},
"Substitute": {
"name": "是否替换已存在学生",
"help": "若咖啡厅已存在所邀请学生的不同服装,选择是否替换该学生\n若不替换,则尝试匹配下一位学生"
}
},
"Bounty": { "Bounty": {
"_info": { "_info": {
"name": "悬赏通缉设置", "name": "悬赏通缉设置",
@@ -241,6 +293,7 @@
"Stage": { "Stage": {
"name": "选择关卡", "name": "选择关卡",
"help": "", "help": "",
"0": "自动选择",
"1": "01 - 高架公路 A", "1": "01 - 高架公路 A",
"2": "02 - 高架公路 B", "2": "02 - 高架公路 B",
"3": "03 - 高架公路 C", "3": "03 - 高架公路 C",
@@ -264,6 +317,7 @@
"Stage": { "Stage": {
"name": "选择关卡", "name": "选择关卡",
"help": "", "help": "",
"0": "自动选择",
"1": "01 - 被遗弃的列车 A", "1": "01 - 被遗弃的列车 A",
"2": "02 - 被遗弃的列车 B", "2": "02 - 被遗弃的列车 B",
"3": "03 - 被遗弃的列车 C", "3": "03 - 被遗弃的列车 C",
@@ -287,6 +341,7 @@
"Stage": { "Stage": {
"name": "选择关卡", "name": "选择关卡",
"help": "", "help": "",
"0": "自动选择",
"1": "01 - 被袭击的教室 A", "1": "01 - 被袭击的教室 A",
"2": "02 - 被袭击的教室 B", "2": "02 - 被袭击的教室 B",
"3": "03 - 被袭击的教室 C", "3": "03 - 被袭击的教室 C",
@@ -322,6 +377,7 @@
"Stage": { "Stage": {
"name": "选择关卡", "name": "选择关卡",
"help": "", "help": "",
"0": "自动选择",
"1": "01 - 三一 A", "1": "01 - 三一 A",
"2": "02 - 三一 B", "2": "02 - 三一 B",
"3": "03 - 三一 C", "3": "03 - 三一 C",
@@ -340,6 +396,7 @@
"Stage": { "Stage": {
"name": "选择关卡", "name": "选择关卡",
"help": "", "help": "",
"0": "自动选择",
"1": "01 - 格黑娜 A", "1": "01 - 格黑娜 A",
"2": "02 - 格黑娜 B", "2": "02 - 格黑娜 B",
"3": "03 - 格黑娜 C", "3": "03 - 格黑娜 C",
@@ -358,6 +415,7 @@
"Stage": { "Stage": {
"name": "选择关卡", "name": "选择关卡",
"help": "", "help": "",
"0": "自动选择",
"1": "01 - 千年 A", "1": "01 - 千年 A",
"2": "02 - 千年 B", "2": "02 - 千年 B",
"3": "03 - 千年 C", "3": "03 - 千年 C",
@@ -382,6 +440,182 @@
"3": "第三位" "3": "第三位"
} }
}, },
"NormalShop": {
"_info": {
"name": "NormalShop._info.name",
"help": "NormalShop._info.help"
},
"Enable": {
"name": "NormalShop.Enable.name",
"help": "NormalShop.Enable.help"
},
"Purchases": {
"name": "NormalShop.Purchases.name",
"help": "NormalShop.Purchases.help",
"1": "1",
"2": "2",
"3": "3",
"4": "4"
},
"1": {
"name": "1",
"help": ""
},
"2": {
"name": "2",
"help": ""
},
"3": {
"name": "3",
"help": ""
},
"4": {
"name": "4",
"help": ""
},
"5": {
"name": "5",
"help": ""
},
"6": {
"name": "6",
"help": ""
},
"7": {
"name": "7",
"help": ""
},
"8": {
"name": "8",
"help": ""
},
"9": {
"name": "9",
"help": ""
},
"10": {
"name": "10",
"help": ""
},
"11": {
"name": "11",
"help": ""
},
"12": {
"name": "12",
"help": ""
},
"13": {
"name": "13",
"help": ""
},
"14": {
"name": "14",
"help": ""
},
"15": {
"name": "15",
"help": ""
},
"16": {
"name": "16",
"help": ""
},
"17": {
"name": "NormalShop.17.name",
"help": "NormalShop.17.help"
},
"18": {
"name": "NormalShop.18.name",
"help": "NormalShop.18.help"
},
"19": {
"name": "NormalShop.19.name",
"help": "NormalShop.19.help"
},
"20": {
"name": "NormalShop.20.name",
"help": "NormalShop.20.help"
}
},
"TacticalChallengeShop": {
"_info": {
"name": "TacticalChallengeShop._info.name",
"help": "TacticalChallengeShop._info.help"
},
"Enable": {
"name": "TacticalChallengeShop.Enable.name",
"help": "TacticalChallengeShop.Enable.help"
},
"Purchases": {
"name": "TacticalChallengeShop.Purchases.name",
"help": "TacticalChallengeShop.Purchases.help",
"1": "1",
"2": "2",
"3": "3",
"4": "4"
},
"1": {
"name": "1",
"help": ""
},
"2": {
"name": "2",
"help": ""
},
"3": {
"name": "3",
"help": ""
},
"4": {
"name": "4",
"help": ""
},
"5": {
"name": "5",
"help": ""
},
"6": {
"name": "6",
"help": ""
},
"7": {
"name": "7",
"help": ""
},
"8": {
"name": "8",
"help": ""
},
"9": {
"name": "9",
"help": ""
},
"10": {
"name": "10",
"help": ""
},
"11": {
"name": "11",
"help": ""
},
"12": {
"name": "12",
"help": ""
},
"13": {
"name": "13",
"help": ""
},
"14": {
"name": "14",
"help": ""
},
"15": {
"name": "15",
"help": ""
}
},
"ItemStorage": { "ItemStorage": {
"_info": { "_info": {
"name": "ItemStorage._info.name", "name": "ItemStorage._info.name",
+4
View File
@@ -1,6 +1,10 @@
from module.config.stored.classes import ( from module.config.stored.classes import (
StoredAP, StoredAP,
StoredBase,
StoredBountyTicket, StoredBountyTicket,
StoredCounter,
StoredExpiredAt0400,
StoredExpiredAtMonday0400,
StoredInt, StoredInt,
StoredScrimmageTicket, StoredScrimmageTicket,
StoredTacticalChallengeTicket, StoredTacticalChallengeTicket,
+8 -8
View File
@@ -75,17 +75,17 @@ OCR_TICKET = ButtonWrapper(
name='OCR_TICKET', name='OCR_TICKET',
jp=Button( jp=Button(
file='./assets/jp/bounty/OCR_TICKET.png', file='./assets/jp/bounty/OCR_TICKET.png',
area=(195, 85, 237, 113), area=(196, 87, 258, 113),
search=(175, 65, 257, 133), search=(176, 67, 278, 133),
color=(197, 206, 213), color=(173, 197, 212),
button=(195, 85, 237, 113), button=(196, 87, 258, 113),
), ),
en=Button( en=Button(
file='./assets/en/bounty/OCR_TICKET.png', file='./assets/en/bounty/OCR_TICKET.png',
area=(225, 85, 267, 114), area=(229, 89, 285, 112),
search=(205, 65, 287, 134), search=(209, 69, 305, 132),
color=(197, 206, 213), color=(177, 194, 207),
button=(225, 85, 267, 114), button=(229, 89, 285, 112),
), ),
) )
SELECT_DESERT_RAILROAD = ButtonWrapper( SELECT_DESERT_RAILROAD = ButtonWrapper(
+1
View File
@@ -41,6 +41,7 @@ class Bounty(BountyUI):
if action == 'stop': if action == 'stop':
raise RequestHumanTakeover raise RequestHumanTakeover
elif action == 'skip': elif action == 'skip':
with self.config.multi_set():
self.config.task_delay(server_update=True) self.config.task_delay(server_update=True)
self.config.task_stop() self.config.task_stop()
+2
View File
@@ -22,6 +22,8 @@ class BountyUI(UI):
return False return False
def enter_stage(self, index: int) -> bool: def enter_stage(self, index: int) -> bool:
if not index:
index = BOUNTY_LIST.insight_max_sweepable_index(self)
if BOUNTY_LIST.select_index_enter(self, index): if BOUNTY_LIST.select_index_enter(self, index):
return True return True
return False return False
+187
View File
@@ -40,6 +40,40 @@ CAFE_FIRST = ButtonWrapper(
button=(82, 152, 136, 175), button=(82, 152, 136, 175),
), ),
) )
CAFE_INVITE = ButtonWrapper(
name='CAFE_INVITE',
jp=Button(
file='./assets/jp/cafe/CAFE_INVITE.png',
area=(816, 635, 845, 655),
search=(796, 615, 865, 675),
color=(173, 182, 192),
button=(816, 635, 845, 655),
),
en=Button(
file='./assets/en/cafe/CAFE_INVITE.png',
area=(816, 635, 845, 655),
search=(796, 615, 865, 675),
color=(173, 182, 192),
button=(816, 635, 845, 655),
),
)
CAFE_INVITED = ButtonWrapper(
name='CAFE_INVITED',
jp=Button(
file='./assets/jp/cafe/CAFE_INVITED.png',
area=(815, 633, 847, 657),
search=(795, 613, 867, 677),
color=(144, 144, 146),
button=(815, 633, 847, 657),
),
en=Button(
file='./assets/en/cafe/CAFE_INVITED.png',
area=(815, 633, 847, 657),
search=(795, 613, 867, 677),
color=(144, 144, 146),
button=(815, 633, 847, 657),
),
)
CAFE_SECOND = ButtonWrapper( CAFE_SECOND = ButtonWrapper(
name='CAFE_SECOND', name='CAFE_SECOND',
jp=Button( jp=Button(
@@ -91,6 +125,23 @@ CHANGE_CAFE_SELECTED = ButtonWrapper(
button=(40, 87, 191, 112), button=(40, 87, 191, 112),
), ),
) )
CHECK_MOMOTALK = ButtonWrapper(
name='CHECK_MOMOTALK',
jp=Button(
file='./assets/jp/cafe/CHECK_MOMOTALK.png',
area=(421, 83, 447, 108),
search=(401, 63, 467, 128),
color=(253, 203, 212),
button=(421, 83, 447, 108),
),
en=Button(
file='./assets/en/cafe/CHECK_MOMOTALK.png',
area=(421, 83, 447, 108),
search=(401, 63, 467, 128),
color=(253, 203, 212),
button=(421, 83, 447, 108),
),
)
CHECK_REWARD = ButtonWrapper( CHECK_REWARD = ButtonWrapper(
name='CHECK_REWARD', name='CHECK_REWARD',
jp=Button( jp=Button(
@@ -186,6 +237,91 @@ INVENTORY = ButtonWrapper(
button=(1123, 90, 1165, 130), button=(1123, 90, 1165, 130),
), ),
) )
INVITE_CONFIRM = ButtonWrapper(
name='INVITE_CONFIRM',
jp=Button(
file='./assets/jp/cafe/INVITE_CONFIRM.png',
area=(609, 147, 671, 178),
search=(589, 127, 691, 198),
color=(152, 164, 177),
button=(665, 471, 870, 533),
),
en=Button(
file='./assets/en/cafe/INVITE_CONFIRM.png',
area=(592, 149, 689, 176),
search=(572, 129, 709, 196),
color=(169, 179, 191),
button=(664, 470, 872, 534),
),
)
INVITE_IN_SECOND = ButtonWrapper(
name='INVITE_IN_SECOND',
jp=Button(
file='./assets/jp/cafe/INVITE_IN_SECOND.png',
area=(482, 147, 799, 177),
search=(462, 127, 819, 197),
color=(166, 177, 188),
button=(482, 147, 799, 177),
),
en=Button(
file='./assets/en/cafe/INVITE_IN_SECOND.png',
area=(482, 147, 799, 177),
search=(462, 127, 819, 197),
color=(166, 177, 188),
button=(482, 147, 799, 177),
),
)
INVITE_IN_SECOND_CLOSE = ButtonWrapper(
name='INVITE_IN_SECOND_CLOSE',
jp=Button(
file='./assets/jp/cafe/INVITE_IN_SECOND_CLOSE.png',
area=(874, 150, 900, 176),
search=(854, 130, 920, 196),
color=(180, 189, 198),
button=(874, 150, 900, 176),
),
en=Button(
file='./assets/en/cafe/INVITE_IN_SECOND_CLOSE.png',
area=(874, 150, 900, 176),
search=(854, 130, 920, 196),
color=(180, 189, 198),
button=(874, 150, 900, 176),
),
)
INVITE_SUBSTITUTE = ButtonWrapper(
name='INVITE_SUBSTITUTE',
jp=Button(
file='./assets/jp/cafe/INVITE_SUBSTITUTE.png',
area=(582, 154, 698, 184),
search=(562, 134, 718, 204),
color=(154, 166, 179),
button=(673, 478, 858, 541),
),
en=Button(
file='./assets/en/cafe/INVITE_SUBSTITUTE.png',
area=(506, 157, 775, 186),
search=(486, 137, 795, 206),
color=(176, 186, 196),
button=(673, 477, 857, 542),
),
)
INVITE_SUBSTITUTE_CLOSE = ButtonWrapper(
name='INVITE_SUBSTITUTE_CLOSE',
jp=Button(
file='./assets/jp/cafe/INVITE_SUBSTITUTE_CLOSE.png',
area=(867, 158, 893, 184),
search=(847, 138, 913, 204),
color=(180, 189, 199),
button=(867, 158, 893, 184),
),
en=Button(
file='./assets/en/cafe/INVITE_SUBSTITUTE_CLOSE.png',
area=(867, 158, 893, 184),
search=(847, 138, 913, 204),
color=(180, 189, 199),
button=(867, 158, 893, 184),
),
)
MOMOTALK_CLOSE = ButtonWrapper( MOMOTALK_CLOSE = ButtonWrapper(
name='MOMOTALK_CLOSE', name='MOMOTALK_CLOSE',
jp=Button( jp=Button(
@@ -203,6 +339,40 @@ MOMOTALK_CLOSE = ButtonWrapper(
button=(824, 82, 850, 108), button=(824, 82, 850, 108),
), ),
) )
MOMOTALK_INVITE = ButtonWrapper(
name='MOMOTALK_INVITE',
jp=Button(
file='./assets/jp/cafe/MOMOTALK_INVITE.png',
area=(764, 211, 809, 234),
search=(744, 191, 829, 254),
color=(90, 163, 195),
button=(764, 211, 809, 234),
),
en=Button(
file='./assets/en/cafe/MOMOTALK_INVITE.png',
area=(755, 210, 817, 233),
search=(735, 190, 837, 253),
color=(98, 179, 211),
button=(755, 210, 817, 233),
),
)
MOMOTALK_ITEM = ButtonWrapper(
name='MOMOTALK_ITEM',
jp=Button(
file='./assets/jp/cafe/MOMOTALK_ITEM.png',
area=(489, 193, 864, 259),
search=(469, 173, 884, 279),
color=(203, 230, 240),
button=(489, 193, 864, 259),
),
en=Button(
file='./assets/en/cafe/MOMOTALK_ITEM.png',
area=(489, 193, 864, 259),
search=(469, 173, 884, 279),
color=(203, 230, 240),
button=(489, 193, 864, 259),
),
)
OCR_CAFE = ButtonWrapper( OCR_CAFE = ButtonWrapper(
name='OCR_CAFE', name='OCR_CAFE',
jp=Button( jp=Button(
@@ -220,6 +390,23 @@ OCR_CAFE = ButtonWrapper(
button=(1105, 639, 1195, 674), button=(1105, 639, 1195, 674),
), ),
) )
OCR_NAME = ButtonWrapper(
name='OCR_NAME',
jp=Button(
file='./assets/jp/cafe/OCR_NAME.png',
area=(488, 194, 704, 588),
search=(468, 174, 724, 608),
color=(237, 239, 241),
button=(488, 194, 704, 588),
),
en=Button(
file='./assets/en/cafe/OCR_NAME.png',
area=(488, 194, 704, 588),
search=(468, 174, 724, 608),
color=(237, 239, 241),
button=(488, 194, 704, 588),
),
)
STUDENT_LIST = ButtonWrapper( STUDENT_LIST = ButtonWrapper(
name='STUDENT_LIST', name='STUDENT_LIST',
jp=Button( jp=Button(
+7 -2
View File
@@ -6,6 +6,7 @@ from module.logger import logger
from module.ui.switch import Switch from module.ui.switch import Switch
from tasks.base.page import page_cafe from tasks.base.page import page_cafe
from tasks.cafe.assets.assets_cafe import * from tasks.cafe.assets.assets_cafe import *
from tasks.cafe.invitation import handle_invitation
from tasks.cafe.ui import CafeUI from tasks.cafe.ui import CafeUI
SWITCH_CAFE = Switch('Cafe_switch') SWITCH_CAFE = Switch('Cafe_switch')
@@ -22,8 +23,9 @@ class CafeStatus(Enum):
OCR = 1 OCR = 1
REWARD = 2 REWARD = 2
GOT = 3 GOT = 3
CLICK = 4 INVITATION = 4
CHECK = 5 CLICK = 5
CHECK = 6
FINISHED = -1 FINISHED = -1
@@ -63,6 +65,9 @@ class Cafe(CafeUI):
logger.info('Cafe reward have been got') logger.info('Cafe reward have been got')
self.appear_then_click(GET_REWARD_CLOSE) self.appear_then_click(GET_REWARD_CLOSE)
if not self.appear(GET_REWARD_CLOSE): if not self.appear(GET_REWARD_CLOSE):
return CafeStatus.INVITATION
case CafeStatus.INVITATION:
if handle_invitation(self):
return CafeStatus.CLICK return CafeStatus.CLICK
case CafeStatus.CLICK: case CafeStatus.CLICK:
buttons = self.get_clickable_buttons(offset=(45, 10)) buttons = self.get_clickable_buttons(offset=(45, 10))
+314
View File
@@ -0,0 +1,314 @@
import re
from datetime import datetime, timedelta
from enum import Enum
import numpy as np
from module.base.base import ModuleBase
from module.base.timer import Timer
from module.base.utils import area_size, area_offset
from module.config.utils import get_server_next_update
from module.logger import logger
from module.ocr.ocr import Ocr
from tasks.cafe.assets.assets_cafe import *
class InvitationOcr(Ocr):
def after_process(self, result):
result = super().after_process(result)
result = result.replace('モI', 'モエ')
return result
class InvitationStatus(Enum):
MOMOTALK = 0
OCR = 1
SELECT = 2
CONFIRM = 3
SUBSTITUTE = 4
IN_SECOND = 5
INVITED = 6
FAILED = 7
FINISHED = -1
class Invitation:
swipe_vector_range = (0.65, 0.85)
cafe_update = ["04:00", "16:00"]
def __init__(self, name: str):
self.name = name
self.ocr = InvitationOcr(OCR_NAME)
self.list = OCR_NAME
self.item = MOMOTALK_ITEM
self.invite = MOMOTALK_INVITE
self.target_names = []
self.waiting_hour = None
self.substitute = None
self.choice = None
self.invited = []
self.current_names = []
def __str__(self):
return f'Invitation({self.name})'
__repr__ = __str__
def __eq__(self, other):
return str(self) == str(other)
def __hash__(self):
return hash(self.name)
def swipe_page(self, main: ModuleBase, vector_range=None, reverse=False):
"""
Args:
main:
vector_range (tuple[float, float]):
reverse (bool):
"""
if vector_range is None:
vector_range = self.swipe_vector_range
vector = np.random.uniform(*vector_range)
width, height = area_size(self.list.button)
vector = (0, -vector * height)
if reverse:
vector = (-vector[0], -vector[1])
name = f'{self.name}_SWIPE'
main.device.swipe_vector(vector, self.list.button, name=name)
main.device.click_record_remove(name)
def load_names(self, main: ModuleBase):
names = self.ocr.detect_and_ocr(main.device.image)
if not names:
logger.warning(f'No valid names in {self.ocr.name}')
return
self.current_names = []
for name_ in names:
name: str = name_.ocr_text.replace(' ', '')
if name.isdigit():
continue
if name.startswith('('):
if not self.current_names:
continue
n = self.current_names.pop(len(self.current_names) - 1)
self.current_names.append((n[0] + name, n[1]))
continue
self.current_names.append((name, name_.box))
@property
def is_invitation(self) -> bool:
return get_server_next_update(self.cafe_update) - datetime.now() > timedelta(hours=self.waiting_hour)
@property
def names(self):
return list(map(lambda x: x[0], self.current_names))
@property
def target_name(self):
return self.target_names[0] if self.target_names else None
def on_success(self):
logger.info(f'Invited {self.target_name}')
self.invited.append(self.target_name)
self.target_names.pop(0)
def on_failed(self):
logger.warning(f'Failed to invite {self.target_name}')
self.target_names.pop(0)
def insight_name(self, name: str, main: ModuleBase, skip_first_screenshot=True) -> bool:
"""
Args:
name:
main:
skip_first_screenshot:
"""
logger.info(f'Insight name: {name}')
last_names: set[str] = set()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
main.device.screenshot()
self.load_names(main)
if name in self.names:
return True
names = self.names
if names and last_names == set(names):
logger.warning(f'Name not found: {name}')
return False
last_names = set(names)
self.swipe_page(main)
main.wait_until_stable(
self.list.button,
timer=Timer(0, 0),
timeout=Timer(1.5, 5)
)
def select_name_invite(
self,
main: ModuleBase,
name: str = None,
insight: bool = True,
skip_first_screenshot: bool = True
) -> bool:
"""
Args:
main:
name:
insight:
skip_first_screenshot:
Returns:
If success
"""
if name is None:
if not self.target_name:
return False
name = self.target_name
if insight and not self.insight_name(name, main, skip_first_screenshot):
return False
logger.info(f'Select name: {name}')
click_interval = Timer(1, 2)
load_names_interval = Timer(1, 5)
timeout = Timer(10, 10).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
main.device.screenshot()
if load_names_interval.reached_and_reset() and not insight:
self.load_names(main)
name_box = next(filter(lambda x: x[0] == name, self.current_names), None)
if name_box is None:
logger.warning(f'No name {name} in {self.ocr.name}')
continue
search_box = area_offset((0, 0, *area_size(self.item.area)), name_box[1][:2])
self.invite.load_search(search_box)
click_button = self.invite.match_multi_template(main.device.image)
if not click_button:
logger.warning(f'No clickable {self.invite.name}')
continue
if click_interval.reached_and_reset():
main.device.click(click_button[0])
return True
if timeout.reached():
logger.warning(f'No clickable {self.invite.name}')
return False
invitation = Invitation('CafeInvitation')
def handle_invitation_status(status: InvitationStatus, main: ModuleBase) -> InvitationStatus:
match status:
case InvitationStatus.MOMOTALK:
if not invitation.is_invitation:
logger.info('Invitation waiting until next refresh')
return InvitationStatus.FINISHED
if main.appear(CAFE_INVITED):
logger.info('Invitation in cooldown')
return InvitationStatus.FINISHED
if invitation.choice != 'list_top' and invitation.target_name is None:
logger.warning('No student to be invited or all invitations failed')
return InvitationStatus.FINISHED
if main.appear(CHECK_MOMOTALK):
return InvitationStatus.OCR
main.appear_then_click(CAFE_INVITE)
case InvitationStatus.OCR:
if invitation.choice == 'list_top':
invitation.load_names(main)
invitation.target_names = invitation.names
invitation.choice = 'list'
if invitation.select_name_invite(main):
return InvitationStatus.SELECT
return InvitationStatus.FAILED
case InvitationStatus.SELECT:
if main.appear(INVITE_CONFIRM):
return InvitationStatus.CONFIRM
if main.config.LANG == 'jp' and main.appear(INVITE_IN_SECOND):
return InvitationStatus.IN_SECOND
if main.appear(INVITE_SUBSTITUTE):
return InvitationStatus.SUBSTITUTE
case InvitationStatus.CONFIRM:
main.appear_then_click(INVITE_CONFIRM)
if not main.appear(INVITE_CONFIRM):
invitation.on_success()
return InvitationStatus.INVITED
case InvitationStatus.IN_SECOND:
main.appear_then_click(INVITE_IN_SECOND_CLOSE)
return InvitationStatus.FAILED
case InvitationStatus.SUBSTITUTE:
if not invitation.substitute:
main.appear_then_click(INVITE_SUBSTITUTE_CLOSE)
return InvitationStatus.FAILED
else:
main.appear_then_click(INVITE_SUBSTITUTE)
if not main.appear(INVITE_SUBSTITUTE):
return InvitationStatus.INVITED
case InvitationStatus.INVITED:
main.appear_then_click(MOMOTALK_CLOSE)
if not main.appear(MOMOTALK_CLOSE):
return InvitationStatus.FINISHED
case InvitationStatus.FAILED:
main.appear_then_click(MOMOTALK_CLOSE)
if not main.appear(CHECK_MOMOTALK):
invitation.on_failed()
return InvitationStatus.MOMOTALK
case InvitationStatus.FINISHED:
pass
case _:
logger.warning(f'Invalid status: {status}')
return status
def handle_invitation(main: ModuleBase):
if not main.config.Invitation_Enable:
logger.info('Invitation disabled')
return True
invitation.waiting_hour = main.config.Invitation_WaitingHour
invitation.substitute = main.config.Invitation_Substitute
if invitation.choice is None:
invitation.choice = main.config.Invitation_Choice
if invitation.choice == 'by_name' and not invitation.target_names:
name = main.config.Invitation_Name
if name is None:
logger.warning('Choose By Name but Inviting Student Name is blank')
return True
name = re.sub(r'[ \t\r\n]', '', name)
name = re.sub(r'[>﹥›˃ᐳ❯]', '>', name)
name = re.sub(r'', '(', name)
name = re.sub(r'', ')', name)
invitation.target_names = name.split('>')
status = InvitationStatus.MOMOTALK
action_timer = Timer(1, 1)
loading_timer = Timer(1, 1)
while 1:
main.device.screenshot()
if not loading_timer.reached():
continue
if action_timer.reached_and_reset():
logger.attr('Status', status)
status = handle_invitation_status(status, main)
if status == InvitationStatus.FINISHED:
return True
+10 -9
View File
@@ -48,21 +48,22 @@ class CafeUI(UI):
vector_left = (-width * r, 0) vector_left = (-width * r, 0)
vector_right = (width * r, 0) vector_right = (width * r, 0)
random_r = (-5, -5, 5, 5) random_r = (-5, -5, 5, 5)
name = 'CAFE_SWIPE'
match direction: match direction:
case 'init': case 'init':
self.device.pinch() self.device.pinch()
self.device.swipe_vector(vector_down, box=BOX_CAFE.area, random_range=random_r, padding=5) self.device.swipe_vector(vector_down, box=BOX_CAFE.area, random_range=random_r, padding=5, name=name)
self.device.swipe_vector(vector_right, box=BOX_CAFE.area, random_range=random_r, padding=5) self.device.swipe_vector(vector_right, box=BOX_CAFE.area, random_range=random_r, padding=5, name=name)
self.device.swipe_vector(vector_up, box=BOX_CAFE.area, random_range=random_r, padding=5) self.device.swipe_vector(vector_up, box=BOX_CAFE.area, random_range=random_r, padding=5, name=name)
self.device.swipe_vector(vector_up, box=BOX_CAFE.area, random_range=random_r, padding=5) self.device.swipe_vector(vector_up, box=BOX_CAFE.area, random_range=random_r, padding=5, name=name)
case 'left': case 'left':
self.device.swipe_vector(vector_left, box=BOX_CAFE.area, random_range=random_r, padding=5) self.device.swipe_vector(vector_left, box=BOX_CAFE.area, random_range=random_r, padding=5, name=name)
self.device.swipe_vector(vector_left, box=BOX_CAFE.area, random_range=random_r, padding=5) self.device.swipe_vector(vector_left, box=BOX_CAFE.area, random_range=random_r, padding=5, name=name)
case 'right': case 'right':
self.device.swipe_vector(vector_right, box=BOX_CAFE.area, random_range=random_r, padding=5) self.device.swipe_vector(vector_right, box=BOX_CAFE.area, random_range=random_r, padding=5, name=name)
self.device.swipe_vector(vector_right, box=BOX_CAFE.area, random_range=random_r, padding=5) self.device.swipe_vector(vector_right, box=BOX_CAFE.area, random_range=random_r, padding=5, name=name)
# solve too much swipe causing restart # solve too much swipe causing restart
self.device.click_record_clear() self.device.click_record_remove(name)
def cafe_additional(self) -> bool: def cafe_additional(self) -> bool:
if self.appear_then_click(INVENTORY): if self.appear_then_click(INVENTORY):
+11 -7
View File
@@ -12,7 +12,10 @@ class DataUpdate(UI):
Page: Page:
in: page_work in: page_work
""" """
ap = DigitCounter(OCR_AP).ocr_single_line(self.device.image) ap, _, ap_total = DigitCounter(OCR_AP).ocr_single_line(self.device.image)
if ap_total == 0:
logger.warning('Invalid AP')
return False
# Data for Credit and Pyroxene # Data for Credit and Pyroxene
ocr = Digit(OCR_DATA) ocr = Digit(OCR_DATA)
timeout = Timer(2, count=6).start() timeout = Timer(2, count=6).start()
@@ -21,27 +24,28 @@ class DataUpdate(UI):
if len(data) != 2: if len(data) != 2:
data = [data[0], data[-1]] data = [data[0], data[-1]]
logger.attr('Data', data) logger.attr('Data', data)
credit, pyroxene = [int(''.join([v for v in d.ocr_text if v.isdigit()])) for d in data] credit, pyroxene = [int(''.join(filter(lambda x: x.isdigit(), d.ocr_text))) for d in data]
if credit > 0 or pyroxene > 0: if credit > 0 or pyroxene > 0:
break break
logger.warning(f'Invalid credit and pyroxene: {data}') logger.warning(f'Invalid credit and pyroxene: {data}')
if timeout.reached(): if timeout.reached():
logger.warning('Get data timeout') logger.warning('Get data timeout')
break return False
logger.attr('Credit', credit) logger.attr('Credit', credit)
logger.attr('Pyroxene', pyroxene) logger.attr('Pyroxene', pyroxene)
with self.config.multi_set(): with self.config.multi_set():
self.config.stored.AP.set(ap[0], ap[2]) self.config.stored.AP.set(ap, ap_total)
self.config.stored.Credit.value = credit self.config.stored.Credit.value = credit
self.config.stored.Pyroxene.value = pyroxene self.config.stored.Pyroxene.value = pyroxene
return ap, credit, pyroxene return True
def run(self): def run(self):
self.ui_ensure(page_work, acquire_lang_checked=False) self.ui_ensure(page_work, acquire_lang_checked=False)
with self.config.multi_set(): if self._get_data():
self._get_data()
self.config.task_delay(server_update=True) self.config.task_delay(server_update=True)
else:
self.config.task_delay(minute=1)
+203
View File
@@ -0,0 +1,203 @@
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 ```
BEGIN_STORY = ButtonWrapper(
name='BEGIN_STORY',
jp=None,
en=Button(
file='./assets/en/momotalk/BEGIN_STORY.png',
area=(796, 540, 1059, 591),
search=(776, 520, 1079, 611),
color=(107, 197, 230),
button=(796, 540, 1059, 591),
),
)
CHAT_AREA = ButtonWrapper(
name='CHAT_AREA',
jp=None,
en=Button(
file='./assets/en/momotalk/CHAT_AREA.png',
area=(760, 149, 1149, 628),
search=(740, 129, 1169, 648),
color=(206, 212, 217),
button=(760, 149, 1149, 628),
),
)
CONFIRM_SKIP = ButtonWrapper(
name='CONFIRM_SKIP',
jp=None,
en=Button(
file='./assets/en/momotalk/CONFIRM_SKIP.png',
area=(674, 486, 871, 555),
search=(654, 466, 891, 575),
color=(112, 207, 241),
button=(674, 486, 871, 555),
),
)
CONFIRM_SORT = ButtonWrapper(
name='CONFIRM_SORT',
jp=None,
en=Button(
file='./assets/en/momotalk/CONFIRM_SORT.png',
area=(239, 407, 657, 439),
search=(219, 387, 677, 459),
color=(248, 249, 249),
button=(239, 407, 657, 439),
),
)
FIRST_UNREAD = ButtonWrapper(
name='FIRST_UNREAD',
jp=None,
en=Button(
file='./assets/en/momotalk/FIRST_UNREAD.png',
area=(636, 239, 661, 265),
search=(616, 219, 681, 285),
color=(251, 86, 45),
button=(636, 239, 661, 265),
),
)
MENU = ButtonWrapper(
name='MENU',
jp=None,
en=Button(
file='./assets/en/momotalk/MENU.png',
area=(1156, 15, 1251, 63),
search=(1136, 0, 1271, 83),
color=(215, 219, 222),
button=(1156, 15, 1251, 63),
),
)
MESSAGE_OFF = ButtonWrapper(
name='MESSAGE_OFF',
jp=None,
en=Button(
file='./assets/en/momotalk/MESSAGE_OFF.png',
area=(143, 273, 203, 298),
search=(123, 253, 223, 318),
color=(93, 105, 122),
button=(143, 273, 203, 298),
),
)
MESSAGE_ON = ButtonWrapper(
name='MESSAGE_ON',
jp=None,
en=Button(
file='./assets/en/momotalk/MESSAGE_ON.png',
area=(143, 271, 203, 301),
search=(123, 251, 223, 321),
color=(160, 168, 182),
button=(143, 271, 203, 301),
),
)
NOTIFICATION_BADGE = ButtonWrapper(
name='NOTIFICATION_BADGE',
jp=None,
en=Button(
file='./assets/en/momotalk/NOTIFICATION_BADGE.png',
area=(171, 109, 200, 128),
search=(151, 89, 220, 148),
color=(242, 101, 47),
button=(171, 109, 200, 128),
),
)
REPLY = ButtonWrapper(
name='REPLY',
jp=None,
en=Button(
file='./assets/en/momotalk/REPLY.png',
area=(791, 431, 855, 462),
search=(771, 411, 875, 482),
color=(198, 210, 216),
button=(791, 431, 855, 462),
),
)
SELECT_STUDENT = ButtonWrapper(
name='SELECT_STUDENT',
jp=None,
en=Button(
file='./assets/en/momotalk/SELECT_STUDENT.png',
area=(839, 369, 998, 403),
search=(819, 349, 1018, 423),
color=(241, 242, 242),
button=(839, 369, 998, 403),
),
)
SKIP = ButtonWrapper(
name='SKIP',
jp=None,
en=Button(
file='./assets/en/momotalk/SKIP.png',
area=(1192, 103, 1229, 141),
search=(1172, 83, 1249, 161),
color=(108, 125, 145),
button=(1192, 103, 1229, 141),
),
)
SORT_OFF = ButtonWrapper(
name='SORT_OFF',
jp=None,
en=Button(
file='./assets/en/momotalk/SORT_OFF.png',
area=(591, 158, 662, 199),
search=(571, 138, 682, 219),
color=(235, 237, 238),
button=(591, 158, 662, 199),
),
)
SORT_ON = ButtonWrapper(
name='SORT_ON',
jp=None,
en=Button(
file='./assets/en/momotalk/SORT_ON.png',
area=(594, 159, 658, 196),
search=(574, 139, 678, 216),
color=(233, 235, 236),
button=(594, 159, 658, 196),
),
)
STORY = ButtonWrapper(
name='STORY',
jp=None,
en=Button(
file='./assets/en/momotalk/STORY.png',
area=(790, 529, 979, 557),
search=(770, 509, 999, 577),
color=(220, 208, 214),
button=(790, 529, 979, 557),
),
)
UNREAD = ButtonWrapper(
name='UNREAD',
jp=None,
en=Button(
file='./assets/en/momotalk/UNREAD.png',
area=(454, 160, 568, 193),
search=(434, 140, 588, 213),
color=(241, 242, 243),
button=(454, 160, 568, 193),
),
)
UNREAD_OFF = ButtonWrapper(
name='UNREAD_OFF',
jp=None,
en=Button(
file='./assets/en/momotalk/UNREAD_OFF.png',
area=(456, 273, 658, 316),
search=(436, 253, 678, 336),
color=(252, 252, 251),
button=(456, 273, 658, 316),
),
)
UNREAD_ON = ButtonWrapper(
name='UNREAD_ON',
jp=None,
en=Button(
file='./assets/en/momotalk/UNREAD_ON.png',
area=(456, 272, 658, 314),
search=(436, 252, 678, 334),
color=(245, 120, 144),
button=(456, 272, 658, 314),
),
)
+61
View File
@@ -0,0 +1,61 @@
from enum import Enum
from module.base.timer import Timer
from module.logger import logger
from tasks.tactical_challenge.assets.assets_tactical_challenge import *
from tasks.momotalk.ui import MomoTalkUI
class MomoTalkStatus(Enum):
OPEN = 0
SORT = 1
CHECK = 2
CHAT = 3
STORY = 4
FINISHED = -1
class MomoTalk(MomoTalkUI):
def handle_momotalk(self, status):
match status:
case MomoTalkStatus.OPEN:
if self.open_momotalk():
return MomoTalkStatus.SORT
return MomoTalkStatus.FINISHED
case MomoTalkStatus.SORT:
if self.sort_messages():
return MomoTalkStatus.CHECK
case MomoTalkStatus.CHECK:
if self.check_first_student():
return MomoTalkStatus.CHAT
return MomoTalkStatus.FINISHED
case MomoTalkStatus.CHAT:
if self.chat():
return MomoTalkStatus.STORY
return MomoTalkStatus.OPEN
case MomoTalkStatus.STORY:
if self.skip_story():
return MomoTalkStatus.OPEN
case MomoTalkStatus.FINISHED:
return status
case _:
logger.warning(f'Invalid status: {status}')
return status
def run(self):
action_timer = Timer(0.5, 1)
status = MomoTalkStatus.OPEN
while 1:
self.device.screenshot()
if self.ui_additional():
continue
if action_timer.reached_and_reset():
logger.attr('Status', status)
status = self.handle_momotalk(status)
if status == MomoTalkStatus.FINISHED:
break
self.config.task_delay(server_update=True)
+189
View File
@@ -0,0 +1,189 @@
from module.base.timer import Timer
from module.base.base import ModuleBase
from module.logger import logger
from module.ui.switch import Switch
from module.base.utils import point_in_area, area_size
from tasks.base.ui import UI
from tasks.base.page import page_main, page_momo_talk
from tasks.momotalk.assets.assets_momotalk import *
import cv2
import numpy as np
"""None of the switches works"""
SWITCH_MESSAGE = Switch("Message_switch")
SWITCH_MESSAGE.add_state("on", MESSAGE_ON)
SWITCH_MESSAGE.add_state("off", MESSAGE_OFF)
SWITCH_UNREAD = Switch("Unread_switch")
SWITCH_UNREAD.add_state("on", UNREAD_ON)
SWITCH_UNREAD.add_state("off", UNREAD_OFF)
SWITCH_SORT = Switch("Sort_switch")
SWITCH_SORT.add_state("on", SORT_ON)
SWITCH_SORT.add_state("off", SORT_OFF)
"""Required for template matching as reply and story
button can be found in different locations"""
REPLY_TEMPLATE = REPLY.matched_button.image
STORY_TEMPLATE = STORY.matched_button.image
class MomoTalkUI(UI):
def __init__(self, config, device):
super().__init__(config, device)
self.swipe_vector_range = (0.65, 0.85)
self.list = CHAT_AREA
def swipe_page(self, direction: str, main: ModuleBase, vector_range=None, reverse=False):
"""
Args:
direction: up, down
main:
vector_range (tuple[float, float]):
reverse (bool):
"""
if vector_range is None:
vector_range = self.swipe_vector_range
vector = np.random.uniform(*vector_range)
width, height = area_size(self.list.button)
if direction == 'up':
vector = (0, vector * height)
elif direction == 'down':
vector = (0, -vector * height)
else:
logger.warning(f'Unknown swipe direction: {direction}')
return
if reverse:
vector = (-vector[0], -vector[1])
main.device.swipe_vector(vector, self.list.button)
def select_then_check(self, dest_enter: ButtonWrapper, dest_check: ButtonWrapper, similarity=0.85):
timer = Timer(5, 10).start()
while 1:
self.device.screenshot()
self.appear_then_click(dest_enter, interval=1, similarity=similarity)
if self.appear(dest_check, similarity=similarity):
return True
if timer.reached():
return False
def select_then_disappear(self, dest_enter: ButtonWrapper, dest_check: ButtonWrapper, force_select=False):
timer = Timer(5, 10).start()
while 1:
self.device.screenshot()
if force_select or self.appear(dest_enter):
self.click_with_interval(dest_enter, interval=1)
if not self.appear(dest_check):
return True
if timer.reached():
return False
def set_switch(self, switch):
"""
Set switch to on. However, unsure why is inaccurate in momotalk.
Returns:
True if switch is set, False if switch not found
"""
if not switch.appear(main=self):
logger.info(f'{switch.name} not found')
return False
switch.set('on', main=self)
return True
def click_all(self, template, x_add=0, y_add=0):
"""
Find the all the locations of the template adding an offset if specified and click them.
TODO: filter coords that are not inside the chat area as otherwise it will close momotalk.
If after filter, no coords then swipe.
"""
click_coords = self.device.click_methods.get(self.config.Emulator_ControlMethod, self.device.click_adb)
image = self.device.screenshot()
result = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
threshold = 0.8
locations = np.where(result >= threshold)
seen = set()
for pt in zip(*locations[::-1]):
center_pt = (int(pt[0] + template.shape[1] / 2 + x_add), int(pt[1] + template.shape[0] / 2 + y_add))
seen.add(center_pt)
if seen:
seen = filter(lambda x: point_in_area(x, CHAT_AREA.area), seen)
[click_coords(coords[0], coords[1]) for coords in seen]
self.swipe_page("down", self)
return True
return False
def open_momotalk(self):
"""
Go to main and check if there are any red notifications in momotalk.
If yes, open it otherwise it means no student available for interaction.
"""
self.ui_ensure(page_main)
if self.match_color(NOTIFICATION_BADGE, threshold=80):
self.ui_ensure(page_momo_talk)
while not self.select_then_check(MESSAGE_OFF, MESSAGE_ON):
pass
return True
logger.warn("No students available for interaction")
return False
def sort_messages(self):
"""
Switch from newest to unread and sort the messages in descending order
"""
logger.info("Sorting messages...")
steps = [UNREAD, CONFIRM_SORT, UNREAD_OFF, UNREAD_ON]
for i in range(len(steps)-2):
self.select_then_check(steps[i], steps[i+1], similarity=0.95)
return not self.appear(CONFIRM_SORT) and self.appear(UNREAD) and self.appear(SORT_ON)
def check_first_student(self):
"""
If the first student has a red notification return True and start chat.
Otherwise it means no students are available for interaction.
"""
if self.match_color(FIRST_UNREAD, threshold=80) and self.select_then_disappear(FIRST_UNREAD, SELECT_STUDENT, force_select=True):
return True
logger.warn("No students available for interaction")
return False
def chat(self):
"""
Waits for the chat area to be stable and then
check if a reply or story button is found and click them.
If the begin story button is found skip story.
"""
logger.info("Chatting with student...")
stability_counter = 0
while 1:
self.wait_until_stable(CHAT_AREA, timer=Timer(10, 10))
if self.appear(BEGIN_STORY):
logger.info("Begin Story detected")
return True
if self.click_all(REPLY_TEMPLATE, y_add=62):
logger.info("Clicked on reply")
stability_counter = 0
continue
if self.click_all(STORY_TEMPLATE, y_add=62):
logger.info("Clicked on story")
stability_counter = 0
continue
logger.info("No new message detected")
stability_counter += 1
if stability_counter > 3:
return False
def skip_story(self):
"""
Skip story by executing a series of steps. Returns True if the confirm skip
button is clicked and disappears
"""
logger.info("Attempting to skip story...")
steps = [BEGIN_STORY, MENU, SKIP]
for step in steps:
self.appear_then_click(step)
if self.appear_then_click(CONFIRM_SKIP) and not self.appear(CONFIRM_SKIP, interval=5):
logger.info("Skipped story successfully")
return True
return False
+8 -8
View File
@@ -75,17 +75,17 @@ OCR_TICKET = ButtonWrapper(
name='OCR_TICKET', name='OCR_TICKET',
jp=Button( jp=Button(
file='./assets/jp/scrimmage/OCR_TICKET.png', file='./assets/jp/scrimmage/OCR_TICKET.png',
area=(195, 85, 235, 113), area=(194, 87, 258, 112),
search=(175, 65, 255, 133), search=(174, 67, 278, 132),
color=(206, 211, 215), color=(207, 213, 218),
button=(195, 85, 235, 113), button=(194, 87, 258, 112),
), ),
en=Button( en=Button(
file='./assets/en/scrimmage/OCR_TICKET.png', file='./assets/en/scrimmage/OCR_TICKET.png',
area=(227, 84, 265, 115), area=(229, 88, 289, 112),
search=(207, 64, 285, 135), search=(209, 68, 309, 132),
color=(207, 212, 216), color=(199, 206, 212),
button=(227, 84, 265, 115), button=(229, 88, 289, 112),
), ),
) )
SELECT_GEHENNA = ButtonWrapper( SELECT_GEHENNA = ButtonWrapper(
+12 -27
View File
@@ -7,6 +7,7 @@ from tasks.base.assets.assets_base_page import BACK
from tasks.base.page import page_school_exchange from tasks.base.page import page_school_exchange
from tasks.scrimmage.assets.assets_scrimmage import * from tasks.scrimmage.assets.assets_scrimmage import *
from tasks.scrimmage.ui import ScrimmageUI from tasks.scrimmage.ui import ScrimmageUI
from tasks.stage.ap import AP
class ScrimmageStatus(Enum): class ScrimmageStatus(Enum):
@@ -18,15 +19,20 @@ class ScrimmageStatus(Enum):
FINISH = 5 FINISH = 5
class Scrimmage(ScrimmageUI): class Scrimmage(ScrimmageUI, AP):
_stage_ap = [10, 15, 15, 15]
@property
def stage_ap(self):
return self._stage_ap
@property @property
def scrimmage_info(self): def scrimmage_info(self):
scrimmage = (SELECT_TRINITY, SELECT_GEHENNA, SELECT_MILLENNIUM) scrimmage = (SELECT_TRINITY, SELECT_GEHENNA, SELECT_MILLENNIUM)
check = (CHECK_TRINITY, CHECK_GEHENNA, CHECK_MILLENNIUM) check = (CHECK_TRINITY, CHECK_GEHENNA, CHECK_MILLENNIUM)
stage = (self.config.Trinity_Stage, self.config.Gehenna_Stage, self.config.Millennium_Stage) 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) 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(scrimmage, check, stage, count)
info = zip(scrimmage, check, stage, count, ap)
return filter(lambda x: x[3] > 0, info) return filter(lambda x: x[3] > 0, info)
@property @property
@@ -42,6 +48,7 @@ class Scrimmage(ScrimmageUI):
if action == 'stop': if action == 'stop':
raise RequestHumanTakeover raise RequestHumanTakeover
elif action == 'skip': elif action == 'skip':
with self.config.multi_set():
self.config.task_delay(server_update=True) self.config.task_delay(server_update=True)
self.config.task_stop() self.config.task_stop()
@@ -49,10 +56,6 @@ class Scrimmage(ScrimmageUI):
def is_ticket_enough(self) -> bool: def is_ticket_enough(self) -> bool:
return self.current_ticket >= self.current_count return self.current_ticket >= self.current_count
@property
def is_ap_enough(self) -> bool:
return self.current_ap >= self.current_task_ap
@property @property
def current_scrimmage(self): def current_scrimmage(self):
return self.task[0][:2] return self.task[0][:2]
@@ -65,25 +68,10 @@ class Scrimmage(ScrimmageUI):
def current_count(self): def current_count(self):
return self.task[0][3] return self.task[0][3]
@property
def current_task_ap(self):
return self.task[0][4] * self.current_count
@property @property
def current_ticket(self): def current_ticket(self):
return self.config.stored.ScrimmageTicket.value 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): def handle_scrimmage(self, status):
match status: match status:
case ScrimmageStatus.OCR: case ScrimmageStatus.OCR:
@@ -95,19 +83,16 @@ class Scrimmage(ScrimmageUI):
if not self.is_ticket_enough: if not self.is_ticket_enough:
logger.warning('Scrimmage ticket not enough') logger.warning('Scrimmage ticket not enough')
self.error_handler() self.error_handler()
if not self.is_ap_enough:
logger.warning('AP not enough')
self.error_handler()
if self.select_scrimmage(*self.current_scrimmage): if self.select_scrimmage(*self.current_scrimmage):
return ScrimmageStatus.ENTER return ScrimmageStatus.ENTER
case ScrimmageStatus.ENTER: case ScrimmageStatus.ENTER:
if self.enter_stage(self.current_stage): if self.enter_stage(self.current_stage) and self.is_ap_enough(self.current_count, self.current_stage):
return ScrimmageStatus.SWEEP return ScrimmageStatus.SWEEP
else: else:
self.error_handler() self.error_handler()
case ScrimmageStatus.SWEEP: case ScrimmageStatus.SWEEP:
if self.do_sweep(self.current_count): if self.do_sweep(self.current_count):
self.update_ap() self.update_ap(self.current_count, self.current_stage)
self.task.pop(0) self.task.pop(0)
return ScrimmageStatus.END return ScrimmageStatus.END
return ScrimmageStatus.ENTER return ScrimmageStatus.ENTER
+6 -1
View File
@@ -3,6 +3,7 @@ from module.logger import logger
from module.ocr.ocr import DigitCounter from module.ocr.ocr import DigitCounter
from tasks.base.ui import UI from tasks.base.ui import UI
from tasks.scrimmage.assets.assets_scrimmage import * from tasks.scrimmage.assets.assets_scrimmage import *
from tasks.stage.ap import AP
from tasks.stage.list import StageList from tasks.stage.list import StageList
from tasks.stage.sweep import StageSweep from tasks.stage.sweep import StageSweep
@@ -10,7 +11,7 @@ SCRIMMAGE_LIST = StageList('ScrimmageList')
SCRIMMAGE_SWEEP = StageSweep('ScrimmageSweep', 6) SCRIMMAGE_SWEEP = StageSweep('ScrimmageSweep', 6)
class ScrimmageUI(UI): class ScrimmageUI(UI, AP):
def select_scrimmage(self, dest_enter: ButtonWrapper, dest_check: ButtonWrapper): def select_scrimmage(self, dest_enter: ButtonWrapper, dest_check: ButtonWrapper):
timer = Timer(5, 10).start() timer = Timer(5, 10).start()
while 1: while 1:
@@ -22,6 +23,10 @@ class ScrimmageUI(UI):
return False return False
def enter_stage(self, index: int) -> bool: def enter_stage(self, index: int) -> bool:
if not index:
index = SCRIMMAGE_LIST.insight_max_sweepable_index(self)
# set AP stage
self.set_stage(index)
if SCRIMMAGE_LIST.select_index_enter(self, index, insight=False): if SCRIMMAGE_LIST.select_index_enter(self, index, insight=False):
return True return True
return False return False
+115
View File
@@ -0,0 +1,115 @@
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 ```
CONFIRM_PURCHASE = ButtonWrapper(
name='CONFIRM_PURCHASE',
jp=None,
en=Button(
file='./assets/en/shop/CONFIRM_PURCHASE.png',
area=(467, 231, 807, 309),
search=(447, 211, 827, 329),
color=(217, 218, 219),
button=(668, 458, 865, 514),
),
)
CONFIRM_REFRESH = ButtonWrapper(
name='CONFIRM_REFRESH',
jp=None,
en=Button(
file='./assets/en/shop/CONFIRM_REFRESH.png',
area=(474, 271, 806, 306),
search=(454, 251, 826, 326),
color=(202, 203, 204),
button=(675, 434, 863, 500),
),
)
ITEM_LIST = ButtonWrapper(
name='ITEM_LIST',
jp=None,
en=Button(
file='./assets/en/shop/ITEM_LIST.png',
area=(625, 127, 1244, 610),
search=(605, 107, 1264, 630),
color=(193, 206, 213),
button=(625, 127, 1244, 610),
),
)
NORMAL_OFF = ButtonWrapper(
name='NORMAL_OFF',
jp=None,
en=Button(
file='./assets/en/shop/NORMAL_OFF.png',
area=(4, 111, 213, 167),
search=(0, 91, 233, 187),
color=(248, 248, 245),
button=(4, 111, 213, 167),
),
)
NORMAL_ON = ButtonWrapper(
name='NORMAL_ON',
jp=None,
en=Button(
file='./assets/en/shop/NORMAL_ON.png',
area=(4, 109, 212, 170),
search=(0, 89, 232, 190),
color=(57, 78, 96),
button=(4, 109, 212, 170),
),
)
OCR_REFRESH = ButtonWrapper(
name='OCR_REFRESH',
jp=None,
en=Button(
file='./assets/en/shop/OCR_REFRESH.png',
area=(712, 302, 762, 344),
search=(692, 282, 782, 364),
color=(225, 225, 226),
button=(712, 302, 762, 344),
),
)
PURCHASE = ButtonWrapper(
name='PURCHASE',
jp=None,
en=Button(
file='./assets/en/shop/PURCHASE.png',
area=(1102, 640, 1227, 684),
search=(1082, 620, 1247, 704),
color=(226, 206, 65),
button=(1102, 640, 1227, 684),
),
)
REFRESH = ButtonWrapper(
name='REFRESH',
jp=None,
en=Button(
file='./assets/en/shop/REFRESH.png',
area=(1098, 643, 1223, 682),
search=(1078, 623, 1243, 702),
color=(231, 234, 237),
button=(1098, 643, 1223, 682),
),
)
TC_OFF = ButtonWrapper(
name='TC_OFF',
jp=None,
en=Button(
file='./assets/en/shop/TC_OFF.png',
area=(2, 503, 209, 558),
search=(0, 483, 229, 578),
color=(239, 242, 244),
button=(2, 503, 209, 558),
),
)
TC_ON = ButtonWrapper(
name='TC_ON',
jp=None,
en=Button(
file='./assets/en/shop/TC_ON.png',
area=(3, 493, 208, 548),
search=(0, 473, 228, 568),
color=(62, 84, 99),
button=(3, 493, 208, 548),
),
)
+118
View File
@@ -0,0 +1,118 @@
from enum import Flag
from module.base.timer import Timer
from module.exception import RequestHumanTakeover
from module.logger import logger
from module.ui.switch import Switch
from tasks.base.assets.assets_base_page import BACK
from tasks.base.page import page_main, page_shop
from tasks.shop.assets.assets_shop import *
from tasks.shop.ui import ShopUI
class ShopStatus(Flag):
SELECT_SHOP = 0
SELECT_ITEMS = 1
PURCHASE = 2
REFRESH = 3
END = 4
FINISH = -1
class Shop(ShopUI):
@property
def shop_info(self):
"""Similiar to bounty_info and scrimmage_info.
Returns a list with elements the select button, check button, how many times do make purchases and the list of items"""
info = []
if self.config.NormalShop_Enable:
normal_config = self.config.cross_get(["Shop", "NormalShop"])
normal_items = [num for num in range(1, 21) if normal_config[str(num)]]
if normal_items:
SWITCH_NORMAL = Switch('NormalShop_Switch')
SWITCH_NORMAL.add_state('on', NORMAL_ON)
SWITCH_NORMAL.add_state('off', NORMAL_OFF)
info.append([SWITCH_NORMAL, self.config.NormalShop_Purchases, normal_items])
if self.config.TacticalChallengeShop_Enable:
tc_config = self.config.cross_get(["Shop", "TacticalChallengeShop"])
tc_items = [num for num in range(1, 16) if tc_config[str(num)]]
if tc_items:
SWITCH_TC = Switch('TacticalChallengeShop_Switch')
SWITCH_TC.add_state('on', TC_ON)
SWITCH_TC.add_state('off', TC_OFF)
info.append([SWITCH_TC, self.config.TacticalChallengeShop_Purchases, tc_items])
return info
@property
def valid_task(self) -> list:
task = self.shop_info
if not task:
logger.warning('Shop enabled but no task set')
return task
@property
def current_shop(self):
return self.task[0][0]
@property
def current_purchase_count(self):
return self.task[0][1]
@property
def current_item_list(self):
return self.task[0][2]
def handle_shop(self, status):
match status:
case ShopStatus.SELECT_SHOP:
if not self.task:
return ShopStatus.FINISH
if self.select_shop(self.current_shop):
self.reset_swipe_flags()
return ShopStatus.SELECT_ITEMS
case ShopStatus.SELECT_ITEMS:
self.select_items(self.current_item_list)
return ShopStatus.PURCHASE
case ShopStatus.PURCHASE:
if self.make_purchase():
return ShopStatus.REFRESH
return ShopStatus.END
case ShopStatus.REFRESH:
if self.refresh_shop(self.current_purchase_count):
return ShopStatus.SELECT_SHOP
return ShopStatus.END
case ShopStatus.END:
if self.appear(page_shop.check_button):
self.task.pop(0)
return ShopStatus.SELECT_SHOP
self.click_with_interval(BACK, interval=2)
case ShopStatus.FINISH:
return status
case _:
logger.warning(f'Invalid status: {status}')
return status
def run(self):
"""Reset the shop and items position by going main and then shop"""
self.ui_ensure(page_main)
self.ui_ensure(page_shop)
self.task = self.valid_task
action_timer = Timer(0.5, 1)
status = ShopStatus.SELECT_SHOP
while 1:
self.device.screenshot()
if self.ui_additional():
continue
if action_timer.reached_and_reset():
logger.attr('Status', status)
status = self.handle_shop(status)
if status == ShopStatus.FINISH:
break
self.config.task_delay(server_update=True)
+138
View File
@@ -0,0 +1,138 @@
import numpy as np
from module.base.timer import Timer
from module.base.base import ModuleBase
from module.base.utils import area_size
from module.logger import logger
from module.ocr.ocr import DigitCounter
from tasks.base.ui import UI
from tasks.shop.assets.assets_shop import *
ITEM_POSITIONS = {
1: (650, 200), 2: (805, 200), 3: (960, 200), 4: (1110, 200),
5: (650, 460), 6: (805, 460), 7: (960, 460), 8: (1110, 460),
9: (650, 200), 10: (805, 200), 11: (960, 200), 12: (1110, 200),
13: (650, 460), 14: (805, 460), 15: (960, 460), 16: (1110, 460),
17: (650, 460), 18: (805, 460), 19: (960, 460), 20: (1110, 460),
}
class ShopUI(UI):
def __init__(self, config, device):
super().__init__(config, device)
self.click_coords = self.device.click_methods.get(self.config.Emulator_ControlMethod, self.device.click_adb)
self.swipe_vector_range = (1.10, 1.15)
self.swipe_flags = {8:False, 16: False}
self.list = ITEM_LIST
def swipe_page(self, direction: str, main: ModuleBase, vector_range=None, reverse=False):
"""
Args:
direction: up, down
main:
vector_range (tuple[float, float]):
reverse (bool):
"""
if vector_range is None:
vector_range = self.swipe_vector_range
vector = np.random.uniform(*vector_range)
width, height = area_size(self.list.button)
if direction == 'up':
vector = (0, vector * height)
elif direction == 'down':
vector = (0, -vector * height)
else:
logger.warning(f'Unknown swipe direction: {direction}')
return
if reverse:
vector = (-vector[0], -vector[1])
main.device.swipe_vector(vector, self.list.button)
def select_then_check(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 select_shop(self, shop_switch):
"""
Set skip switch to on
Returns:
True if switch is set, False if switch not found
"""
if not shop_switch.appear(main=self):
logger.info(f'{shop_switch.name} not found')
return False
shop_switch.set('on', main=self)
return True
def select_items(self, item_list):
"""
Select items in the item list checking if swipe is required each time.
However, swipes are inaccurate and clicks too fast.
"""
timer = Timer(1).start()
for item in item_list:
if self.should_swipe(item):
self.swipe_page('down', self)
self.wait_until_stable(
self.list.button,
timer=Timer(3, 0),
timeout=Timer(1.5, 5)
)
while not timer.reached_and_reset():
pass
self.click_coords(*ITEM_POSITIONS[item])
def should_swipe(self, item):
"""
Return True based on two checkpoints:
one at 8 and the other at 16.
Only once for each checkpoint.
"""
if (8 < item < 16) and not self.swipe_flags[8]:
self.swipe_flags[8] = True
return True
elif item > 16 and not self.swipe_flags[16]:
self.swipe_flags[16] = True
return True
return False
def reset_swipe_flags(self):
self.swipe_flags[8], self.swipe_flags[16] = False, False
def make_purchase(self):
if self.select_then_check(PURCHASE, CONFIRM_PURCHASE) and self.appear_then_click(CONFIRM_PURCHASE):
return True
logger.warning("No items were selected. Unable to purchase.")
return False
def refresh_shop(self, need_count):
"""
Refresh the shop
"""
refresh_count = self.get_refresh_count()
if refresh_count:
purchased_count = 4 - refresh_count
if need_count > purchased_count and self.appear_then_click(CONFIRM_REFRESH):
logger.info("Refreshed the shop")
return True
return False
def get_refresh_count(self):
if not self.select_then_check(REFRESH, CONFIRM_REFRESH):
logger.warning('OCR failed due to invalid page')
return False
count, _, total = DigitCounter(OCR_REFRESH).ocr_single_line(self.device.image)
if total == 0:
logger.warning('Invalid count')
return False
return count
+48
View File
@@ -0,0 +1,48 @@
from module.base.base import ModuleBase
from module.logger import logger
class AP(ModuleBase):
_stage = 0
@property
def stage(self) -> int:
return self._stage
@classmethod
def set_stage(cls, stage: int):
cls._stage = stage
@property
def stage_ap(self) -> int | list:
"""
To be redefined in subclass.
Returns:
Task ap
"""
return 0
@property
def current_ap(self):
return self.config.stored.AP.value
def update_ap(self, count: int, stage: int = None):
ap = self.config.stored.AP
ap_old = ap.value
ap_new = ap_old - self.stage_ap_cost(stage) * count
ap.set(ap_new, ap.total)
logger.info(f'Set AP: {ap_old} -> {ap_new}')
def stage_ap_cost(self, stage: int = None) -> int:
if isinstance(self.stage_ap, int):
return self.stage_ap
if isinstance(self.stage_ap, list):
if not stage:
stage = self.stage if self.stage else len(self.stage_ap)
return self.stage_ap[stage - 1]
def is_ap_enough(self, count: int, stage: int = None) -> bool:
cost = self.stage_ap_cost(stage) * count
logger.info(f'Check AP: {self.current_ap} / {cost}')
return self.current_ap >= cost
+97 -51
View File
@@ -1,17 +1,17 @@
import cv2 import re
import numpy as np import numpy as np
from module.base.base import ModuleBase from module.base.base import ModuleBase
from module.base.button import ClickButton, match_template
from module.base.timer import Timer from module.base.timer import Timer
from module.base.utils import area_pad, area_size, area_offset, random_rectangle_vector_opted from module.base.utils import area_pad, area_size, area_offset
from module.logger import logger from module.logger import logger
from module.ocr.ocr import Ocr from module.ocr.ocr import Ocr
from tasks.stage.assets.assets_stage_list import * from tasks.stage.assets.assets_stage_list import *
class StageList: class StageList:
drag_vector_range = (0.65, 0.85) swipe_vector_range = (0.65, 0.85)
def __init__( def __init__(
self, self,
@@ -21,7 +21,7 @@ class StageList:
button_item: ButtonWrapper = None, button_item: ButtonWrapper = None,
button_enter: ButtonWrapper = None, button_enter: ButtonWrapper = None,
button_stars: ButtonWrapper = None, button_stars: ButtonWrapper = None,
drag_direction: str = "down" swipe_direction: str = "down"
): ):
self.name = name self.name = name
self.stage = button_list if button_list else STAGE_LIST self.stage = button_list if button_list else STAGE_LIST
@@ -29,11 +29,11 @@ class StageList:
self.stage_item = (button_item if button_item else STAGE_ITEM).button self.stage_item = (button_item if button_item else STAGE_ITEM).button
self.enter = button_enter if button_enter else STAGE_ENTER self.enter = button_enter if button_enter else STAGE_ENTER
self.sweepable = button_stars if button_stars else STAGE_STARS self.sweepable = button_stars if button_stars else STAGE_STARS
self.drag_direction = drag_direction self.swipe_direction = swipe_direction
self.current_index_min = 1 self.current_index_min = 1
self.current_index_max = 1 self.current_index_max = 1
self.current_indexes = [] self.current_indexes: list[tuple[str, tuple]] = []
def __str__(self): def __str__(self):
return f'StageList({self.name})' return f'StageList({self.name})'
@@ -48,11 +48,14 @@ class StageList:
@property @property
def _indexes(self) -> list[int]: def _indexes(self) -> list[int]:
return list(map(lambda x: int(x.ocr_text), self.current_indexes)) return [int(x[0]) for x in self.current_indexes]
def load_stage_indexes(self, main: ModuleBase): def load_stage_indexes(self, main: ModuleBase):
self.current_indexes = list( self.current_indexes = list(
filter(lambda x: x.ocr_text.isdigit(), self.index_ocr.detect_and_ocr(main.device.image)) filter(
lambda x: re.match(r'^\d{1,2}-?\d?$', x[0]) and x[0] != '00',
map(lambda x: (x.ocr_text, x.box), self.index_ocr.detect_and_ocr(main.device.image))
)
) )
if not self.current_indexes: if not self.current_indexes:
logger.warning(f'No valid index in {self.index_ocr.name}') logger.warning(f'No valid index in {self.index_ocr.name}')
@@ -63,7 +66,7 @@ class StageList:
self.current_index_max = max(indexes) self.current_index_max = max(indexes)
logger.attr(self.index_ocr.name, f'Index range: {self.current_index_min} - {self.current_index_max}') logger.attr(self.index_ocr.name, f'Index range: {self.current_index_min} - {self.current_index_max}')
def drag_page(self, direction: str, main: ModuleBase, vector_range=None, reverse=False): def swipe_page(self, direction: str, main: ModuleBase, vector_range=None, reverse=False):
""" """
Args: Args:
direction: up, down direction: up, down
@@ -72,7 +75,7 @@ class StageList:
reverse (bool): reverse (bool):
""" """
if vector_range is None: if vector_range is None:
vector_range = self.drag_vector_range vector_range = self.swipe_vector_range
vector = np.random.uniform(*vector_range) vector = np.random.uniform(*vector_range)
width, height = area_size(self.stage.button) width, height = area_size(self.stage.button)
if direction == 'up': if direction == 'up':
@@ -80,13 +83,12 @@ class StageList:
elif direction == 'down': elif direction == 'down':
vector = (0, -vector * height) vector = (0, -vector * height)
else: else:
logger.warning(f'Unknown drag direction: {direction}') logger.warning(f'Unknown swipe direction: {direction}')
return return
if reverse: if reverse:
vector = (-vector[0], -vector[1]) vector = (-vector[0], -vector[1])
p1, p2 = random_rectangle_vector_opted(vector, box=self.stage.button) main.device.swipe_vector(vector, self.stage.button, name=f'{self.name}_SWIPE')
main.device.drag(p1, p2, name=f'{self.name}_DRAG')
def insight_index(self, index: int, main: ModuleBase, skip_first_screenshot=True) -> bool: def insight_index(self, index: int, main: ModuleBase, skip_first_screenshot=True) -> bool:
""" """
@@ -110,18 +112,7 @@ class StageList:
self.load_stage_indexes(main=main) self.load_stage_indexes(main=main)
if self.current_index_min <= index <= self.current_index_max: if self.current_index_min <= index <= self.current_index_max:
break return True
if index < self.current_index_min:
self.drag_page(self.drag_direction, main, reverse=True)
elif index > self.current_index_max:
self.drag_page(self.drag_direction, main)
main.wait_until_stable(
self.stage.button,
timer=Timer(0, 0),
timeout=Timer(1.5, 5)
)
indexes = self._indexes indexes = self._indexes
if indexes and last_indexes == set(indexes): if indexes and last_indexes == set(indexes):
@@ -129,22 +120,82 @@ class StageList:
return False return False
last_indexes = set(indexes) last_indexes = set(indexes)
return True if index < self.current_index_min:
self.swipe_page(self.swipe_direction, main, reverse=True)
elif index > self.current_index_max:
self.swipe_page(self.swipe_direction, main)
@staticmethod main.wait_until_stable(
def _match_clickable_points(image, template, threshold=0.85): self.stage.button,
image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) timer=Timer(0, 0),
template = cv2.cvtColor(template, cv2.COLOR_RGB2GRAY) timeout=Timer(1.5, 5)
)
res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED) def insight_max_sweepable_index(self, main: ModuleBase, skip_first_screenshot=True) -> int:
loc = np.where(res >= threshold) """
return [point for point in zip(*loc[::-1])] Args:
main:
skip_first_screenshot:
def is_sweepable(self, image, main: ModuleBase, skip_first_screenshot=True) -> bool: Returns:
if not skip_first_screenshot: Index of max sweepable stage
"""
logger.info('Insight sweepable index')
max_sweepable_index = 0
last_max_sweepable_index = 0
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
main.device.screenshot() main.device.screenshot()
return match_template(image, self.sweepable.matched_button.image) self.load_stage_indexes(main=main)
sweepable_index = next(
filter(
lambda x: not self.is_sweepable(main, self.search_box(x[-1][:2])),
self.current_indexes
), None
)
# all sweepable
if sweepable_index is None:
logger.info('All sweepable')
max_sweepable_index = self.current_index_max
self.swipe_page(self.swipe_direction, main)
if max_sweepable_index == last_max_sweepable_index:
logger.info(f'Max sweepable index: {max_sweepable_index}')
return max_sweepable_index
last_max_sweepable_index = max_sweepable_index
# all not sweepable
elif int(sweepable_index[0]) == self.current_index_min:
logger.info('All not sweepable')
if int(sweepable_index[0]) == 1:
logger.warning('No sweepable index')
return 0
self.swipe_page(self.swipe_direction, main, reverse=True)
else:
logger.info(f'Sweepable index: {int(sweepable_index[0]) - 1}')
return int(sweepable_index[0]) - 1
main.wait_until_stable(
self.stage.button,
timer=Timer(0, 0),
timeout=Timer(1.5, 5)
)
def is_sweepable(self, main: ModuleBase, search_box) -> bool:
self.sweepable.load_search(search_box)
return main.appear(self.sweepable, similarity=0.8)
def search_box(
self,
index_cord: tuple[int, int],
padding: tuple[int, int] = (-20, -15)
) -> tuple[int, int, int, int]:
stage_item_box = area_pad((*padding, *area_size(self.stage_item)))
return area_offset(stage_item_box, index_cord)
def select_index_enter( def select_index_enter(
self, self,
@@ -152,7 +203,7 @@ class StageList:
index: int, index: int,
insight: bool = True, insight: bool = True,
sweepable: bool = True, sweepable: bool = True,
offset: tuple[int, int] = (-20, -15), padding: tuple[int, int] = (-20, -15),
skip_first_screenshot: bool = True, skip_first_screenshot: bool = True,
interval: int = 1.5 interval: int = 1.5
) -> bool: ) -> bool:
@@ -170,35 +221,30 @@ class StageList:
main.device.screenshot() main.device.screenshot()
# load index if not insight # load index if not insight
if load_index_interval.reached_and_reset() and not insight: if load_index_interval.reached_and_reset():
self.load_stage_indexes(main=main) self.load_stage_indexes(main=main)
# find box of index # find box of index
index_box = next(filter(lambda x: int(x.ocr_text) == index, self.current_indexes), None) index_box = next(filter(lambda x: int(x[0]) == index, self.current_indexes), None)
if index_box is None: if index_box is None:
logger.warning(f'No index {index} in {self.index_ocr.name}') logger.warning(f'No index {index} in {self.index_ocr.name}')
continue continue
stage_item_box = area_pad((*offset, *area_size(self.stage_item))) search_box = self.search_box(index_box[-1][:2], padding)
search_box = area_offset(stage_item_box, index_box.box[:2]) if sweepable and not self.is_sweepable(main, search_box):
search_image = main.image_crop(search_box)
if sweepable and not self.is_sweepable(search_image, main, skip_first_screenshot):
logger.warning(f'Index {index} is not sweepable') logger.warning(f'Index {index} is not sweepable')
return False return False
points = self._match_clickable_points(search_image, self.enter.matched_button.image) self.enter.load_search(search_box)
click_button = self.enter.match_multi_template(main.device.image)
if not points: if not click_button:
logger.warning(f'No clickable {self.enter.name}') logger.warning(f'No clickable {self.enter.name}')
continue 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)
if click_interval.reached_and_reset(): if click_interval.reached_and_reset():
main.device.click(click_button) main.device.click(click_button[0])
return True return True
if timeout.reached(): if timeout.reached():
+1 -1
View File
@@ -204,7 +204,7 @@ class StageSweep:
if not self.set_mode(mode, num): if not self.set_mode(mode, num):
return False return False
timer = Timer(0.5, 1) timer = Timer(0.5, 1)
timer_stable = Timer(0.5, 1).start() timer_stable = Timer(1, 1).start()
status = SweepStatus.SELECT status = SweepStatus.SELECT
while 1: while 1:
if not timer_stable.reached(): if not timer_stable.reached():
@@ -41,6 +41,8 @@ class TacticalChallenge(TacticalChallengeUI):
def _handle_challenge(self, status): def _handle_challenge(self, status):
match status: match status:
case TCStatus.REWARD: case TCStatus.REWARD:
if self.ui_additional():
return status
if self.get_reward(): if self.get_reward():
return TCStatus.OCR return TCStatus.OCR
case TCStatus.OCR: case TCStatus.OCR:
+27
View File
@@ -0,0 +1,27 @@
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 ```
CLAIM = ButtonWrapper(
name='CLAIM',
jp=None,
en=Button(
file='./assets/en/task/CLAIM.png',
area=(935, 639, 1015, 698),
search=(915, 619, 1035, 718),
color=(234, 214, 69),
button=(935, 639, 1015, 698),
),
)
CLAIM_ALL = ButtonWrapper(
name='CLAIM_ALL',
jp=None,
en=Button(
file='./assets/en/task/CLAIM_ALL.png',
area=(1054, 642, 1243, 700),
search=(1034, 622, 1263, 720),
color=(236, 219, 67),
button=(1054, 642, 1243, 700),
),
)
+27
View File
@@ -0,0 +1,27 @@
from module.base.timer import Timer
from module.logger import logger
from tasks.base.page import page_task
from tasks.base.ui import UI
from tasks.task.assets.assets_task import *
class Task(UI):
def run(self):
self.ui_ensure(page_task)
action_timer = Timer(1).start()
while 1:
self.device.screenshot()
if self.ui_additional():
continue
if action_timer.reached_and_reset():
if self.match_color(CLAIM_ALL):
self.device.click(CLAIM_ALL)
logger.info("Click Claim All")
continue
if self.match_color(CLAIM):
self.device.click(CLAIM)
logger.info("Click Claim")
continue
break
self.config.task_delay(minute=120)