diff --git a/pyproject.toml b/pyproject.toml index 408021b..21019fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ description = "A high performance Python interface for communicating with RLBot dynamic = ["version"] requires-python = ">= 3.11" dependencies = [ - "rlbot_flatbuffers~=0.16.0", + "rlbot_flatbuffers~=0.17.0", "psutil==7.*", ] readme = "README.md" diff --git a/rlbot/config.py b/rlbot/config.py index 20a1946..4226df8 100644 --- a/rlbot/config.py +++ b/rlbot/config.py @@ -94,30 +94,28 @@ def load_match_config(config_path: Path | str) -> flat.MatchConfiguration: match variant: case "rlbot": - variety, use_config = flat.CustomBot(), True + abs_config_path = (config_path.parent / car_config).resolve() + players.append( + load_player_config(abs_config_path, team, name, loadout_file) + ) case "psyonix": - variety, use_config = flat.Psyonix(skill), True + abs_config_path = (config_path.parent / car_config).resolve() + players.append( + load_psyonix_config( + team, + skill, + name, + loadout_file, + abs_config_path, + ) + ) case "human": - variety, use_config = flat.Human(), False - case "partymember": - logger.warning("PartyMember player type is not supported yet.") - variety, use_config = flat.PartyMember(), False + players.append(flat.PlayerConfiguration(flat.Human(), team, 0)) case t: raise ConfigParsingException( f"Invalid player type {repr(t)} for player {len(players)}." ) - if use_config and car_config: - abs_config_path = (config_path.parent / car_config).resolve() - players.append( - load_player_config(abs_config_path, variety, team, name, loadout_file) # type: ignore - ) - else: - loadout = load_player_loadout(loadout_file, team) if loadout_file else None - players.append( - flat.PlayerConfiguration(variety, name, team, loadout=loadout) - ) - scripts: list[flat.ScriptConfiguration] = [] for script_table in config.get("scripts", []): if script_config := __str(script_table, "config_file"): @@ -170,7 +168,9 @@ def load_match_config(config_path: Path | str) -> flat.MatchConfiguration: existing_match_behavior=__enum( match_table, "existing_match_behavior", flat.ExistingMatchBehavior ), - enable_rendering=__bool(match_table, "enable_rendering"), + enable_rendering=__enum( + match_table, "enable_rendering", flat.DebugRendering.OffByDefault + ), enable_state_setting=__bool(match_table, "enable_state_setting"), freeplay=__bool(match_table, "freeplay"), ) @@ -217,8 +217,7 @@ def load_player_loadout(path: Path | str, team: int) -> flat.PlayerLoadout: def load_player_config( - path: Path | str | None, - type: flat.CustomBot | flat.Psyonix, + path: Path | str, team: int, name_override: str | None = None, loadout_override: Path | str | None = None, @@ -227,20 +226,6 @@ def load_player_config( Reads the bot toml file at the provided path and creates a `PlayerConfiguration` of the given type for the given team. """ - if path is None: - loadout = ( - load_player_loadout(loadout_override, team) - if loadout_override is not None - else None - ) - - return flat.PlayerConfiguration( - type, - name_override or "", - team, - loadout=loadout, - ) - path = Path(path) with open(path, "rb") as f: config = tomllib.load(f) @@ -266,15 +251,64 @@ def load_player_config( ) return flat.PlayerConfiguration( - type, - name_override or __str(settings, "name"), + flat.CustomBot( + name_override or __str(settings, "name"), + str(root_dir), + run_command, + loadout, + __str(settings, "agent_id"), + __bool(settings, "hivemind"), + ), + team, + 0, + ) + + +def load_psyonix_config( + team: int, + skill_level: flat.PsyonixSkill, + name_override: str | None = None, + loadout_override: Path | str | None = None, + path: Path | str | None = None, +) -> flat.PlayerConfiguration: + """ + Creates a `PlayerConfiguration` for a Psyonix bot of the given team and skill. + If a path is provided, it will be used override the default name and loadout. + """ + name = name_override + loadout_path = loadout_override + + # Don't parse the toml file if we have no data we need to extract, + # even if a path to a toml file is provided. + if path is not None and (name is None or loadout_path is None): + path = Path(path) + with open(path, "rb") as f: + config = tomllib.load(f) + + settings = __table(config, "settings") + + if name is None: + name = __str(settings, "name") + + if loadout_path is None: + loadout_path = ( + path.parent / Path(__str(settings, "loadout_file")) + if "loadout_file" in settings + else None + ) + + loadout = ( + load_player_loadout(loadout_path, team) if loadout_path is not None else None + ) + + return flat.PlayerConfiguration( + flat.PsyonixBot( + name or "", + loadout, + skill_level, + ), team, - str(root_dir), - run_command, - loadout, 0, - __str(settings, "agent_id"), - __bool(settings, "hivemind"), ) diff --git a/rlbot/interface.py b/rlbot/interface.py index feea530..b8a351f 100644 --- a/rlbot/interface.py +++ b/rlbot/interface.py @@ -1,7 +1,6 @@ import logging import time from collections.abc import Callable -from dataclasses import dataclass from enum import IntEnum from pathlib import Path from socket import IPPROTO_TCP, TCP_NODELAY, socket @@ -18,36 +17,6 @@ RLBOT_SERVER_PORT = 23234 -class SocketDataType(IntEnum): - """ - See https://github.com/RLBot/core/blob/master/RLBotCS/Types/DataType.cs - and https://wiki.rlbot.org/framework/sockets-specification/#data-types - """ - - NONE = 0 - GAME_PACKET = 1 - FIELD_INFO = 2 - START_COMMAND = 3 - MATCH_CONFIGURATION = 4 - PLAYER_INPUT = 5 - DESIRED_GAME_STATE = 6 - RENDER_GROUP = 7 - REMOVE_RENDER_GROUP = 8 - MATCH_COMMUNICATION = 9 - BALL_PREDICTION = 10 - CONNECTION_SETTINGS = 11 - STOP_COMMAND = 12 - SET_LOADOUT = 13 - INIT_COMPLETE = 14 - CONTROLLABLE_TEAM_INFO = 15 - - -@dataclass(repr=False, eq=False, frozen=True, match_args=False, slots=True) -class SocketMessage: - type: SocketDataType - data: bytes - - class MsgHandlingResult(IntEnum): TERMINATED = 0 NO_INCOMING_MSGS = 1 @@ -66,7 +35,6 @@ class SocketRelay: is_connected = False _running = False """Indicates whether a messages are being handled by the `run` loop (potentially in a background thread)""" - _ball_pred = flat.BallPrediction() on_connect_handlers: list[Callable[[], None]] = [] packet_handlers: list[Callable[[flat.GamePacket], None]] = [] @@ -77,7 +45,7 @@ class SocketRelay: controllable_team_info_handlers: list[ Callable[[flat.ControllableTeamInfo], None] ] = [] - raw_handlers: list[Callable[[SocketMessage], None]] = [] + raw_handlers: list[Callable[[flat.CoreMessage], None]] = [] def __init__( self, @@ -116,50 +84,45 @@ def _read_exact(self, n: int) -> bytes: pos += cr return bytes(buff) - def read_message(self) -> SocketMessage: - type_int = self._read_int() + def read_message(self) -> bytes: size = self._read_int() - data = self._read_exact(size) - return SocketMessage(SocketDataType(type_int), data) + return self._read_exact(size) - def send_bytes(self, data: bytes, data_type: SocketDataType): + def send_bytes(self, data: bytes): assert self.is_connected, "Connection has not been established" size = len(data) if size > MAX_SIZE_2_BYTES: - self.logger.error( - "Couldn't send %s message because it was too big!", data_type.name - ) + self.logger.error("Couldn't send message because it was too big!") return - message = self._int_to_bytes(data_type) + self._int_to_bytes(size) + data + message = self._int_to_bytes(size) + data self.socket.sendall(message) - def send_init_complete(self): - self.send_bytes(bytes(), SocketDataType.INIT_COMPLETE) - - def send_set_loadout(self, set_loadout: flat.SetLoadout): - self.send_bytes(set_loadout.pack(), SocketDataType.SET_LOADOUT) - - def send_match_comm(self, match_comm: flat.MatchComm): - self.send_bytes(match_comm.pack(), SocketDataType.MATCH_COMMUNICATION) - - def send_player_input(self, player_input: flat.PlayerInput): - self.send_bytes(player_input.pack(), SocketDataType.PLAYER_INPUT) - - def send_game_state(self, game_state: flat.DesiredGameState): - self.send_bytes(game_state.pack(), SocketDataType.DESIRED_GAME_STATE) - - def send_render_group(self, render_group: flat.RenderGroup): - self.send_bytes(render_group.pack(), SocketDataType.RENDER_GROUP) + def send_msg( + self, + msg: ( + flat.DisconnectSignal + | flat.StartCommand + | flat.MatchConfiguration + | flat.PlayerInput + | flat.DesiredGameState + | flat.RenderGroup + | flat.RemoveRenderGroup + | flat.MatchComm + | flat.ConnectionSettings + | flat.StopCommand + | flat.SetLoadout + | flat.InitComplete + ), + ): + self.send_bytes(flat.InterfacePacket(msg).pack()) def remove_render_group(self, group_id: int): - flatbuffer = flat.RemoveRenderGroup(group_id).pack() - self.send_bytes(flatbuffer, SocketDataType.REMOVE_RENDER_GROUP) + self.send_msg(flat.RemoveRenderGroup(group_id)) def stop_match(self, shutdown_server: bool = False): - flatbuffer = flat.StopCommand(shutdown_server).pack() - self.send_bytes(flatbuffer, SocketDataType.STOP_COMMAND) + self.send_msg(flat.StopCommand(shutdown_server)) def start_match(self, match_config: Path | flat.MatchConfiguration): self.logger.info("Python interface is attempting to start match...") @@ -167,17 +130,15 @@ def start_match(self, match_config: Path | flat.MatchConfiguration): match match_config: case Path() as path: string_path = str(path.absolute().resolve()) - flatbuffer = flat.StartCommand(string_path).pack() - flat_type = SocketDataType.START_COMMAND + flatbuffer = flat.StartCommand(string_path) case flat.MatchConfiguration() as settings: - flatbuffer = settings.pack() - flat_type = SocketDataType.MATCH_CONFIGURATION + flatbuffer = settings case _: raise ValueError( "Expected MatchSettings or path to match settings toml file" ) - self.send_bytes(flatbuffer, flat_type) + self.send_msg(flatbuffer) def connect( self, @@ -242,13 +203,14 @@ def connect( for handler in self.on_connect_handlers: handler() - flatbuffer = flat.ConnectionSettings( - agent_id=self.agent_id, - wants_ball_predictions=wants_ball_predictions, - wants_comms=wants_match_communications, - close_between_matches=close_between_matches, - ).pack() - self.send_bytes(flatbuffer, SocketDataType.CONNECTION_SETTINGS) + self.send_msg( + flat.ConnectionSettings( + agent_id=self.agent_id, + wants_ball_predictions=wants_ball_predictions, + wants_comms=wants_match_communications, + close_between_matches=close_between_matches, + ) + ) def run(self, *, background_thread: bool = False): """ @@ -286,16 +248,14 @@ def handle_incoming_messages(self, blocking: bool = False) -> MsgHandlingResult: return self.handle_incoming_message(incoming_message) except flat.InvalidFlatbuffer as e: self.logger.error( - "Error while unpacking message of type %s (%s bytes): %s", - incoming_message.type.name, - len(incoming_message.data), + "Error while unpacking message (%s bytes): %s", + len(incoming_message), e, ) return MsgHandlingResult.TERMINATED except Exception as e: self.logger.error( - "Unexpected error while handling message of type %s: %s", - incoming_message.type.name, + "Unexpected error while handling message of type: %s", e, ) return MsgHandlingResult.TERMINATED @@ -306,56 +266,43 @@ def handle_incoming_messages(self, blocking: bool = False) -> MsgHandlingResult: self.logger.error("SocketRelay disconnected unexpectedly!") return MsgHandlingResult.TERMINATED - def handle_incoming_message( - self, incoming_message: SocketMessage - ) -> MsgHandlingResult: + def handle_incoming_message(self, incoming_message: bytes) -> MsgHandlingResult: """ Handles a messages by passing it to the relevant handlers. Returns True if the message was NOT a shutdown request (i.e. NONE). """ + flatbuffer = flat.CorePacket.unpack(incoming_message).message + for raw_handler in self.raw_handlers: - raw_handler(incoming_message) + raw_handler(flatbuffer) - match incoming_message.type: - case SocketDataType.NONE: + match flatbuffer.item: + case flat.DisconnectSignal(): return MsgHandlingResult.TERMINATED - case SocketDataType.GAME_PACKET: - if len(self.packet_handlers) > 0: - packet = flat.GamePacket.unpack(incoming_message.data) - for handler in self.packet_handlers: - handler(packet) - case SocketDataType.FIELD_INFO: - if len(self.field_info_handlers) > 0: - field_info = flat.FieldInfo.unpack(incoming_message.data) - for handler in self.field_info_handlers: - handler(field_info) - case SocketDataType.MATCH_CONFIGURATION: - if len(self.match_config_handlers) > 0: - match_settings = flat.MatchConfiguration.unpack( - incoming_message.data - ) - for handler in self.match_config_handlers: - handler(match_settings) - case SocketDataType.MATCH_COMMUNICATION: - if len(self.match_comm_handlers) > 0: - match_comm = flat.MatchComm.unpack(incoming_message.data) - for handler in self.match_comm_handlers: - handler(match_comm) - case SocketDataType.BALL_PREDICTION: - if len(self.ball_prediction_handlers) > 0: - self._ball_pred.unpack_with(incoming_message.data) - for handler in self.ball_prediction_handlers: - handler(self._ball_pred) - case SocketDataType.CONTROLLABLE_TEAM_INFO: - if len(self.controllable_team_info_handlers) > 0: - player_mappings = flat.ControllableTeamInfo.unpack( - incoming_message.data - ) - for handler in self.controllable_team_info_handlers: - handler(player_mappings) + case flat.GamePacket() as packet: + for handler in self.packet_handlers: + handler(packet) + case flat.FieldInfo() as field_info: + for handler in self.field_info_handlers: + handler(field_info) + case flat.MatchConfiguration() as match_config: + for handler in self.match_config_handlers: + handler(match_config) + case flat.MatchComm() as match_comm: + for handler in self.match_comm_handlers: + handler(match_comm) + case flat.BallPrediction() as ball_prediction: + for handler in self.ball_prediction_handlers: + handler(ball_prediction) + case flat.ControllableTeamInfo() as controllable_team_info: + for handler in self.controllable_team_info_handlers: + handler(controllable_team_info) case _: - pass + self.logger.warning( + "Received unknown message type: %s", + type(flatbuffer.item).__name__, + ) return MsgHandlingResult.MORE_MSGS_QUEUED @@ -364,7 +311,7 @@ def disconnect(self): self.logger.warning("Asked to disconnect but was already disconnected.") return - self.send_bytes(bytes([1]), SocketDataType.NONE) + self.send_msg(flat.DisconnectSignal()) timeout = 5.0 while self._running and timeout > 0: time.sleep(0.1) diff --git a/rlbot/managers/bot.py b/rlbot/managers/bot.py index 9320967..765b707 100644 --- a/rlbot/managers/bot.py +++ b/rlbot/managers/bot.py @@ -13,6 +13,8 @@ from rlbot.utils import fill_desired_game_state from rlbot.utils.logging import DEFAULT_LOGGER, get_logger +WARNED_SPAWN_ID_DEPRECATED = False + class Bot: """ @@ -28,7 +30,18 @@ class Bot: team: int = -1 index: int = -1 name: str = "" - spawn_id: int = 0 + player_id: int = 0 + + @property + def spawn_id(self) -> int: + global WARNED_SPAWN_ID_DEPRECATED + if not WARNED_SPAWN_ID_DEPRECATED: + WARNED_SPAWN_ID_DEPRECATED = True + self.logger.warning( + "'spawn_id' getter accessed, which is deprecated in favor of 'player_id'." + ) + + return self.player_id match_config = flat.MatchConfiguration() """ @@ -92,10 +105,12 @@ def _try_initialize(self): # Search match settings for our name for player in self.match_config.player_configurations: - if player.spawn_id == self.spawn_id: - self.name = player.name - self.logger = get_logger(self.name) - break + match player.variety.item: + case flat.CustomBot(name): + if player.player_id == self.player_id: + self.name = name + self.logger = get_logger(self.name) + break try: self.initialize() @@ -107,7 +122,7 @@ def _try_initialize(self): exit() self._initialized_bot = True - self._game_interface.send_init_complete() + self._game_interface.send_msg(flat.InitComplete()) def _handle_match_config(self, match_config: flat.MatchConfiguration): self.match_config = match_config @@ -124,7 +139,7 @@ def _handle_controllable_team_info( ): self.team = player_mappings.team controllable = player_mappings.controllables[0] - self.spawn_id = controllable.spawn_id + self.player_id = controllable.identifier self.index = controllable.index self._has_player_mapping = True @@ -154,7 +169,7 @@ def _packet_processor(self, packet: flat.GamePacket): return player_input = flat.PlayerInput(self.index, controller) - self._game_interface.send_player_input(player_input) + self._game_interface.send_msg(player_input) def _run(self): running = True @@ -237,7 +252,7 @@ def send_match_comm( - `display`: The message to be displayed in the game in "quick chat", or `None` to display nothing. - `team_only`: If True, only your team will receive the message. """ - self._game_interface.send_match_comm( + self._game_interface.send_msg( flat.MatchComm( self.index, self.team, @@ -261,7 +276,7 @@ def set_game_state( """ game_state = fill_desired_game_state(balls, cars, match_info, commands) - self._game_interface.send_game_state(game_state) + self._game_interface.send_msg(game_state) def set_loadout(self, loadout: flat.PlayerLoadout, index: Optional[int] = None): """ @@ -270,9 +285,7 @@ def set_loadout(self, loadout: flat.PlayerLoadout, index: Optional[int] = None): Does nothing if called outside `initialize` unless state setting is enabled in which case it respawns the car with the new loadout. """ - self._game_interface.send_set_loadout( - flat.SetLoadout(index or self.index, loadout) - ) + self._game_interface.send_msg(flat.SetLoadout(index or self.index, loadout)) def initialize(self): """ diff --git a/rlbot/managers/hivemind.py b/rlbot/managers/hivemind.py index c9ed52a..1cf1759 100644 --- a/rlbot/managers/hivemind.py +++ b/rlbot/managers/hivemind.py @@ -14,6 +14,8 @@ from rlbot.utils import fill_desired_game_state from rlbot.utils.logging import DEFAULT_LOGGER, get_logger +WARNED_SPAWN_ID_DEPRECATED = False + class Hivemind: """ @@ -30,7 +32,18 @@ class Hivemind: team: int = -1 indices: list[int] = [] names: list[str] = [] - spawn_ids: list[int] = [] + player_ids: list[int] = [] + + @property + def spawn_ids(self) -> list[int]: + global WARNED_SPAWN_ID_DEPRECATED + if not WARNED_SPAWN_ID_DEPRECATED: + WARNED_SPAWN_ID_DEPRECATED = True + self._logger.warning( + "'spawn_id' getter accessed, which is deprecated in favor of 'player_id'." + ) + + return self.player_ids match_config = flat.MatchConfiguration() """ @@ -92,12 +105,14 @@ def _try_initialize(self): return # Search match settings for our spawn ids - for spawn_id in self.spawn_ids: + for player_id in self.player_ids: for player in self.match_config.player_configurations: - if player.spawn_id == spawn_id: - self.names.append(player.name) - self.loggers.append(get_logger(player.name)) - break + match player.variety.item: + case flat.CustomBot(name): + if player.player_id == player_id: + self.names.append(name) + self.loggers.append(get_logger(name)) + break try: self.initialize() @@ -111,7 +126,7 @@ def _try_initialize(self): exit() self._initialized_bot = True - self._game_interface.send_init_complete() + self._game_interface.send_msg(flat.InitComplete()) def _handle_match_config(self, match_config: flat.MatchConfiguration): self.match_config = match_config @@ -128,7 +143,7 @@ def _handle_controllable_team_info( ): self.team = player_mappings.team for controllable in player_mappings.controllables: - self.spawn_ids.append(controllable.spawn_id) + self.player_ids.append(controllable.identifier) self.indices.append(controllable.index) self._has_player_mapping = True @@ -166,7 +181,7 @@ def _packet_processor(self, packet: flat.GamePacket): ", ".join(map(str, self.indices)), ) player_input = flat.PlayerInput(index, controller) - self._game_interface.send_player_input(player_input) + self._game_interface.send_msg(player_input) def _run(self): running = True @@ -251,7 +266,7 @@ def send_match_comm( - `display`: The message to be displayed in the game in "quick chat", or `None` to display nothing. - `team_only`: If True, only your team will receive the message. """ - self._game_interface.send_match_comm( + self._game_interface.send_msg( flat.MatchComm( index, self.team, @@ -275,7 +290,7 @@ def set_game_state( """ game_state = fill_desired_game_state(balls, cars, match_info, commands) - self._game_interface.send_game_state(game_state) + self._game_interface.send_msg(game_state) def set_loadout(self, loadout: flat.PlayerLoadout, index: int): """ @@ -284,7 +299,7 @@ def set_loadout(self, loadout: flat.PlayerLoadout, index: int): Does nothing if called outside `initialize` unless state setting is enabled in which case it respawns the car with the new loadout. """ - self._game_interface.send_set_loadout(flat.SetLoadout(index, loadout)) + self._game_interface.send_msg(flat.SetLoadout(index, loadout)) def initialize(self): """ diff --git a/rlbot/managers/match.py b/rlbot/managers/match.py index af2cea5..c90decb 100644 --- a/rlbot/managers/match.py +++ b/rlbot/managers/match.py @@ -121,7 +121,7 @@ def start_match( self.rlbot_interface.start_match(config) if not self.initialized: - self.rlbot_interface.send_init_complete() + self.rlbot_interface.send_msg(flat.InitComplete()) self.initialized = True if wait_for_start: @@ -152,7 +152,7 @@ def set_game_state( """ game_state = fill_desired_game_state(balls, cars, match_info, commands) - self.rlbot_interface.send_game_state(game_state) + self.rlbot_interface.send_msg(game_state) def shut_down(self, use_force_if_necessary: bool = True): """ diff --git a/rlbot/managers/rendering.py b/rlbot/managers/rendering.py index 082e25e..31e697d 100644 --- a/rlbot/managers/rendering.py +++ b/rlbot/managers/rendering.py @@ -55,9 +55,7 @@ class Renderer: _screen_height_factor = 1.0 def __init__(self, game_interface: SocketRelay): - self._render_group: Callable[[flat.RenderGroup], None] = ( - game_interface.send_render_group - ) + self._render_group: Callable[[flat.RenderGroup], None] = game_interface.send_msg self._remove_render_group: Callable[[int], None] = ( game_interface.remove_render_group @@ -85,12 +83,18 @@ def create_color_hsv(hue: float, saturation: float, value: float) -> flat.Color: t = value * (1 - (1 - f) * saturation) match i % 6: - case 0: r, g, b = value, t, p - case 1: r, g, b = q, value, p - case 2: r, g, b = p, value, t - case 3: r, g, b = p, q, value - case 4: r, g, b = t, p, value - case 5: r, g, b = value, p, q + case 0: + r, g, b = value, t, p + case 1: + r, g, b = q, value, p + case 2: + r, g, b = p, value, t + case 3: + r, g, b = p, q, value + case 4: + r, g, b = t, p, value + case 5: + r, g, b = value, p, q return flat.Color(math.floor(r * 255), math.floor(g * 255), math.floor(b * 255)) diff --git a/rlbot/managers/script.py b/rlbot/managers/script.py index 613b242..274c999 100644 --- a/rlbot/managers/script.py +++ b/rlbot/managers/script.py @@ -84,7 +84,7 @@ def _try_initialize(self): exit() self._initialized_script = True - self._game_interface.send_init_complete() + self._game_interface.send_msg(flat.InitComplete()) def _handle_match_config(self, match_config: flat.MatchConfiguration): self.match_config = match_config @@ -204,7 +204,7 @@ def send_match_comm( - `display`: The message to be displayed in the game in "quick chat", or `None` to display nothing. - `team_only`: If True, only your team will receive the message. For scripts, this means other scripts. """ - self._game_interface.send_match_comm( + self._game_interface.send_msg( flat.MatchComm( self.index, 2, @@ -228,7 +228,7 @@ def set_game_state( """ game_state = fill_desired_game_state(balls, cars, match_info, commands) - self._game_interface.send_game_state(game_state) + self._game_interface.send_msg(game_state) def set_loadout(self, loadout: flat.PlayerLoadout, index: int): """ @@ -236,7 +236,7 @@ def set_loadout(self, loadout: flat.PlayerLoadout, index: int): Will be ignored if called when state setting is disabled. """ - self._game_interface.send_set_loadout(flat.SetLoadout(index, loadout)) + self._game_interface.send_msg(flat.SetLoadout(index, loadout)) def initialize(self): """ diff --git a/rlbot/version.py b/rlbot/version.py index 2bcdf27..a7ed31d 100644 --- a/rlbot/version.py +++ b/rlbot/version.py @@ -1 +1 @@ -__version__ = "2.0.0-beta.43" +__version__ = "2.0.0-beta.44" diff --git a/tests/render_test/render.py b/tests/render_test/render.py index edbf7f4..df1c257 100644 --- a/tests/render_test/render.py +++ b/tests/render_test/render.py @@ -30,10 +30,12 @@ def handle_packet(self, packet: flat.GamePacket): self.do_render(radius) - self.renderer.begin_rendering('tick') - hsv = self.renderer.create_color_hsv(packet.match_info.seconds_elapsed * 0.1, 1.0, 1.0) + self.renderer.begin_rendering("tick") + hsv = self.renderer.create_color_hsv( + packet.match_info.seconds_elapsed * 0.1, 1.0, 1.0 + ) self.renderer.set_resolution(1920, 1080) - self.renderer.draw_string_2d('HSV 300px 50px', 300, 50, 1.0, hsv) + self.renderer.draw_string_2d("HSV 300px 50px", 300, 50, 1.0, hsv) self.renderer.set_resolution(1, 1) self.renderer.end_rendering() @@ -74,9 +76,7 @@ def do_render(self, radius: float): CarAnchor(0, Vector3(200, 0, 0)), 0.02, 0.02, self.renderer.blue ) - self.renderer.draw_rect_2d( - 0.75, 0.75, 0.1, 0.1, Color(150, 30, 100), centered=False - ) + self.renderer.draw_rect_2d(0.75, 0.75, 0.1, 0.1, Color(150, 30, 100)) self.renderer.draw_rect_2d(0.75, 0.75, 0.1, 0.1, self.renderer.black) for hkey, h in { "left": flat.TextHAlign.Left,