Skip to content

Commit dae493d

Browse files
committed
feat: Delay map loading to avoid modified content kick
1 parent 2327464 commit dae493d

6 files changed

Lines changed: 69 additions & 3 deletions

File tree

BF2AutoSpectator/common/config.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class Config(metaclass=Singleton):
2222
__tesseract_path: str
2323
__limit_rtl: bool
2424
__instance_rtl: int
25+
__map_load_delay: int
2526

2627
__use_controller: bool
2728
__controller_base_uri: str
@@ -40,7 +41,7 @@ class Config(metaclass=Singleton):
4041
__player_rotation_paused_until: datetime = None
4142

4243
def set_options(self, player_name: str, player_pass: str, server_ip: str, server_port: str, server_pass: str,
43-
server_mod: str, game_path: str, tesseract_path: str, limit_rtl: bool, instance_rtl: int,
44+
server_mod: str, game_path: str, tesseract_path: str, limit_rtl: bool, instance_rtl: int, map_load_delay: int,
4445
use_controller: bool, controller_base_uri: str, control_obs: bool, obs_url: str,
4546
resolution: str, debug_screenshot: bool,
4647
min_iterations_on_player: int, max_iterations_on_player: int,
@@ -56,6 +57,7 @@ def set_options(self, player_name: str, player_pass: str, server_ip: str, server
5657
self.__tesseract_path = tesseract_path
5758
self.__limit_rtl = limit_rtl
5859
self.__instance_rtl = instance_rtl
60+
self.__map_load_delay = map_load_delay
5961

6062
self.__use_controller = use_controller
6163
self.__controller_base_uri = controller_base_uri
@@ -122,6 +124,9 @@ def limit_rtl(self) -> bool:
122124
def get_instance_trl(self) -> int:
123125
return self.__instance_rtl
124126

127+
def get_map_load_delay(self) -> int:
128+
return self.__map_load_delay
129+
125130
def use_controller(self) -> bool:
126131
return self.__use_controller
127132

BF2AutoSpectator/common/constants.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@
5959
'score-list': (10, 42, 1071, 677),
6060
'top-players': (221, 42, 860, 677),
6161
'top-scores': (434, 42, 647, 677),
62-
'map-briefing': (646, 42, 435, 677)
62+
'map-briefing': (646, 42, 435, 677),
63+
'loading-bar': (9, 685, 1220, 22)
6364
},
6465
'spawn-menu': {
6566
'close-button': (1232, 38, 23, 664)
@@ -116,7 +117,8 @@
116117
'score-list': (12, 53, 1339, 846),
117118
'top-players': (276, 53, 1075, 846),
118119
'top-scores': (542, 53, 809, 846),
119-
'map-briefing': (808, 53, 543, 846)
120+
'map-briefing': (808, 53, 543, 846),
121+
'loading-bar': (12, 858, 1525, 27)
120122
},
121123
'spawn-menu': {
122124
'close-button': (1541, 47, 30, 830)

BF2AutoSpectator/game/instance_manager.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,19 @@ def is_map_loading(self) -> bool:
337337
# Check if game is on round end screen
338338
return self.is_round_end_screen_visible() and not join_game_button_present
339339

340+
def is_loading_bar_visible(self) -> bool:
341+
histogram = histogram_screenshot_region(
342+
self.game_window,
343+
constants.COORDINATES[self.resolution]['hists']['eor']['loading-bar']
344+
)
345+
346+
delta = calc_cv2_hist_delta(
347+
histogram,
348+
self.histograms[self.resolution]['eor']['loading-bar']
349+
)
350+
351+
return delta < constants.HISTCMP_MAX_DELTA
352+
340353
def is_map_briefing_visible(self) -> bool:
341354
return 'map briefing' in ocr_screenshot_game_window_region(
342355
self.game_window,
@@ -643,6 +656,33 @@ def disconnect_from_server(self) -> bool:
643656
# We should still be in the menu but see the "play now" button instead of the "disconnect" button
644657
return self.is_in_menu() and self.is_play_now_button_visible()
645658

659+
def delay_map_load(self, delay: int) -> bool:
660+
if not self.state.map_loading():
661+
return False
662+
663+
check_count = 0
664+
check_limit = 5
665+
started_loading = False
666+
while not started_loading and check_count < check_limit:
667+
started_loading = self.is_loading_bar_visible()
668+
if not started_loading:
669+
check_count += 1
670+
time.sleep(1)
671+
672+
if not started_loading:
673+
return False
674+
675+
# Toggling ALT somehow "pauses"/"resumes" the game while keeping the audio running
676+
# In contrast, BF2mld's approach of suspending the process pauses the audio (not ideal with loading music on)
677+
logger.debug('Suspending map load')
678+
pyautogui.press('alt')
679+
time.sleep(delay)
680+
681+
logger.debug('Resuming map load')
682+
pyautogui.press('alt')
683+
684+
return True
685+
646686
def toggle_hud(self, direction: int) -> bool:
647687
return self.issue_console_command(f'renderer.drawHud {str(direction)}')
648688

BF2AutoSpectator/game/instance_state.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class GameInstanceState:
2121
__server_player_count: int = -1
2222

2323
# Map details (map is as in one entry in the map rotation)
24+
__rotation_map_load_delayed: bool = False
2425
__rotation_map_name: str = None
2526
__rotation_map_size: int = -1
2627
__rotation_game_mode: str = None
@@ -114,6 +115,12 @@ def set_server_player_count(self, player_count: int):
114115
def get_server_player_count(self) -> int:
115116
return self.__server_player_count
116117

118+
def set_rotation_map_load_delayed(self, delayed: bool):
119+
self.__rotation_map_load_delayed = delayed
120+
121+
def rotation_map_load_delayed(self) -> bool:
122+
return self.__rotation_map_load_delayed
123+
117124
def set_rotation_map_name(self, map_name: str):
118125
self.__rotation_map_name = map_name
119126

@@ -200,6 +207,7 @@ def halted(self, grace_period: float = 0.0) -> bool:
200207
# Reset relevant fields after map rotation
201208
def map_rotation_reset(self):
202209
self.__active_join_possible_after = None
210+
self.__rotation_map_load_delayed = False
203211
self.__rotation_map_name = None
204212
self.__rotation_map_size = -1
205213
self.__rotation_game_mode = None
@@ -235,6 +243,7 @@ def restart_reset(self):
235243
self.__map_loading = False
236244
self.__active_join_possible_after = None
237245
self.__round_num = 0
246+
self.__rotation_map_load_delayed = False
238247
self.__rotation_map_name = None
239248
self.__rotation_map_size = -1
240249
self.__rotation_game_mode = None

BF2AutoSpectator/spectate.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ def run():
3838
parser.add_argument('--min-iterations-on-player',
3939
help='Number of iterations to stay on a player before allowing the next_player command',
4040
type=int, default=1)
41+
parser.add_argument('--map-load-delay',
42+
help='Number of seconds to delay map loading by (think: BF2mld)',
43+
type=int, default=5)
4144
parser.add_argument('--use-controller', dest='use_controller', action='store_true')
4245
parser.add_argument('--controller-base-uri', help='Base uri of web controller', type=str)
4346
parser.add_argument('--control-obs', dest='control_obs', action='store_true')
@@ -63,6 +66,7 @@ def run():
6366
tesseract_path=args.tesseract_path,
6467
limit_rtl=args.limit_rtl,
6568
instance_rtl=args.instance_rtl,
69+
map_load_delay=args.map_load_delay,
6670
use_controller=args.use_controller,
6771
controller_base_uri=args.controller_base_uri,
6872
control_obs=args.control_obs,
@@ -547,6 +551,12 @@ def run():
547551
cc.update_game_phase(GamePhase.betweenRounds)
548552
gis.map_rotation_reset()
549553
continue
554+
555+
# Suspend/delay map loading to avoid a modified content kick on map switches
556+
delay = config.get_map_load_delay()
557+
if delay > 0 and not gis.rotation_map_load_delayed() and gim.delay_map_load(delay):
558+
gis.set_rotation_map_load_delayed(True)
559+
550560
# Set loading phase *after* between rounds phase to make sure we go spectating -> between rounds -> loading
551561
cc.update_game_phase(GamePhase.loading)
552562
time.sleep(3)

pickle/histograms.pickle

2.16 KB
Binary file not shown.

0 commit comments

Comments
 (0)