class GameInterface: game = None participants = None start_match_configuration = None game_status_callback_type = None callback_func = None extension = None def __init__(self, logger): self.logger = logger self.dll_path = get_dll_32_location() if is_32_bit_python( ) else get_dll_location() self.renderer = RenderingManager() # wait for the dll to load def setup_function_types(self): self.wait_until_loaded() # update live data packet func = self.game.UpdateLiveDataPacket func.argtypes = [ctypes.POINTER(GameTickPacket)] func.restype = ctypes.c_int func = self.game.UpdateFieldInfo func.argtypes = [ctypes.POINTER(FieldInfoPacket)] func.restype = ctypes.c_int func = self.game.UpdateLiveDataPacketFlatbuffer func.argtypes = [] func.restype = ByteBuffer func = self.game.UpdateRigidBodyTick func.argtypes = [ctypes.POINTER(RigidBodyTick)] func.restype = ctypes.c_int func = self.game.UpdateFieldInfoFlatbuffer func.argtypes = [] func.restype = ByteBuffer func = self.game.GetBallPredictionStruct func.argtypes = [ctypes.POINTER(BallPrediction)] func.restype = ctypes.c_int func = self.game.GetBallPrediction func.argtypes = [] func.restype = ByteBuffer # start match func = self.game.StartMatch func.argtypes = [ MatchSettings, self.game_status_callback_type, ctypes.c_void_p ] func.restype = ctypes.c_int # update player input func = self.game.UpdatePlayerInput func.argtypes = [PlayerInput, ctypes.c_int] func.restype = ctypes.c_int # update player input func = self.game.UpdatePlayerInputFlatbuffer func.argtypes = [ctypes.c_void_p, ctypes.c_int] func.restype = ctypes.c_int # send chat func = self.game.SendChat func.argtypes = [ ctypes.c_uint, ctypes.c_int, ctypes.c_bool, self.game_status_callback_type, ctypes.c_void_p ] func.restype = ctypes.c_int func = self.game.SendQuickChat func.argtypes = [ctypes.c_void_p, ctypes.c_int] func.restype = ctypes.c_int # set game state func = self.game.SetGameState func.argtypes = [ctypes.c_void_p, ctypes.c_int] func.restype = ctypes.c_int self.renderer.setup_function_types(self.game) self.logger.debug('game interface functions are setup') # free the memory at the given pointer func = self.game.Free func.argtypes = [ctypes.c_void_p] def update_live_data_packet(self, game_tick_packet: GameTickPacket): rlbot_status = self.game.UpdateLiveDataPacket(game_tick_packet) self.game_status(None, rlbot_status) return game_tick_packet def update_field_info_packet(self, field_info_packet: FieldInfoPacket): rlbot_status = self.game.UpdateFieldInfo(field_info_packet) self.game_status(None, rlbot_status) return field_info_packet def update_match_data_packet(self): pass def start_match(self): self.wait_until_loaded() # self.game_input_packet.bStartMatch = True rlbot_status = self.game.StartMatch( self.start_match_configuration, self.create_status_callback(None if self.extension is None else self.extension.onMatchStart), None) if rlbot_status != 0: exception_class = get_exception_from_error_code(rlbot_status) raise exception_class() self.logger.debug('Starting match with status: %s', RLBotCoreStatus.status_list[rlbot_status]) def update_player_input(self, player_input, index): rlbot_status = self.game.UpdatePlayerInput(player_input, index) self.game_status(None, rlbot_status, WARNING) def send_chat(self, index, team_only, message_details): rlbot_status = self.game.SendChat(message_details, index, team_only, self.create_status_callback(), None) self.game_status(None, rlbot_status) def send_chat_flat(self, chat_message_builder): buf = chat_message_builder.Output() rlbot_status = self.game.SendQuickChat(bytes(buf), len(buf)) self.game_status(None, rlbot_status) return rlbot_status def create_callback(self): return def game_status(self, id, rlbot_status, level=DEBUG): if rlbot_status != RLBotCoreStatus.Success and rlbot_status != RLBotCoreStatus.BufferOverfilled: self.logger.log(level, "bad status %s", RLBotCoreStatus.status_list[rlbot_status]) def wait_until_loaded(self): self.game.IsInitialized.restype = ctypes.c_bool is_loaded = self.game.IsInitialized() if is_loaded: self.logger.debug('DLL is loaded!') if not is_loaded: time.sleep(1) self.wait_until_loaded() def load_interface(self): self.game_status_callback_type = ctypes.CFUNCTYPE( None, ctypes.c_uint, ctypes.c_uint) self.callback_func = self.game_status_callback_type( wrap_callback(self.game_status)) self.game = ctypes.CDLL(self.dll_path) time.sleep(1) self.setup_function_types() def inject_dll(self): """ Calling this function will inject the DLL without GUI DLL will return status codes from 0 to 5 which correspond to injector_codes DLL injection is only valid if codes are 0->'INJECTION_SUCCESSFUL' or 3->'RLBOT_DLL_ALREADY_INJECTED' It will print the output code and if it's not valid it will kill runner.py If RL isn't running the Injector will stay hidden waiting for RL to open and inject as soon as it does """ self.logger.info('Injecting DLL') # Inject DLL injector_dir = os.path.join(get_dll_directory(), 'RLBot_Injector.exe') for file in [ 'RLBot_Injector.exe', 'RLBot_Core.dll', 'RLBot_Core_Interface.dll', 'RLBot_Core_Interface_32.dll' ]: file_path = os.path.join(get_dll_directory(), file) if not os.path.isfile(file_path): raise FileNotFoundError( '{} was not found in {}. ' 'Please check that the file exists and your antivirus ' 'is not removing it. See https://github.com/RLBot/RLBot/wiki/Antivirus-Notes' .format(file, get_dll_directory())) incode = subprocess.call([injector_dir, 'hidden']) injector_codes = [ 'INJECTION_SUCCESSFUL', 'INJECTION_FAILED', 'MULTIPLE_ROCKET_LEAGUE_PROCESSES_FOUND', 'RLBOT_DLL_ALREADY_INJECTED', 'RLBOT_DLL_NOT_FOUND', 'MULTIPLE_RLBOT_DLL_FILES_FOUND' ] injector_valid_codes = [ 'INJECTION_SUCCESSFUL', 'RLBOT_DLL_ALREADY_INJECTED' ] injection_status = injector_codes[incode] if injection_status in injector_valid_codes: self.logger.info('Finished Injecting DLL') if injection_status == 'INJECTION_SUCCESSFUL': # We need to wait for the injections to be finished self.countdown(20) return injection_status else: self.logger.error('Failed to inject DLL: ' + injection_status) sys.exit() def countdown(self, countdown_timer): self.logger.info( "Waiting {} seconds for DLL to load".format(countdown_timer)) for i in range(countdown_timer): sys.stdout.write(".") sys.stdout.flush() time.sleep(1) print('') def create_status_callback(self, callback=None): """ Creates a callback for the rlbot status, uses default function if callback is none. :param callback: :return: """ if callback is None: return self.callback_func def safe_wrapper(id, rlbotstatsus): callback(rlbotstatsus) return self.game_status_callback_type(wrap_callback(safe_wrapper)) def set_extension(self, extension): self.game_status_callback_type(wrap_callback(self.game_status)) self.extension = extension def update_player_input_flat(self, player_input_builder): buf = player_input_builder.Output() rlbot_status = self.game.UpdatePlayerInputFlatbuffer( bytes(buf), len(buf)) self.game_status(None, rlbot_status, WARNING) def set_game_state(self, set_state_builder): buf = set_state_builder.Output() rlbot_status = self.game.SetGameState(bytes(buf), len(buf)) self.game_status(None, rlbot_status) def get_live_data_flat_binary(self): """ Gets the live data packet in flatbuffer binary format. You'll need to do something like GameTickPacket.GetRootAsGameTickPacket(binary, 0) to get the data out. This is a temporary method designed to keep the integration test working. It returns the raw bytes of the flatbuffer so that it can be stored in a file. We can get rid of this once we have a first-class data recorder that lives inside the core dll. """ byte_buffer = self.game.UpdateLiveDataPacketFlatbuffer() if byte_buffer.size >= 4: # GetRootAsGameTickPacket gets angry if the size is less than 4 # We're counting on this copying the data over to a new memory location so that the original # pointer can be freed safely. proto_string = ctypes.string_at(byte_buffer.ptr, byte_buffer.size) self.game.Free(byte_buffer.ptr) # Avoid a memory leak self.game_status(None, RLBotCoreStatus.Success) return proto_string def update_rigid_body_tick(self, rigid_body_tick: RigidBodyTick): """Get the most recent state of the physics engine.""" rlbot_status = self.game.UpdateRigidBodyTick(rigid_body_tick) self.game_status(None, rlbot_status) return rigid_body_tick def get_field_info(self) -> FieldInfo: """ Gets the field information from the interface. :return: The field information """ byte_buffer = self.game.UpdateFieldInfoFlatbuffer() if byte_buffer.size >= 4: # GetRootAsGameTickPacket gets angry if the size is less than 4 # We're counting on this copying the data over to a new memory location so that the original # pointer can be freed safely. proto_string = ctypes.string_at(byte_buffer.ptr, byte_buffer.size) self.game.Free(byte_buffer.ptr) # Avoid a memory leak self.game_status(None, RLBotCoreStatus.Success) return FieldInfo.GetRootAsFieldInfo(proto_string, 0) def update_ball_prediction(self, ball_prediction: BallPrediction): rlbot_status = self.game.GetBallPredictionStruct(ball_prediction) self.game_status(None, rlbot_status) return ball_prediction def get_ball_prediction(self) -> BallPredictionPacket: """ Gets the latest ball prediction available in shared memory. Only works if BallPrediction.exe is running. """ byte_buffer = self.game.GetBallPrediction() if byte_buffer.size >= 4: # GetRootAsGameTickPacket gets angry if the size is less than 4 # We're counting on this copying the data over to a new memory location so that the original # pointer can be freed safely. proto_string = ctypes.string_at(byte_buffer.ptr, byte_buffer.size) self.game.Free(byte_buffer.ptr) # Avoid a memory leak self.game_status(None, RLBotCoreStatus.Success) return BallPredictionPacket.GetRootAsBallPrediction( proto_string, 0)
class GameInterface: game = None start_match_configuration = None game_status_callback_type = None callback_func = None extension = None def __init__(self, logger): self.logger = logger self.dll_path = get_dll_32_location() if is_32_bit_python( ) else get_dll_location() self.renderer = RenderingManager() # wait for the dll to load def setup_function_types(self): self.wait_until_loaded() # update live data packet func = self.game.UpdateLiveDataPacket func.argtypes = [ctypes.POINTER(GameTickPacket)] func.restype = ctypes.c_int func = self.game.UpdateFieldInfo func.argtypes = [ctypes.POINTER(FieldInfoPacket)] func.restype = ctypes.c_int func = self.game.UpdateLiveDataPacketFlatbuffer func.argtypes = [] func.restype = ByteBuffer func = self.game.UpdateRigidBodyTick func.argtypes = [ctypes.POINTER(RigidBodyTick)] func.restype = ctypes.c_int func = self.game.UpdateFieldInfoFlatbuffer func.argtypes = [] func.restype = ByteBuffer func = self.game.GetBallPredictionStruct func.argtypes = [ctypes.POINTER(BallPrediction)] func.restype = ctypes.c_int func = self.game.GetBallPrediction func.argtypes = [] func.restype = ByteBuffer # start match func = self.game.StartMatch func.argtypes = [MatchSettings] func.restype = ctypes.c_int # update player input func = self.game.UpdatePlayerInput func.argtypes = [PlayerInput, ctypes.c_int] func.restype = ctypes.c_int # update player input func = self.game.UpdatePlayerInputFlatbuffer func.argtypes = [ctypes.c_void_p, ctypes.c_int] func.restype = ctypes.c_int # send chat func = self.game.SendChat func.argtypes = [ ctypes.c_uint, ctypes.c_int, ctypes.c_bool, self.game_status_callback_type, ctypes.c_void_p ] func.restype = ctypes.c_int func = self.game.SendQuickChat func.argtypes = [ctypes.c_void_p, ctypes.c_int] func.restype = ctypes.c_int func = self.game.ReceiveChat func.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int] func.restype = ByteBuffer # set game state func = self.game.SetGameState func.argtypes = [ctypes.c_void_p, ctypes.c_int] func.restype = ctypes.c_int func = self.game.GetMatchSettings func.argtypes = [] func.restype = ByteBuffer self.renderer.setup_function_types(self.game) self.logger.debug('game interface functions are setup') # free the memory at the given pointer func = self.game.Free func.argtypes = [ctypes.c_void_p] def update_live_data_packet(self, game_tick_packet: GameTickPacket): rlbot_status = self.game.UpdateLiveDataPacket(game_tick_packet) self.game_status(None, rlbot_status) return game_tick_packet def update_field_info_packet(self, field_info_packet: FieldInfoPacket): rlbot_status = self.game.UpdateFieldInfo(field_info_packet) self.game_status(None, rlbot_status) return field_info_packet def update_match_data_packet(self): pass def start_match(self): self.wait_until_loaded() # self.game_input_packet.bStartMatch = True rlbot_status = self.game.StartMatch(self.start_match_configuration) if rlbot_status != 0: exception_class = get_exception_from_error_code(rlbot_status) raise exception_class() self.logger.debug('Starting match with status: %s', RLBotCoreStatus.status_list[rlbot_status]) def update_player_input(self, player_input, index): rlbot_status = self.game.UpdatePlayerInput(player_input, index) self.game_status(None, rlbot_status, WARNING) def send_chat(self, index, team_only, message_details): rlbot_status = self.game.SendChat(message_details, index, team_only, self.create_status_callback(), None) self.game_status(None, rlbot_status) def send_chat_flat(self, chat_message_builder): buf = chat_message_builder.Output() rlbot_status = self.game.SendQuickChat(bytes(buf), len(buf)) self.game_status(None, rlbot_status) return rlbot_status def receive_chat(self, index, team, last_message_index) -> QuickChatMessages: byte_buffer = self.game.ReceiveChat(index, team, last_message_index) if byte_buffer.size >= 4: # GetRootAsGameTickPacket gets angry if the size is less than 4 # We're counting on this copying the data over to a new memory location so that the original # pointer can be freed safely. proto_string = ctypes.string_at(byte_buffer.ptr, byte_buffer.size) self.game.Free(byte_buffer.ptr) # Avoid a memory leak return QuickChatMessages.GetRootAsQuickChatMessages( proto_string, 0) else: raise EmptyDllResponse() def create_callback(self): return def game_status(self, id, rlbot_status, level=DEBUG): if rlbot_status != RLBotCoreStatus.Success and rlbot_status != RLBotCoreStatus.BufferOverfilled: self.logger.log(level, "bad status %s", RLBotCoreStatus.status_list[rlbot_status]) def wait_until_loaded(self): for i in range(0, 120): self.game.IsInitialized.restype = ctypes.c_bool is_loaded = self.game.IsInitialized() if is_loaded: self.logger.info('DLL is initialized!') return else: time.sleep(1) raise TimeoutError( "RLBot took too long to initialize! Was Rocket League started with the -rlbot flag? " "If you're not sure, close Rocket League and let us open it for you next time!" ) def wait_until_valid_packet(self): self.logger.info('Waiting for valid packet...') for i in range(0, 16): packet = game_data_struct.GameTickPacket() self.update_live_data_packet(packet) if packet.num_cars > 0: self.logger.info('Packets are looking good!') return else: time.sleep(0.5) self.logger.info('Gave up waiting for valid packet :(') def load_interface(self): self.game_status_callback_type = ctypes.CFUNCTYPE( None, ctypes.c_uint, ctypes.c_uint) self.callback_func = self.game_status_callback_type( wrap_callback(self.game_status)) self.game = ctypes.CDLL(self.dll_path) time.sleep(1) self.setup_function_types() def create_status_callback(self, callback=None): """ Creates a callback for the rlbot status, uses default function if callback is none. :param callback: :return: """ if callback is None: return self.callback_func def safe_wrapper(id, rlbotstatsus): callback(rlbotstatsus) return self.game_status_callback_type(wrap_callback(safe_wrapper)) def set_extension(self, extension): self.game_status_callback_type(wrap_callback(self.game_status)) self.extension = extension def update_player_input_flat(self, player_input_builder): buf = player_input_builder.Output() rlbot_status = self.game.UpdatePlayerInputFlatbuffer( bytes(buf), len(buf)) self.game_status(None, rlbot_status, WARNING) def set_game_state(self, game_state: GameState) -> None: builder = flatbuffers.Builder(0) game_state_offset = game_state.convert_to_flat(builder) if game_state_offset is None: return # There are no values to be set, so just skip it builder.Finish(game_state_offset) buf = builder.Output() rlbot_status = self.game.SetGameState(bytes(buf), len(buf)) self.game_status(None, rlbot_status) def get_live_data_flat_binary(self): """ Gets the live data packet in flatbuffer binary format. You'll need to do something like GameTickPacket.GetRootAsGameTickPacket(binary, 0) to get the data out. This is a temporary method designed to keep the integration test working. It returns the raw bytes of the flatbuffer so that it can be stored in a file. We can get rid of this once we have a first-class data recorder that lives inside the core dll. """ byte_buffer = self.game.UpdateLiveDataPacketFlatbuffer() if byte_buffer.size >= 4: # GetRootAsGameTickPacket gets angry if the size is less than 4 # We're counting on this copying the data over to a new memory location so that the original # pointer can be freed safely. proto_string = ctypes.string_at(byte_buffer.ptr, byte_buffer.size) self.game.Free(byte_buffer.ptr) # Avoid a memory leak self.game_status(None, RLBotCoreStatus.Success) return proto_string def update_rigid_body_tick(self, rigid_body_tick: RigidBodyTick): """Get the most recent state of the physics engine.""" rlbot_status = self.game.UpdateRigidBodyTick(rigid_body_tick) self.game_status(None, rlbot_status) return rigid_body_tick def get_field_info(self) -> FieldInfo: """ Gets the field information from the interface. :return: The field information """ byte_buffer = self.game.UpdateFieldInfoFlatbuffer() if byte_buffer.size >= 4: # GetRootAsGameTickPacket gets angry if the size is less than 4 # We're counting on this copying the data over to a new memory location so that the original # pointer can be freed safely. proto_string = ctypes.string_at(byte_buffer.ptr, byte_buffer.size) self.game.Free(byte_buffer.ptr) # Avoid a memory leak self.game_status(None, RLBotCoreStatus.Success) return FieldInfo.GetRootAsFieldInfo(proto_string, 0) def update_ball_prediction(self, ball_prediction: BallPrediction): rlbot_status = self.game.GetBallPredictionStruct(ball_prediction) self.game_status(None, rlbot_status) return ball_prediction def get_ball_prediction(self) -> BallPredictionPacket: """ Gets the latest ball prediction available in shared memory. Only works if BallPrediction.exe is running. """ byte_buffer = self.game.GetBallPrediction() if byte_buffer.size >= 4: # GetRootAsGameTickPacket gets angry if the size is less than 4 # We're counting on this copying the data over to a new memory location so that the original # pointer can be freed safely. proto_string = ctypes.string_at(byte_buffer.ptr, byte_buffer.size) self.game.Free(byte_buffer.ptr) # Avoid a memory leak self.game_status(None, RLBotCoreStatus.Success) return BallPredictionPacket.GetRootAsBallPrediction( proto_string, 0) def get_match_settings(self) -> MatchSettings: """ Gets the current match settings in flatbuffer format. Useful for determining map, game mode, mutator settings, etc. """ byte_buffer = self.game.GetMatchSettings() if byte_buffer.size >= 4: # GetRootAsGameTickPacket gets angry if the size is less than 4 # We're counting on this copying the data over to a new memory location so that the original # pointer can be freed safely. proto_string = ctypes.string_at(byte_buffer.ptr, byte_buffer.size) self.game.Free(byte_buffer.ptr) # Avoid a memory leak self.game_status(None, RLBotCoreStatus.Success) return MatchSettingsPacket.GetRootAsMatchSettings(proto_string, 0)
class GameInterface: game = None participants = None start_match_configuration = None game_status_callback_type = None callback_func = None extension = None def __init__(self, logger): self.logger = logger self.dll_path = get_dll_32_location() if is_32_bit_python( ) else get_dll_location() self.renderer = RenderingManager() # wait for the dll to load def setup_function_types(self): self.wait_until_loaded() # update live data packet func = self.game.UpdateLiveDataPacket func.argtypes = [ctypes.POINTER(GameTickPacket)] func.restype = ctypes.c_int func = self.game.UpdateFieldInfo func.argtypes = [ctypes.POINTER(FieldInfoPacket)] func.restype = ctypes.c_int func = self.game.UpdateLiveDataPacketFlatbuffer func.argtypes = [] func.restype = ByteBuffer func = self.game.UpdateFieldInfoFlatbuffer func.argtypes = [] func.restype = ByteBuffer # start match func = self.game.StartMatch func.argtypes = [ MatchSettings, self.game_status_callback_type, ctypes.c_void_p ] func.restype = ctypes.c_int # update player input func = self.game.UpdatePlayerInput func.argtypes = [PlayerInput, ctypes.c_int] func.restype = ctypes.c_int # update player input func = self.game.UpdatePlayerInputFlatbuffer func.argtypes = [ctypes.c_void_p, ctypes.c_int] func.restype = ctypes.c_int # send chat func = self.game.SendChat func.argtypes = [ ctypes.c_uint, ctypes.c_int, ctypes.c_bool, self.game_status_callback_type, ctypes.c_void_p ] func.restype = ctypes.c_int func = self.game.SendQuickChat func.argtypes = [ctypes.c_void_p, ctypes.c_int] func.restype = ctypes.c_int self.renderer.setup_function_types(self.game) self.logger.debug('game interface functions are setup') # free the memory at the given pointer func = self.game.Free func.argtypes = [ctypes.c_void_p] def update_live_data_packet(self, game_tick_packet: GameTickPacket): rlbot_status = self.game.UpdateLiveDataPacket(game_tick_packet) self.game_status(None, rlbot_status) return game_tick_packet def update_field_info_packet(self, field_info_packet: FieldInfoPacket): rlbot_status = self.game.UpdateFieldInfo(field_info_packet) self.game_status(None, rlbot_status) return field_info_packet def update_match_data_packet(self): pass def start_match(self): self.wait_until_loaded() # self.game_input_packet.bStartMatch = True rlbot_status = self.game.StartMatch( self.start_match_configuration, self.create_status_callback(None if self.extension is None else self.extension.onMatchStart), None) if rlbot_status != 0: raise rlbot_exception.RLBotException( ).raise_exception_from_error_code(rlbot_status) self.logger.debug('Starting match with status: %s', RLBotCoreStatus.status_list[rlbot_status]) def update_player_input(self, player_input, index): rlbot_status = self.game.UpdatePlayerInput(player_input, index) self.game_status(None, rlbot_status) def send_chat(self, index, team_only, message_details): rlbot_status = self.game.SendChat(message_details, index, team_only, self.create_status_callback(), None) self.game_status(None, rlbot_status) pass def send_chat_flat(self, chat_message_builder): buf = chat_message_builder.Output() rlbot_status = self.game.SendQuickChat(bytes(buf), len(buf)) self.game_status(None, rlbot_status) def create_callback(self): return def game_status(self, id, rlbot_status): pass # self.logger.debug(RLBotCoreStatus.status_list[rlbot_status]) def wait_until_loaded(self): self.game.IsInitialized.restype = ctypes.c_bool is_loaded = self.game.IsInitialized() if not is_loaded: self.logger.debug('DLL is loaded!') if not is_loaded: time.sleep(1) self.wait_until_loaded() def load_interface(self): self.game_status_callback_type = ctypes.CFUNCTYPE( None, ctypes.c_uint, ctypes.c_uint) self.callback_func = self.game_status_callback_type( wrap_callback(self.game_status)) self.game = ctypes.CDLL(self.dll_path) time.sleep(1) self.setup_function_types() def inject_dll(self): """ Calling this function will inject the DLL without GUI DLL will return status codes from 0 to 5 which correspond to injector_codes DLL injection is only valid if codes are 0->'INJECTION_SUCCESSFUL' or 3->'RLBOT_DLL_ALREADY_INJECTED' It will print the output code and if it's not valid it will kill runner.py If RL isn't running the Injector will stay hidden waiting for RL to open and inject as soon as it does """ self.logger.info('Injecting DLL') # Inject DLL injector_dir = os.path.join(get_dll_directory(), "RLBot_Injector.exe") incode = subprocess.call([injector_dir, 'hidden']) injector_codes = [ 'INJECTION_SUCCESSFUL', 'INJECTION_FAILED', 'MULTIPLE_ROCKET_LEAGUE_PROCESSES_FOUND', 'RLBOT_DLL_ALREADY_INJECTED', 'RLBOT_DLL_NOT_FOUND', 'MULTIPLE_RLBOT_DLL_FILES_FOUND' ] injector_valid_codes = [ 'INJECTION_SUCCESSFUL', 'RLBOT_DLL_ALREADY_INJECTED' ] injection_status = injector_codes[incode] if injection_status in injector_valid_codes: self.logger.info('Finished Injecting DLL') if injection_status == 'INJECTION_SUCCESSFUL': # We need to wait for the injections to be finished self.countdown(11) pass return injection_status else: self.logger.error('Failed to inject DLL: ' + injection_status) sys.exit() def countdown(self, countdown_timer): for i in range(countdown_timer): self.logger.info(countdown_timer - i) time.sleep(1) def create_status_callback(self, callback=None): """ Creates a callback for the rlbot status, uses default function if callback is none. :param callback: :return: """ if callback is None: return self.callback_func def safe_wrapper(id, rlbotstatsus): callback(rlbotstatsus) return self.game_status_callback_type(wrap_callback(safe_wrapper)) def set_extension(self, extension): self.game_status_callback_type(wrap_callback(self.game_status)) self.extension = extension def update_player_input_flat(self, player_input_builder): buf = player_input_builder.Output() rlbot_status = self.game.UpdatePlayerInputFlatbuffer( bytes(buf), len(buf)) self.game_status(None, rlbot_status) def get_live_data_flat_binary(self): """ Gets the live data packet in flatbuffer binary format. You'll need to do something like GameTickPacket.GetRootAsGameTickPacket(binary, 0) to get the data out. This is a temporary method designed to keep the integration test working. It returns the raw bytes of the flatbuffer so that it can be stored in a file. We can get rid of this once we have a first-class data recorder that lives inside the core dll. """ byte_buffer = self.game.UpdateLiveDataPacketFlatbuffer() if byte_buffer.size >= 4: # GetRootAsGameTickPacket gets angry if the size is less than 4 # We're counting on this copying the data over to a new memory location so that the original # pointer can be freed safely. proto_string = ctypes.string_at(byte_buffer.ptr, byte_buffer.size) self.game.Free(byte_buffer.ptr) # Avoid a memory leak self.game_status(None, "Success") return proto_string def get_field_info(self): """ Gets the field information from the interface. :return: The field information """ byte_buffer = self.game.UpdateFieldInfoFlatbuffer() if byte_buffer.size >= 4: # GetRootAsGameTickPacket gets angry if the size is less than 4 # We're counting on this copying the data over to a new memory location so that the original # pointer can be freed safely. proto_string = ctypes.string_at(byte_buffer.ptr, byte_buffer.size) self.game.Free(byte_buffer.ptr) # Avoid a memory leak self.game_status(None, "Success") return FieldInfo.FieldInfo.GetRootAsFieldInfo(proto_string, 0)
class GameInterface: game = None start_match_configuration: MatchSettings = None match_config: 'MatchConfig' = None start_match_flatbuffer: Builder = None game_status_callback_type = None callback_func = None extension = None def __init__(self, logger): self.logger = logger self.dll_path = get_dll_32_location() if is_32_bit_python( ) else get_dll_location() self.renderer = RenderingManager() # wait for the dll to load def setup_function_types(self): self.wait_until_loaded() # update live data packet func = self.game.UpdateLiveDataPacket func.argtypes = [ctypes.POINTER(GameTickPacket)] func.restype = ctypes.c_int func = self.game.UpdateFieldInfo func.argtypes = [ctypes.POINTER(FieldInfoPacket)] func.restype = ctypes.c_int func = self.game.UpdateLiveDataPacketFlatbuffer func.argtypes = [] func.restype = ByteBuffer func = self.game.UpdateRigidBodyTick func.argtypes = [ctypes.POINTER(RigidBodyTick)] func.restype = ctypes.c_int func = self.game.UpdateFieldInfoFlatbuffer func.argtypes = [] func.restype = ByteBuffer func = self.game.GetBallPredictionStruct func.argtypes = [ctypes.POINTER(BallPrediction)] func.restype = ctypes.c_int func = self.game.GetBallPrediction func.argtypes = [] func.restype = ByteBuffer # start match func = self.game.StartMatch func.argtypes = [MatchSettings] func.restype = ctypes.c_int func = self.game.StartMatchFlatbuffer func.argtypes = [ctypes.c_void_p, ctypes.c_int] func.restype = ctypes.c_int # update player input func = self.game.UpdatePlayerInput func.argtypes = [PlayerInput, ctypes.c_int] func.restype = ctypes.c_int # update player input func = self.game.UpdatePlayerInputFlatbuffer func.argtypes = [ctypes.c_void_p, ctypes.c_int] func.restype = ctypes.c_int # send chat func = self.game.SendChat func.argtypes = [ ctypes.c_uint, ctypes.c_int, ctypes.c_bool, self.game_status_callback_type, ctypes.c_void_p ] func.restype = ctypes.c_int func = self.game.SendQuickChat func.argtypes = [ctypes.c_void_p, ctypes.c_int] func.restype = ctypes.c_int func = self.game.ReceiveChat func.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int] func.restype = ByteBuffer # set game state func = self.game.SetGameState func.argtypes = [ctypes.c_void_p, ctypes.c_int] func.restype = ctypes.c_int func = self.game.GetMatchSettings func.argtypes = [] func.restype = ByteBuffer func = self.game.FreshLiveDataPacket func.argtypes = [ ctypes.POINTER(GameTickPacket), ctypes.c_int, ctypes.c_int ] func.restype = ctypes.c_int func = self.game.FreshLiveDataPacketFlatbuffer func.argtypes = [ctypes.c_int, ctypes.c_int] func.restype = ByteBuffer if hasattr(self.game, 'StartTcpCommunication'): # Currently, sockets are only working on Windows and Mac, not linux. # Linux has older binaries which do not include this function. func = self.game.StartTcpCommunication func.argtypes = [ ctypes.c_int, ctypes.c_bool, ctypes.c_bool, ctypes.c_bool ] func.restype = ctypes.c_int self.renderer.setup_function_types(self.game) self.logger.debug('game interface functions are setup') # free the memory at the given pointer func = self.game.Free func.argtypes = [ctypes.c_void_p] def update_live_data_packet(self, game_tick_packet: GameTickPacket): rlbot_status = self.game.UpdateLiveDataPacket(game_tick_packet) self.game_status(None, rlbot_status) return game_tick_packet def fresh_live_data_packet(self, game_tick_packet: GameTickPacket, timeout_millis: int, key: int): rlbot_status = self.game.FreshLiveDataPacket(game_tick_packet, timeout_millis, key) self.game_status(None, rlbot_status) return game_tick_packet def update_field_info_packet(self, field_info_packet: FieldInfoPacket): rlbot_status = self.game.UpdateFieldInfo(field_info_packet) self.game_status(None, rlbot_status) return field_info_packet def update_match_data_packet(self): pass def start_match(self): socket_relay = SocketRelay() match_config = self.match_config def connect_handler(): socket_relay.send_match_config(match_config) socket_relay.disconnect() socket_relay.on_connect_handlers.append(connect_handler) try: socket_relay.connect_and_run(False, False, False) except timeout: raise TimeoutError("Took too long to connect to RLBot.exe!") def update_player_input(self, player_input, index): rlbot_status = self.game.UpdatePlayerInput(player_input, index) self.game_status(None, rlbot_status, WARNING) def send_chat(self, index, team_only, message_details): rlbot_status = self.game.SendChat(message_details, index, team_only, self.create_status_callback(), None) self.game_status(None, rlbot_status) def send_chat_flat(self, chat_message_builder): buf = chat_message_builder.Output() rlbot_status = self.game.SendQuickChat(bytes(buf), len(buf)) self.game_status(None, rlbot_status) return rlbot_status def receive_chat(self, index, team, last_message_index) -> QuickChatMessages: byte_buffer = self.game.ReceiveChat(index, team, last_message_index) if byte_buffer.size >= 4: # GetRootAsGameTickPacket gets angry if the size is less than 4 # We're counting on this copying the data over to a new memory location so that the original # pointer can be freed safely. proto_string = ctypes.string_at(byte_buffer.ptr, byte_buffer.size) self.game.Free(byte_buffer.ptr) # Avoid a memory leak return QuickChatMessages.GetRootAsQuickChatMessages( proto_string, 0) else: raise EmptyDllResponse() def create_callback(self): return def game_status(self, id, rlbot_status, level=DEBUG): if rlbot_status != RLBotCoreStatus.Success and rlbot_status != RLBotCoreStatus.BufferOverfilled: if 0 <= rlbot_status < len(RLBotCoreStatus.status_list): self.logger.log(level, "bad status %s", RLBotCoreStatus.status_list[rlbot_status]) else: self.logger.log(level, f"bad status {rlbot_status}") def wait_until_loaded(self): for i in range(0, 120): self.game.IsInitialized.restype = ctypes.c_bool is_loaded = self.game.IsInitialized() if is_loaded: self.logger.info('DLL is initialized!') return else: time.sleep(1) raise TimeoutError( "RLBot took too long to initialize! Was Rocket League started with the -rlbot flag? " "If you're not sure, close Rocket League and let us open it for you next time!" ) def wait_until_ready_to_communicate(self): if hasattr(self.game, 'IsReadyForCommunication'): for i in range(0, 120): self.game.IsReadyForCommunication.restype = ctypes.c_bool is_loaded = self.game.IsReadyForCommunication() if is_loaded: self.logger.info('DLL is ready for comms!') return else: self.logger.debug( 'Waiting until DLL is ready to communicate...') time.sleep(1) raise TimeoutError( "RLBot took too long to initialize! Was Rocket League started with the -rlbot flag? " "If you're not sure, close Rocket League and let us open it for you next time!" ) def wait_until_valid_packet(self): detector = ValidPacketDetector(self.match_config) detector.wait_until_valid_packet() def load_interface(self, port=23234, wants_ball_predictions=True, wants_quick_chat=True, wants_game_messages=False): self.game_status_callback_type = ctypes.CFUNCTYPE( None, ctypes.c_uint, ctypes.c_uint) self.callback_func = self.game_status_callback_type( wrap_callback(self.game_status)) self.game = ctypes.CDLL(self.dll_path) time.sleep(0.2) self.setup_function_types() if hasattr(self.game, 'StartTcpCommunication'): # Currently, sockets are only working on Windows and Mac, not linux. # Linux has older binaries which do not include this function. self.logger.info("About to call StartTcp") self.game.StartTcpCommunication(port, wants_ball_predictions, wants_quick_chat, wants_game_messages) self.wait_until_ready_to_communicate() def create_status_callback(self, callback=None): """ Creates a callback for the rlbot status, uses default function if callback is none. :param callback: :return: """ if callback is None: return self.callback_func def safe_wrapper(id, rlbotstatsus): callback(rlbotstatsus) return self.game_status_callback_type(wrap_callback(safe_wrapper)) def set_extension(self, extension): self.game_status_callback_type(wrap_callback(self.game_status)) self.extension = extension def update_player_input_flat(self, player_input_builder): buf = player_input_builder.Output() rlbot_status = self.game.UpdatePlayerInputFlatbuffer( bytes(buf), len(buf)) self.game_status(None, rlbot_status, WARNING) def set_game_state(self, game_state: GameState) -> None: builder = flatbuffers.Builder(0) game_state_offset = game_state.convert_to_flat(builder) if game_state_offset is None: return # There are no values to be set, so just skip it builder.Finish(game_state_offset) buf = builder.Output() rlbot_status = self.game.SetGameState(bytes(buf), len(buf)) self.game_status(None, rlbot_status) def get_live_data_flat_binary(self): """ Gets the live data packet in flatbuffer binary format. You'll need to do something like GameTickPacket.GetRootAsGameTickPacket(binary, 0) to get the data out. This is a temporary method designed to keep the integration test working. It returns the raw bytes of the flatbuffer so that it can be stored in a file. We can get rid of this once we have a first-class data recorder that lives inside the core dll. """ byte_buffer = self.game.UpdateLiveDataPacketFlatbuffer() return self._process_live_flatbuffer(byte_buffer) def get_fresh_live_data_flat_binary(self, timeout_millis: int, key: int): """ Gets the live data packet in flatbuffer binary format. You'll need to do something like GameTickPacket.GetRootAsGameTickPacket(binary, 0) to get the data out. This one blocks until a fresh frame is available, or until the timeout has elapsed. :param timeout_millis: will give up waiting for a fresh packet and just return a stale one after this number of milliseconds. :param key: answers the question "fresh from whose perspective?". Freshness will be tracked separately for whatever key you pass. Bot index is a reasonable choice. """ byte_buffer = self.game.FreshLiveDataPacketFlatbuffer( timeout_millis, key) return self._process_live_flatbuffer(byte_buffer) def _process_live_flatbuffer(self, byte_buffer): if byte_buffer.size >= 4: # GetRootAsGameTickPacket gets angry if the size is less than 4 # We're counting on this copying the data over to a new memory location so that the original # pointer can be freed safely. proto_string = ctypes.string_at(byte_buffer.ptr, byte_buffer.size) self.game.Free(byte_buffer.ptr) # Avoid a memory leak self.game_status(None, RLBotCoreStatus.Success) return proto_string def update_rigid_body_tick(self, rigid_body_tick: RigidBodyTick): """Get the most recent state of the physics engine.""" rlbot_status = self.game.UpdateRigidBodyTick(rigid_body_tick) self.game_status(None, rlbot_status) return rigid_body_tick def get_field_info(self) -> FieldInfo: """ Gets the field information from the interface. :return: The field information """ byte_buffer = self.game.UpdateFieldInfoFlatbuffer() if byte_buffer.size >= 4: # GetRootAsGameTickPacket gets angry if the size is less than 4 # We're counting on this copying the data over to a new memory location so that the original # pointer can be freed safely. proto_string = ctypes.string_at(byte_buffer.ptr, byte_buffer.size) self.game.Free(byte_buffer.ptr) # Avoid a memory leak self.game_status(None, RLBotCoreStatus.Success) return FieldInfo.GetRootAsFieldInfo(proto_string, 0) def update_ball_prediction(self, ball_prediction: BallPrediction): rlbot_status = self.game.GetBallPredictionStruct(ball_prediction) self.game_status(None, rlbot_status) return ball_prediction def get_ball_prediction(self) -> BallPredictionPacket: """ Gets the latest ball prediction available in shared memory. Only works if BallPrediction.exe is running. """ byte_buffer = self.game.GetBallPrediction() if byte_buffer.size >= 4: # GetRootAsGameTickPacket gets angry if the size is less than 4 # We're counting on this copying the data over to a new memory location so that the original # pointer can be freed safely. proto_string = ctypes.string_at(byte_buffer.ptr, byte_buffer.size) self.game.Free(byte_buffer.ptr) # Avoid a memory leak self.game_status(None, RLBotCoreStatus.Success) return BallPredictionPacket.GetRootAsBallPrediction( proto_string, 0) def get_match_settings(self) -> MatchSettingsPacket: """ Gets the current match settings in flatbuffer format. Useful for determining map, game mode, mutator settings, etc. """ byte_buffer = self.game.GetMatchSettings() if byte_buffer.size >= 4: # GetRootAsGameTickPacket gets angry if the size is less than 4 # We're counting on this copying the data over to a new memory location so that the original # pointer can be freed safely. proto_string = ctypes.string_at(byte_buffer.ptr, byte_buffer.size) self.game.Free(byte_buffer.ptr) # Avoid a memory leak self.game_status(None, RLBotCoreStatus.Success) return MatchSettingsPacket.GetRootAsMatchSettings(proto_string, 0)