From 74efdbc171215ad98ef4aa210c6708ba3a9e6697 Mon Sep 17 00:00:00 2001 From: Virx Date: Tue, 29 Jul 2025 15:24:35 -0400 Subject: [PATCH 1/6] Add `get_human` --- rlbot/config.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/rlbot/config.py b/rlbot/config.py index 6d60049..771aa0e 100644 --- a/rlbot/config.py +++ b/rlbot/config.py @@ -3,7 +3,6 @@ from typing import Any, Literal import rlbot.flat as flat -from rlbot.utils.logging import DEFAULT_LOGGER as logger from rlbot.utils.os_detector import CURRENT_OS, OS @@ -112,7 +111,7 @@ def load_match_config(config_path: Path | str) -> flat.MatchConfiguration: ) ) case "human": - players.append(flat.PlayerConfiguration(flat.Human(), team, 0)) + players.append(get_human(team)) case t: raise ConfigParsingException( f"Invalid player type {repr(t)} for player {len(players)}." @@ -323,6 +322,13 @@ def load_psyonix_config( ) +def get_human(team: int) -> flat.PlayerConfiguration: + """ + Creates a `PlayerConfiguration` for a human player on the given team. + """ + return flat.PlayerConfiguration(flat.Human(), team) + + def load_script_config(path: Path | str) -> flat.ScriptConfiguration: """ Reads the script toml file at the provided path and creates a `ScriptConfiguration` from it. From 8257d0f9975c2b32b1f55c0f8b317d514e6c1205 Mon Sep 17 00:00:00 2001 From: Virx Date: Tue, 29 Jul 2025 15:25:03 -0400 Subject: [PATCH 2/6] Fix script in py 3.11 --- tests/cfg_to_toml.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/cfg_to_toml.py b/tests/cfg_to_toml.py index 4a1b560..d98e21f 100644 --- a/tests/cfg_to_toml.py +++ b/tests/cfg_to_toml.py @@ -113,9 +113,9 @@ def convert_to_toml(self) -> dict[str, dict[str, Any]]: toml_dict["settings"] = self._convert_settings() toml_dict["details"] = self._convert_details() - toml_dict["settings"][ - "agent_id" - ] = f"{toml_dict["details"]["developer"]}/{toml_dict["settings"]["name"]}" + toml_dict["settings"]["agent_id"] = ( + f"{toml_dict['details']['developer']}/{toml_dict['settings']['name']}" + ) return toml_dict From 13b1654aa49a2da0b87f2fcc782523e68e0a64a8 Mon Sep 17 00:00:00 2001 From: Virx Date: Tue, 29 Jul 2025 15:39:02 -0400 Subject: [PATCH 3/6] Migrate from black to ruff --- README.md | 7 ++++--- pyproject.toml | 9 +++++++++ rlbot/__init__.py | 2 +- rlbot/interface.py | 6 +++--- rlbot/utils/maps.py | 4 ++-- tests/fashion/bot.py | 2 +- tests/nexto/bot.py | 1 - tests/nexto/nexto_obs.py | 3 +-- tests/run_forever.py | 1 - tests/runner.py | 3 +-- 10 files changed, 22 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 9f6c354..2c00216 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,11 @@ The following is how to setup a development environment for this project, NOT ho - `pip uninstall rlbot_flatbuffers` - `pip install --editable ` -This project is formatted using [Black](https://github.com/psf/black). +This project is formatted using [Ruff](https://docs.astral.sh/ruff/formatter/). -- Install: `pip install black`. -- Use: `black .` +- Install: `pip install ruff`. +- Sort imports: `ruff check --select I --fix` +- Format code: `ruff format` ## Testing diff --git a/pyproject.toml b/pyproject.toml index 21019fe..2372924 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,3 +23,12 @@ Repository = "https://github.com/RLBot/python-interface" [tool.setuptools.dynamic] version = {attr = "rlbot.version.__version__"} + +[dependency-groups] +dev = [ + "ruff>=0.12.5", + "toml>=0.10.2", +] + +[tool.ruff.format] +docstring-code-format = true diff --git a/rlbot/__init__.py b/rlbot/__init__.py index 58f3ace..614d684 100644 --- a/rlbot/__init__.py +++ b/rlbot/__init__.py @@ -1 +1 @@ -from .version import __version__ +from .version import __version__ as __version__ diff --git a/rlbot/interface.py b/rlbot/interface.py index a4ffda4..a00c8d9 100644 --- a/rlbot/interface.py +++ b/rlbot/interface.py @@ -323,7 +323,7 @@ def disconnect(self): self.logger.critical("RLBot is not responding to our disconnect request!?") self._running = False - assert ( - not self._running - ), "Disconnect request or timeout should have set self._running to False" + assert not self._running, ( + "Disconnect request or timeout should have set self._running to False" + ) self.is_connected = False diff --git a/rlbot/utils/maps.py b/rlbot/utils/maps.py index 689e6ce..e397772 100644 --- a/rlbot/utils/maps.py +++ b/rlbot/utils/maps.py @@ -83,7 +83,7 @@ "FuturaGarden": "UF_Day_P", "DFHStadium_Anniversary": "stadium_10a_p", "Holyfield": "Labs_Holyfield_Space_P", - "DriftWoods_Night": "woods_night_p" + "DriftWoods_Night": "woods_night_p", } STANDARD_MAPS = [ @@ -138,5 +138,5 @@ "Neotokyo_Arcade", "FuturaGarden", "DFHStadium_Anniversary", - "DriftWoods_Night" + "DriftWoods_Night", ] diff --git a/tests/fashion/bot.py b/tests/fashion/bot.py index 553a385..d1bf4bc 100644 --- a/tests/fashion/bot.py +++ b/tests/fashion/bot.py @@ -50,7 +50,7 @@ def get_output(self, packet: flat.GamePacket) -> flat.ControllerState: ), ) - self.logger.info(f"State setting new loadout") + self.logger.info("State setting new loadout") self.set_loadout(loadout) self.last_tick = packet.match_info.frame_num diff --git a/tests/nexto/bot.py b/tests/nexto/bot.py index c4af5a9..a57ac28 100644 --- a/tests/nexto/bot.py +++ b/tests/nexto/bot.py @@ -369,7 +369,6 @@ def toxicity(self, packet: GamePacket): return for p in human_opps: - d = math.sqrt( (p.physics.location.x - bad_goal[0]) ** 2 + (p.physics.location.y - bad_goal[1]) ** 2 diff --git a/tests/nexto/nexto_obs.py b/tests/nexto/nexto_obs.py index b54c3df..8f22669 100644 --- a/tests/nexto/nexto_obs.py +++ b/tests/nexto/nexto_obs.py @@ -286,8 +286,7 @@ def batched_build_obs(self, encoded_states: np.ndarray): for i in range(n_players): encoded_player = encoded_states[ :, - players_start_index - + i * player_length : players_start_index + players_start_index + i * player_length : players_start_index + (i + 1) * player_length, ] diff --git a/tests/run_forever.py b/tests/run_forever.py index 749772a..830a85a 100644 --- a/tests/run_forever.py +++ b/tests/run_forever.py @@ -13,7 +13,6 @@ if __name__ == "__main__": match_manager = MatchManager(RLBOT_SERVER_FOLDER) - match_manager.ensure_server_started() current_map = -1 diff --git a/tests/runner.py b/tests/runner.py index d93d930..e74390c 100644 --- a/tests/runner.py +++ b/tests/runner.py @@ -16,7 +16,6 @@ match_manager = MatchManager(RLBOT_SERVER_FOLDER) try: - match_manager.ensure_server_started() match_manager.start_match(MATCH_CONFIG_PATH) logger.info("Waiting before shutdown...") @@ -25,7 +24,7 @@ except KeyboardInterrupt: logger.warning("Shutting down early due to interrupt") except Exception: - logger.critical(f"Shutting down early due to the following error:") + logger.critical("Shutting down early due to the following error:") print_exc() match_manager.shut_down() From 239215ac4f2d3f1006fb3abf5d7c111fce86473e Mon Sep 17 00:00:00 2001 From: Virx Date: Thu, 31 Jul 2025 00:20:42 -0400 Subject: [PATCH 4/6] Move `self.rlbot_interface.run(...)` into `self.connect(...)` --- .gitignore | 1 + rlbot/managers/match.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index bcbe491..daa991a 100644 --- a/.gitignore +++ b/.gitignore @@ -175,6 +175,7 @@ pyrightconfig.json # End of https://www.toptal.com/developers/gitignore/api/python +uv.lock .vscode/ *.obj RLBotServer* diff --git a/rlbot/managers/match.py b/rlbot/managers/match.py index d6e3692..a6fbb0a 100644 --- a/rlbot/managers/match.py +++ b/rlbot/managers/match.py @@ -93,6 +93,7 @@ def connect( rlbot_server_ip=rlbot_server_ip, rlbot_server_port=rlbot_server_port or self.rlbot_server_port, ) + self.rlbot_interface.run(background_thread=True) def wait_for_first_packet(self): while self.packet is None or self.packet.match_info.match_phase in { @@ -122,7 +123,6 @@ def start_match( wants_ball_predictions=False, close_between_matches=False, ) - self.rlbot_interface.run(background_thread=True) self.rlbot_interface.start_match(config) @@ -136,7 +136,7 @@ def start_match( def disconnect(self): """ - Disconnect from the RLBotServer. + Disconnect from RLBotServer. Note that the server will continue running as long as Rocket League does. """ self.rlbot_interface.disconnect() @@ -167,8 +167,8 @@ def shut_down(self, use_force_if_necessary: bool = True): self.logger.info("Shutting down RLBot...") - # In theory this is all we need for the server to cleanly shut itself down try: + # In theory this is all we need for the server to cleanly shut itself down self.rlbot_interface.stop_match(shutdown_server=True) except BrokenPipeError: match gateway.find_server_process(self.main_executable_name)[0]: From 68c39095c626c8ebbac5cc45efcb3ad3b7d8b133 Mon Sep 17 00:00:00 2001 From: Virx Date: Thu, 31 Jul 2025 00:21:29 -0400 Subject: [PATCH 5/6] Infinite match series testing --- tests/atba/atba.bot.toml | 2 +- tests/atba/atba.py | 23 +++-------- tests/many_match.py | 84 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 18 deletions(-) create mode 100644 tests/many_match.py diff --git a/tests/atba/atba.bot.toml b/tests/atba/atba.bot.toml index a2f5885..efe7374 100644 --- a/tests/atba/atba.bot.toml +++ b/tests/atba/atba.bot.toml @@ -14,7 +14,7 @@ root_dir = "" run_command = "..\\..\\venv\\Scripts\\python atba.py" # The command RLBot will call to start your bot on linux # If this isn't set, RLBot may attempt to run the Windows command under Wine -run_command_linux = "../../venv/bin/python atba.py" +run_command_linux = "../../.venv/bin/python atba.py" [details] description = "Made possible by RLBot" diff --git a/tests/atba/atba.py b/tests/atba/atba.py index a986804..4d5cc3c 100644 --- a/tests/atba/atba.py +++ b/tests/atba/atba.py @@ -1,7 +1,6 @@ from __future__ import annotations import math -from typing import Optional from rlbot import flat from rlbot.managers import Bot @@ -67,9 +66,9 @@ def __init__(self): def initialize(self): self.logger.info("Initializing agent!") - - num_boost_pads = len(self.field_info.boost_pads) - self.logger.info(f"There are {num_boost_pads} boost pads on the field.") + self.logger.info( + f"There are {len(self.field_info.boost_pads)} boost pads and {len(self.field_info.goals)} goals on the field." + ) if self.rendering: self.renderer.begin_rendering("custom one-time rendering group") @@ -83,16 +82,6 @@ def initialize(self): ) self.renderer.end_rendering() - def handle_match_comm( - self, - index: int, - team: int, - content: bytes, - display: Optional[str], - team_only: bool, - ): - self.logger.info(f"Received match communication from index {index}! {display}") - def get_output(self, packet: flat.GamePacket) -> flat.ControllerState: if self.rendering: self.test_rendering(packet) @@ -110,10 +99,10 @@ def get_output(self, packet: flat.GamePacket) -> flat.ControllerState: if self.state_setting: self.test_state_setting(packet) - if self.match_comms: + if self.match_comms and packet.match_info.match_phase == flat.MatchPhase.Active: # Limit packet spam if packet.match_info.frame_num - self.last_send >= 360: - self.send_match_comm(b"", "Hello world!", True) + self.send_match_comm(b"", "Hello world!", False) self.last_send = packet.match_info.frame_num ball_location = Vector2(packet.balls[0].physics.location) @@ -145,7 +134,7 @@ def test_state_setting(self, packet: flat.GamePacket): i: flat.DesiredCarState( flat.DesiredPhysics(rotation=flat.RotatorPartial(yaw=0)) ) - for i, car in enumerate(packet.players) + for i in range(len(packet.players)) }, ) diff --git a/tests/many_match.py b/tests/many_match.py new file mode 100644 index 0000000..90450af --- /dev/null +++ b/tests/many_match.py @@ -0,0 +1,84 @@ +from pathlib import Path +from time import sleep + +from rlbot import flat +from rlbot.config import load_player_config +from rlbot.managers import MatchManager + +DIR = Path(__file__).parent + +BOT_PATH = DIR / "atba/atba.bot.toml" +RLBOT_SERVER_FOLDER = DIR / "../../core/RLBotCS/bin/Release/" + +num_comms = set() + + +def handle_match_comm(comm: flat.MatchComm): + global num_comms + if comm.team < 2: + num_comms.add(comm.index) + + +if __name__ == "__main__": + match_manager = MatchManager(RLBOT_SERVER_FOLDER) + match_manager.rlbot_interface.match_comm_handlers.append(handle_match_comm) + match_manager.ensure_server_started() + match_manager.connect( + wants_match_communications=True, + wants_ball_predictions=False, + close_between_matches=False, + ) + + current_map = -1 + + blue_bot = load_player_config(BOT_PATH, 0) + orange_bot = load_player_config(BOT_PATH, 1) + + match_settings = flat.MatchConfiguration( + launcher=flat.Launcher.Steam, + auto_start_agents=True, + wait_for_agents=True, + existing_match_behavior=flat.ExistingMatchBehavior.Restart, + game_map_upk="Stadium_P", + instant_start=True, + enable_state_setting=True, + player_configurations=[ + blue_bot, + blue_bot, + blue_bot, + blue_bot, + blue_bot, + orange_bot, + orange_bot, + orange_bot, + orange_bot, + orange_bot, + ], + ) + + num_games = 0 + paused = False + + while not paused: + num_games += 1 + print(f"Starting match # {num_games}") + + match_manager.start_match(match_settings, ensure_server_started=False) + # when calling start_match, by default it will wait for the first packet + assert match_manager.packet is not None + + sleep(2) + num_comms.clear() + while len(num_comms) < 10: + # give an extra 5 seconds for the match to start before calling it a failure + if ( + match_manager.packet.match_info.match_phase == flat.MatchPhase.Active + and match_manager.packet.match_info.game_time_remaining < 60 * 4 + 55 + ): + match_manager.set_game_state(commands=["Pause"]) + paused = True + break + sleep(1) + + print("Failed to start match. Paused and exiting.") + match_manager.disconnect() From 6435cf854d43ff38bbafe9fc0c7bba50c1bad297 Mon Sep 17 00:00:00 2001 From: Virx Date: Thu, 31 Jul 2025 00:21:33 -0400 Subject: [PATCH 6/6] Bump version --- rlbot/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rlbot/version.py b/rlbot/version.py index 5a6dd25..1a64d22 100644 --- a/rlbot/version.py +++ b/rlbot/version.py @@ -1 +1 @@ -__version__ = "2.0.0-beta.47" +__version__ = "2.0.0-beta.48"