-
Notifications
You must be signed in to change notification settings - Fork 756
/
fast_forward.py
504 lines (430 loc) · 17.6 KB
/
fast_forward.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
import os
from module.base.timer import Timer
from module.base.utils import color_bar_percentage
from module.handler.assets import *
from module.handler.auto_search import AutoSearchHandler
from module.logger import logger
from module.ui.switch import Switch
fast_forward = Switch('Fast_Forward')
fast_forward.add_status('on', check_button=FAST_FORWARD_ON)
fast_forward.add_status('off', check_button=FAST_FORWARD_OFF)
fleet_lock = Switch('Fleet_Lock', offset=(5, 20))
fleet_lock.add_status('on', check_button=FLEET_LOCKED)
fleet_lock.add_status('off', check_button=FLEET_UNLOCKED)
auto_search = Switch('Auto_Search', offset=(20, 20))
auto_search.add_status('on', check_button=AUTO_SEARCH_ON)
auto_search.add_status('off', check_button=AUTO_SEARCH_OFF)
def map_files(event):
"""
Args:
event (str): Event name under './campaign'
Returns:
list[str]: List of map files, such as ['sp1', 'sp2', 'sp3']
"""
folder = f'./campaign/{event}'
if not os.path.exists(folder):
logger.warning(f'Map file folder: {folder} does not exist, can not get map files')
return []
files = []
for file in os.listdir(folder):
name, ext = os.path.splitext(file)
if ext != '.py':
continue
if name == 'campaign_base':
continue
files.append(name)
return files
def to_map_input_name(name: str) -> str:
"""
Convert to user input names.
7-2 -> 7-2
campaign_7_2 -> 7-2
d3 -> D3
"""
name = name.upper()
name = name.replace('CAMPAIGN_', '').replace('_', '-')
return name
def to_map_file_name(name: str) -> str:
"""
Convert to the name of map files.
7-2 -> campaign_7_2
campaign_7_2 -> campaign_7_2
D3 -> d3
"""
name = name.lower()
if name and name[0].isdigit():
name = 'campaign_' + name.replace('-', '_')
return name
class FastForwardHandler(AutoSearchHandler):
map_clear_percentage = 0.
map_achieved_star_1 = False
map_achieved_star_2 = False
map_achieved_star_3 = False
map_is_100_percent_clear = False
map_is_3_stars = False
map_is_threat_safe = False
map_has_clear_mode = False
map_is_clear_mode = False # Clear mode == fast forward
map_is_auto_search = False
map_is_2x_book = False
STAGE_INCREASE = [
"""
1-1 > 1-2 > 1-3 > 1-4
> 2-1 > 2-2 > 2-3 > 2-4
> 3-1 > 3-2 > 3-3 > 3-4
> 4-1 > 4-2 > 4-3 > 4-4
> 5-1 > 5-2 > 5-3 > 5-4
> 6-1 > 6-2 > 6-3 > 6-4
> 7-1 > 7-2 > 7-3 > 7-4
> 8-1 > 8-2 > 8-3 > 8-4
> 9-1 > 9-2 > 9-3 > 9-4
> 10-1 > 10-2 > 10-3 > 10-4
> 11-1 > 11-2 > 11-3 > 11-4
> 12-1 > 12-2 > 12-3 > 12-4
> 13-1 > 13-2 > 13-3 > 13-4
> 14-1 > 14-2 > 14-3 > 14-4
""",
'A1 > A2 > A3',
'B1 > B2 > B3',
'C1 > C2 > C3',
'D1 > D2 > D3',
'SP1 > SP2 > SP3 > SP4',
'T1 > T2 > T3 > T4',
]
map_fleet_checked = False
def map_get_info(self):
"""
Logs:
| INFO | [Map_info] 98%, star_1, star_2, star_3, clear, 3_star, green, fast_forward
"""
self.map_clear_percentage = self.get_map_clear_percentage()
self.map_achieved_star_1 = self.appear(MAP_STAR_1)
self.map_achieved_star_2 = self.appear(MAP_STAR_2)
self.map_achieved_star_3 = self.appear(MAP_STAR_3)
self.map_is_100_percent_clear = self.map_clear_percentage > 0.95
self.map_is_3_stars = self.map_achieved_star_1 and self.map_achieved_star_2 and self.map_achieved_star_3
self.map_is_threat_safe = self.appear(MAP_GREEN)
if self.config.Campaign_Name.lower() == 'sp':
# Minor issue here
# Using auto_search option because clear mode cannot be detected whether on SP
# If user manually turn off auto search, alas can't enable it again
self.map_has_clear_mode = auto_search.appear(main=self)
else:
self.map_has_clear_mode = self.map_is_100_percent_clear and fast_forward.appear(main=self)
# Override config
if self.map_achieved_star_1:
# Story before boss spawn, Attribute "story_refresh_boss" in chapter_template.lua
self.config.MAP_HAS_MAP_STORY = False
self.config.MAP_CLEAR_ALL_THIS_TIME = self.config.STAR_REQUIRE_3 \
and not self.__getattribute__(f'map_achieved_star_{self.config.STAR_REQUIRE_3}') \
and (self.config.StopCondition_MapAchievement in ['map_3_stars', 'threat_safe'])
self.map_show_info()
def map_show_info(self):
# Log
logger.attr('MAP_CLEAR_ALL_THIS_TIME', self.config.MAP_CLEAR_ALL_THIS_TIME)
names = ['map_achieved_star_1', 'map_achieved_star_2', 'map_achieved_star_3',
'map_is_100_percent_clear', 'map_is_3_stars',
'map_is_threat_safe', 'map_has_clear_mode']
strip = ['map', 'achieved', 'is', 'has']
log_names = ['_'.join([x for x in name.split('_') if x not in strip]) for name in names]
text = ', '.join([l for l, n in zip(log_names, names) if self.__getattribute__(n)])
text = f'{int(self.map_clear_percentage * 100)}%, ' + text
logger.attr('Map_info', text)
logger.attr('StopCondition_MapAchievement', self.config.StopCondition_MapAchievement)
def handle_fast_forward(self):
if not self.map_has_clear_mode:
self.map_is_clear_mode = False
self.map_is_auto_search = False
self.map_is_2x_book = False
return False
if self.config.Campaign_UseClearMode:
self.config.MAP_HAS_AMBUSH = False
self.config.MAP_HAS_FLEET_STEP = False
self.config.MAP_HAS_MOVABLE_ENEMY = False
self.config.MAP_HAS_MOVABLE_NORMAL_ENEMY = False
self.config.MAP_HAS_PORTAL = False
self.config.MAP_HAS_LAND_BASED = False
self.config.MAP_HAS_MAZE = False
self.config.MAP_HAS_FORTRESS = False
self.config.MAP_HAS_BOUNCING_ENEMY = False
self.config.MAP_HAS_DECOY_ENEMY = False
self.map_is_clear_mode = True
if self.config.MAP_CLEAR_ALL_THIS_TIME:
logger.info('MAP_CLEAR_ALL_THIS_TIME does not work with auto search, disable auto search temporarily')
self.map_is_auto_search = False
else:
self.map_is_auto_search = self.config.Campaign_UseAutoSearch
self.map_is_2x_book = self.config.Campaign_Use2xBook
else:
# When disable fast forward, MAP_HAS_AMBUSH depends on map settings.
# self.config.MAP_HAS_AMBUSH = True
self.map_is_clear_mode = False
self.map_is_auto_search = False
self.map_is_2x_book = False
pass
status = 'on' if self.config.Campaign_UseClearMode else 'off'
changed = fast_forward.set(status=status, main=self)
return changed
def handle_map_fleet_lock(self, enable=None):
"""
Args:
enable (bool): Default to None, use Campaign_UseFleetLock.
Returns:
bool: If switched.
"""
# Fleet lock depends on if it appear on map, not depends on map status.
# Because if already in map, there's no map status,
if not fleet_lock.appear(main=self):
logger.info('No fleet lock option.')
return False
if enable is None:
enable = self.config.Campaign_UseFleetLock
status = 'on' if enable else 'off'
changed = fleet_lock.set(status=status, main=self)
return changed
def handle_auto_search(self):
"""
Returns:
bool: If changed
Pages:
in: MAP_PREPARATION
"""
# if not self.map_is_clear_mode:
# return False
if not auto_search.appear(main=self):
logger.info('No auto search option.')
self.map_is_auto_search = False
return False
status = 'on' if self.map_is_auto_search else 'off'
changed = auto_search.set(status=status, main=self)
return changed
def handle_auto_search_setting(self):
"""
Returns:
bool: If changed
Pages:
in: FLEET_PREPARATION
"""
if not self.map_is_auto_search:
return False
logger.info('Auto search setting')
self.fleet_preparation_sidebar_ensure(3)
self.auto_search_setting_ensure(self.config.Fleet_FleetOrder)
if self.config.SUBMARINE:
self.auto_search_setting_ensure(self.config.Submarine_AutoSearchMode)
return True
@property
def is_call_submarine_at_boss(self):
return self.config.SUBMARINE and self.config.Submarine_Mode in ['boss_only', 'hunt_and_boss']
def handle_auto_submarine_call_disable(self):
"""
Returns:
bool: If changed
Pages:
in: FLEET_PREPARATION
"""
if self.map_fleet_checked:
return False
if not self.is_call_submarine_at_boss:
return False
if not self.map_is_auto_search:
logger.warning('Can not set submarine call because auto search not available, assuming disabled')
logger.warning('Please do the followings: '
'goto any stage -> auto search role -> set submarine role to standby')
logger.warning('If you already did, ignore this warning')
return False
logger.info('Disable auto submarine call')
self.fleet_preparation_sidebar_ensure(3)
self.auto_search_setting_ensure('sub_standby')
return True
def handle_auto_search_continue(self):
"""
Override AutoSearchHandler definition
for 2x book handling if needed
"""
if self.appear(AUTO_SEARCH_MENU_CONTINUE, offset=self._auto_search_menu_offset, interval=2):
self.map_is_2x_book = self.config.Campaign_Use2xBook
self.handle_2x_book_setting(mode='auto')
if self.appear_then_click(AUTO_SEARCH_MENU_CONTINUE, offset=self._auto_search_menu_offset):
self.interval_reset(AUTO_SEARCH_MENU_CONTINUE)
else:
# AUTO_SEARCH_MENU_CONTINUE disappeared after handle_2x_book_setting()
pass
return True
return False
def get_map_clear_percentage(self):
"""
Returns:
float: 0 to 1.
Pages:
in: MAP_PREPARATION
"""
return color_bar_percentage(self.device.image, area=MAP_CLEAR_PERCENTAGE.area, prev_color=(231, 170, 82))
def campaign_name_increase(self, name):
"""
Increase name to its next stage.
Args:
name (str): Such as `6-1`, `a1`, `campaign_6_1`
Returns:
str: Name of next stage in upper case,
or origin name if unable to increase.
"""
name = to_map_input_name(name)
for increase in self.STAGE_INCREASE:
increase = [i.strip(' \t\r\n') for i in increase.split('>')]
if name in increase:
index = increase.index(name) + 1
if index < len(increase):
new = increase[index]
# Don't check main stages, assume all exist
# Main stages are named like campaign_7_2, but user inputs 7-2
if self.config.Campaign_Event == 'campaign_main':
return new
# Check if map file exist
existing = map_files(self.config.Campaign_Event)
logger.info(f'Existing files: {existing}')
if new.lower() in existing:
return new
else:
logger.info(f'Stage increase reach end, new map {new} does not exist')
return name
else:
logger.info('Stage increase reach end')
return name
return name
def triggered_map_stop(self):
"""
Returns:
bool:
"""
if self.config.StopCondition_MapAchievement == '100_percent_clear':
if self.map_is_100_percent_clear:
return True
if self.config.StopCondition_MapAchievement == 'map_3_stars':
if self.map_is_100_percent_clear and self.map_is_3_stars:
return True
if self.config.StopCondition_MapAchievement == 'threat_safe_without_3_stars':
if self.map_is_100_percent_clear and self.map_is_threat_safe:
return True
if self.config.StopCondition_MapAchievement == 'threat_safe':
if self.map_is_100_percent_clear and self.map_is_3_stars and self.map_is_threat_safe:
return True
return False
def handle_map_stop(self):
"""
Modify configs after reaching a stop condition.
Disable current task or increase stage.
"""
if self.config.StopCondition_StageIncrease:
prev_stage = to_map_input_name(self.config.Campaign_Name)
next_stage = self.campaign_name_increase(prev_stage)
if next_stage != prev_stage:
logger.info(f'Stage {prev_stage} increases to {next_stage}')
self.config.Campaign_Name = next_stage
else:
logger.info(f'Stage {prev_stage} cannot increase, stop at current stage')
self.config.Scheduler_Enable = False
else:
self.config.Scheduler_Enable = False
def _set_2x_book_status(self, status, check_button, box_button, skip_first_screenshot=True):
"""
Set appropriate 2x book setting
with corresponding status and buttons
Built with retry mechanism that limits to 3
attempts that span 3 second intervals each
Args:
status (string):
on or off
check_button (Button):
button to check before attempting to click
box_button (Button):
button to click and image color count against
skip_first_screenshot (bool):
namesake
Returns:
bool:
True if detected having set correctly
False can occur for 2 reasons either
assets insufficient to detect properly
or 2x book setting is absent
"""
confirm_timer = Timer(1).start()
clicked_threshold = 0
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if clicked_threshold > 3:
break
if self.appear(check_button, offset=self._auto_search_menu_offset, interval=3):
box_button.load_offset(check_button)
enabled = self.image_color_count(box_button.button, color=(156, 255, 82), threshold=221, count=20)
if (status == 'on' and enabled) or (status == 'off' and not enabled):
return True
if (status == 'on' and not enabled) or (status == 'off' and enabled):
self.device.click(box_button)
clicked_threshold += 1
if not clicked_threshold and confirm_timer.reached():
logger.info('Map do not have 2x book setting')
return False
logger.warning(f'Wait time has expired; Cannot set 2x book setting')
return False
def handle_2x_book_setting(self, mode='prep'):
"""
Handles 2x book setting if applicable
Args:
mode (string):
prep or auto, assume auto if not prep
Returns:
bool:
If handled to completion
"""
if not self.map_is_clear_mode:
return False
if not hasattr(self, 'emotion'):
logger.info('Emotion instance not loaded, cannot handle 2x book setting')
return False
logger.info(f'Handling 2x book setting, mode={mode}.')
if mode == 'prep':
book_check = BOOK_CHECK_PREP
book_box = BOOK_BOX_PREP
else:
book_check = BOOK_CHECK_AUTO
book_box = BOOK_BOX_AUTO
status = 'on' if self.map_is_2x_book else 'off'
if self._set_2x_book_status(status, book_check, book_box):
self.emotion.map_is_2x_book = self.map_is_2x_book
else:
self.map_is_2x_book = False
self.emotion.map_is_2x_book = self.map_is_2x_book
self.handle_info_bar()
return True
def handle_2x_book_popup(self):
if self.appear(BOOK_POPUP_CHECK, offset=(20, 20)):
if self.handle_popup_confirm('2X_BOOK'):
return True
return False
def handle_map_walk_speedup(self, skip_first_screenshot=True):
"""
Turn on walk speedup, no reason to turn it off
"""
if not self.config.MAP_HAS_WALK_SPEEDUP:
return False
timeout = Timer(2, count=4).start()
interval = Timer(1, count=2)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if self.image_color_count(MAP_WALK_SPEEDUP, color=(132, 255, 148), threshold=180, count=50):
logger.attr('Walk_Speedup', 'on')
return True
if timeout.reached():
logger.warning(f'Wait time has expired; Cannot set map walk speedup')
return False
if interval.reached():
self.device.click(MAP_WALK_SPEEDUP)
interval.reset()
continue