diff --git a/pyproject.toml b/pyproject.toml index 2372924..2cfdce7 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.17.0", + "rlbot_flatbuffers~=0.18.2", "psutil==7.*", ] readme = "README.md" @@ -26,7 +26,7 @@ version = {attr = "rlbot.version.__version__"} [dependency-groups] dev = [ - "ruff>=0.12.5", + "ruff>=0.13.0", "toml>=0.10.2", ] diff --git a/rlbot/interface.py b/rlbot/interface.py index a00c8d9..a4d0d3d 100644 --- a/rlbot/interface.py +++ b/rlbot/interface.py @@ -2,10 +2,11 @@ import time from collections.abc import Callable from enum import IntEnum +from ipaddress import ip_address from pathlib import Path -from socket import IPPROTO_TCP, TCP_NODELAY, socket +from socket import AF_INET, AF_INET6, IPPROTO_TCP, TCP_NODELAY +from socket import socket as sock from threading import Thread -from typing import Optional from rlbot import flat from rlbot.utils.logging import get_logger @@ -32,6 +33,8 @@ class SocketRelay: from `rlbot.managers`. """ + can_render = False + """Indicates whether RLBotServer has given permission to send rendering commands""" is_connected = False _running = False """Indicates whether a messages are being handled by the `run` loop (potentially in a background thread)""" @@ -45,26 +48,23 @@ class SocketRelay: controllable_team_info_handlers: list[ Callable[[flat.ControllableTeamInfo], None] ] = [] - rendering_status_handlers: list[Callable[[flat.RenderingStatus], None]] = [] - raw_handlers: list[Callable[[flat.CoreMessage], None]] = [] + raw_handlers: list[Callable[[flat.CorePacket], None]] = [] + + socket: sock | None = None def __init__( self, agent_id: str, connection_timeout: float = 120, - logger: Optional[logging.Logger] = None, + logger: logging.Logger | None = None, ): self.agent_id = agent_id self.connection_timeout = connection_timeout self.logger = get_logger("interface") if logger is None else logger - self.socket = socket() - - # Allow sending packets before getting a response from core - self.socket.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) - def __del__(self): - self.socket.close() + if self.socket is not None: + self.socket.close() @staticmethod def _int_to_bytes(val: int) -> bytes: @@ -74,6 +74,8 @@ def _read_int(self) -> int: return int.from_bytes(self._read_exact(2), "big") def _read_exact(self, n: int) -> bytes: + assert self.socket is not None, "Socket has not been established" + buff = bytearray(n) view = memoryview(buff) @@ -90,6 +92,7 @@ def read_message(self) -> bytes: return self._read_exact(size) def send_bytes(self, data: bytes): + assert self.socket is not None, "Socket has not been established" assert self.is_connected, "Connection has not been established" size = len(data) @@ -158,7 +161,13 @@ def connect( """ assert not self.is_connected, "Connection has already been established" + # Check if the IP is IPv4 or IPv6 and configure the socket accordingly + family = AF_INET if ip_address(rlbot_server_ip).version == 4 else AF_INET6 + self.socket = sock(family) self.socket.settimeout(self.connection_timeout) + # Allow sending packets before getting a response from core + self.socket.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) + try: begin_time = time.time() next_warning = 10 @@ -239,6 +248,7 @@ def handle_incoming_messages(self, blocking: bool = False) -> MsgHandlingResult: Second boolean returns true if there might be more messages to handle without a delay. """ + assert self.socket is not None, "Socket has not been established" assert self.is_connected, "Connection has not been established" try: self.socket.setblocking(blocking) @@ -272,12 +282,12 @@ def handle_incoming_message(self, incoming_message: bytes) -> MsgHandlingResult: Returns True if the message was NOT a shutdown request """ - flatbuffer = flat.CorePacket.unpack(incoming_message).message + flatbuffer = flat.CorePacket.unpack(incoming_message) for raw_handler in self.raw_handlers: raw_handler(flatbuffer) - match flatbuffer.item: + match flatbuffer.message: case flat.DisconnectSignal(): return MsgHandlingResult.TERMINATED case flat.GamePacket() as packet: @@ -287,6 +297,10 @@ def handle_incoming_message(self, incoming_message: bytes) -> MsgHandlingResult: for handler in self.field_info_handlers: handler(field_info) case flat.MatchConfiguration() as match_config: + self.can_render = ( + match_config.enable_rendering == flat.DebugRendering.OnByDefault + ) + for handler in self.match_config_handlers: handler(match_config) case flat.MatchComm() as match_comm: @@ -299,8 +313,7 @@ def handle_incoming_message(self, incoming_message: bytes) -> MsgHandlingResult: for handler in self.controllable_team_info_handlers: handler(controllable_team_info) case flat.RenderingStatus() as rendering_status: - for handler in self.rendering_status_handlers: - handler(rendering_status) + self.can_render = rendering_status.status case _: self.logger.warning( "Received unknown message type: %s", diff --git a/rlbot/managers/bot.py b/rlbot/managers/bot.py index 43e6984..935eb04 100644 --- a/rlbot/managers/bot.py +++ b/rlbot/managers/bot.py @@ -1,6 +1,5 @@ import os from traceback import print_exc -from typing import Optional from rlbot import flat from rlbot.interface import ( @@ -13,8 +12,6 @@ from rlbot.utils import fill_desired_game_state from rlbot.utils.logging import DEFAULT_LOGGER, get_logger -WARNED_SPAWN_ID_DEPRECATED = False - class Bot: """ @@ -32,17 +29,6 @@ class Bot: name: str = "" 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() """ Contains info about what map you're on, game mode, mutators, etc. @@ -63,10 +49,10 @@ def spawn_id(self) -> int: _has_field_info = False _has_player_mapping = False - _latest_packet: Optional[flat.GamePacket] = None + _latest_packet: flat.GamePacket | None = None _latest_prediction = flat.BallPrediction() - def __init__(self, default_agent_id: Optional[str] = None): + def __init__(self, default_agent_id: str | None = None): agent_id = os.environ.get("RLBOT_AGENT_ID") or default_agent_id if agent_id is None: @@ -90,9 +76,6 @@ def __init__(self, default_agent_id: Optional[str] = None): self._handle_controllable_team_info ) self._game_interface.packet_handlers.append(self._handle_packet) - self._game_interface.rendering_status_handlers.append( - self.rendering_status_update - ) self.renderer = Renderer(self._game_interface) @@ -107,7 +90,7 @@ def _try_initialize(self): return for player in self.match_config.player_configurations: - match player.variety.item: + match player.variety: case flat.CustomBot(name): if player.player_id == self.player_id: self.name = name @@ -134,9 +117,6 @@ def _try_initialize(self): def _handle_match_config(self, match_config: flat.MatchConfiguration): self.match_config = match_config self._has_match_settings = True - self.can_render = ( - match_config.enable_rendering == flat.DebugRendering.OnByDefault - ) self._try_initialize() @@ -241,19 +221,10 @@ def _handle_match_communication(self, match_comm: flat.MatchComm): match_comm.team_only, ) - def rendering_status_update(self, update: flat.RenderingStatus): - """ - Called when the server sends a rendering status update for ANY bot or script. - - By default, this will update `self.renderer.can_render` if appropriate. - """ - if update.is_bot and update.index == self.index: - self.renderer.can_render = update.status - def update_rendering_status( self, status: bool, - index: Optional[int] = None, + index: int | None = None, is_bot: bool = True, ): """ @@ -274,7 +245,7 @@ def handle_match_comm( index: int, team: int, content: bytes, - display: Optional[str], + display: str | None, team_only: bool, ): """ @@ -284,7 +255,7 @@ def handle_match_comm( """ def send_match_comm( - self, content: bytes, display: Optional[str] = None, team_only: bool = False + self, content: bytes, display: str | None = None, team_only: bool = False ): """ Emits a match communication message to other bots and scripts. @@ -307,7 +278,7 @@ def set_game_state( self, balls: dict[int, flat.DesiredBallState] = {}, cars: dict[int, flat.DesiredCarState] = {}, - match_info: Optional[flat.DesiredMatchInfo] = None, + match_info: flat.DesiredMatchInfo | None = None, commands: list[str] = [], ): """ @@ -319,7 +290,7 @@ def set_game_state( game_state = fill_desired_game_state(balls, cars, match_info, commands) self._game_interface.send_msg(game_state) - def set_loadout(self, loadout: flat.PlayerLoadout, index: Optional[int] = None): + def set_loadout(self, loadout: flat.PlayerLoadout, index: int | None = None): """ Sets the loadout of a bot. Can be used to select or generate a loadout for the match when called inside `initialize`. diff --git a/rlbot/managers/hivemind.py b/rlbot/managers/hivemind.py index 311ac25..53ca25c 100644 --- a/rlbot/managers/hivemind.py +++ b/rlbot/managers/hivemind.py @@ -1,7 +1,6 @@ import os from logging import Logger from traceback import print_exc -from typing import Optional from rlbot import flat from rlbot.interface import ( @@ -14,8 +13,6 @@ from rlbot.utils import fill_desired_game_state from rlbot.utils.logging import DEFAULT_LOGGER, get_logger -WARNED_SPAWN_ID_DEPRECATED = False - class Hivemind: """ @@ -34,17 +31,6 @@ class Hivemind: names: list[str] = [] 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() """ Contains info about what map you're on, game mode, mutators, etc. @@ -65,10 +51,10 @@ def spawn_ids(self) -> list[int]: _has_field_info = False _has_player_mapping = False - _latest_packet: Optional[flat.GamePacket] = None + _latest_packet: flat.GamePacket | None = None _latest_prediction = flat.BallPrediction() - def __init__(self, default_agent_id: Optional[str] = None): + def __init__(self, default_agent_id: str | None = None): agent_id = os.environ.get("RLBOT_AGENT_ID") or default_agent_id if agent_id is None: @@ -92,9 +78,6 @@ def __init__(self, default_agent_id: Optional[str] = None): self._handle_controllable_team_info ) self._game_interface.packet_handlers.append(self._handle_packet) - self._game_interface.rendering_status_handlers.append( - self.rendering_status_update - ) self.renderer = Renderer(self._game_interface) @@ -110,7 +93,7 @@ def _try_initialize(self): # Search match configuration for our spawn ids for player_id in self.player_ids: for player in self.match_config.player_configurations: - match player.variety.item: + match player.variety: case flat.CustomBot(name): if player.player_id == player_id: self.names.append(name) @@ -239,19 +222,10 @@ def run( self.retire() del self._game_interface - def rendering_status_update(self, update: flat.RenderingStatus): - """ - Called when the server sends a rendering status update for ANY bot or script. - - By default, this will update `self.renderer.can_render` if appropriate. - """ - if update.is_bot and update.index in self.indices: - self.renderer.can_render = update.status - def update_rendering_status( self, status: bool, - index: Optional[int] = None, + index: int | None = None, is_bot: bool = True, ): """ @@ -283,7 +257,7 @@ def handle_match_comm( index: int, team: int, content: bytes, - display: Optional[str], + display: str | None, team_only: bool, ): """ @@ -296,7 +270,7 @@ def send_match_comm( self, index: int, content: bytes, - display: Optional[str] = None, + display: str | None = None, team_only: bool = False, ): """ @@ -320,7 +294,7 @@ def set_game_state( self, balls: dict[int, flat.DesiredBallState] = {}, cars: dict[int, flat.DesiredCarState] = {}, - match_info: Optional[flat.DesiredMatchInfo] = None, + match_info: flat.DesiredMatchInfo | None = None, commands: list[str] = [], ): """ diff --git a/rlbot/managers/match.py b/rlbot/managers/match.py index fdaf125..de05208 100644 --- a/rlbot/managers/match.py +++ b/rlbot/managers/match.py @@ -1,6 +1,7 @@ +import os +import stat from pathlib import Path from time import sleep -from typing import Optional import psutil @@ -8,7 +9,7 @@ from rlbot.interface import RLBOT_SERVER_IP, RLBOT_SERVER_PORT, SocketRelay from rlbot.utils import fill_desired_game_state, gateway from rlbot.utils.logging import DEFAULT_LOGGER -from rlbot.utils.os_detector import MAIN_EXECUTABLE_NAME +from rlbot.utils.os_detector import CURRENT_OS, OS, RLBOT_SERVER_NAME class MatchManager: @@ -17,18 +18,21 @@ class MatchManager: """ logger = DEFAULT_LOGGER - packet: Optional[flat.GamePacket] = None - rlbot_server_process: Optional[psutil.Process] = None + packet: flat.GamePacket | None = None + rlbot_server_process: psutil.Process | None = None rlbot_server_port = RLBOT_SERVER_PORT initialized = False - def __init__( - self, - main_executable_path: Optional[Path] = None, - main_executable_name: str = MAIN_EXECUTABLE_NAME, - ): - self.main_executable_path = main_executable_path - self.main_executable_name = main_executable_name + def __init__(self, rlbot_server_path: Path | None = None): + """ + Initialize a MatchManager. + Args: + rlbot_server_path: The path to the RLBotServer executable. The path is used to launch the server + if it is not already running. If set to None, the RLBotServer in the current working directory will + be used, and otherwise the globally installed RLBotServer will be used. + """ + + self.rlbot_server_path = rlbot_server_path self.rlbot_interface: SocketRelay = SocketRelay("") self.rlbot_interface.packet_handlers.append(self._packet_reporter) @@ -42,27 +46,75 @@ def __exit__(self, exc_type, exc_val, exc_tb) -> None: def ensure_server_started(self): """ Ensures that RLBotServer is running, starting it if it is not. + If no path to an RLBotServer executable was passed during initialization of the MatchManager, + the function will use the RLBotServer executable in the current working directory, if any, or + otherwise the global installed RLBotServer will be used, if any. """ + exe_name = ( + self.rlbot_server_path.stem + if self.rlbot_server_path is not None and self.rlbot_server_path.is_file() + else RLBOT_SERVER_NAME + ) self.rlbot_server_process, self.rlbot_server_port = gateway.find_server_process( - self.main_executable_name + exe_name ) if self.rlbot_server_process is not None: - self.logger.info("Already have %s running!", self.main_executable_name) + self.logger.info("%s is already running!", exe_name) return - if self.main_executable_path is None: - self.main_executable_path = Path.cwd() + if self.rlbot_server_path is None: + # Look in cwd or localappdata + path = Path.cwd() / RLBOT_SERVER_NAME + if not path.exists() and CURRENT_OS == OS.WINDOWS: + self.logger.debug( + f"Could not find RLBotServer in cwd ('{path.parent}'), trying %localappdata% instead." + ) + path = ( + Path(os.environ.get("LOCALAPPDATA")) + / "RLBot5" + / "bin" + / RLBOT_SERVER_NAME + ) + if not path.exists(): + raise FileNotFoundError( + "Unable to find RLBotServer in the current working directory " + "or in the default installation location. " + "Is your antivirus messing you up? Check " + "https://github.com/RLBot/RLBot/wiki/Antivirus-Notes." + ) + else: + # User specified path + path = self.rlbot_server_path + if path.exists() and path.is_dir(): + path = path / RLBOT_SERVER_NAME + if not path.exists(): + raise FileNotFoundError( + f"Unable to find RLBotServer at the specified path '{path}'." + ) + + if path is None or not os.access(path, os.F_OK): + raise FileNotFoundError( + f"Unable to find RLBotServer at '{path}'. " + "Is your antivirus messing you up? Check " + "https://github.com/RLBot/RLBot/wiki/Antivirus-Notes." + ) + + if not os.access(path, os.X_OK): + os.chmod(path, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) - rlbot_server_process, self.rlbot_server_port = gateway.launch( - self.main_executable_path, - self.main_executable_name, - ) + if not os.access(path, os.X_OK): + raise PermissionError( + "Unable to execute RLBotServer due to file permissions! Is your antivirus messing you up? " + f"Check https://github.com/RLBot/RLBot/wiki/Antivirus-Notes. The exact path is '{path}'" + ) + + rlbot_server_process, self.rlbot_server_port = gateway.launch(path) self.rlbot_server_process = psutil.Process(rlbot_server_process.pid) self.logger.info( "Started %s with process id %s", - self.main_executable_name, + path, self.rlbot_server_process.pid, ) @@ -76,7 +128,7 @@ def connect( wants_ball_predictions: bool, close_between_matches: bool = True, rlbot_server_ip: str = RLBOT_SERVER_IP, - rlbot_server_port: Optional[int] = None, + rlbot_server_port: int | None = None, ): """ Connects to the RLBot server specifying the given settings. @@ -109,7 +161,7 @@ def connect_and_run( wants_ball_predictions: bool, close_between_matches: bool = True, rlbot_server_ip: str = RLBOT_SERVER_IP, - rlbot_server_port: Optional[int] = None, + rlbot_server_port: int | None = None, background_thread: bool = False, ): """ @@ -184,7 +236,7 @@ def set_game_state( self, balls: dict[int, flat.DesiredBallState] = {}, cars: dict[int, flat.DesiredCarState] = {}, - match_info: Optional[flat.DesiredMatchInfo] = None, + match_info: flat.DesiredMatchInfo | None = None, commands: list[str] = [], ): """ diff --git a/rlbot/managers/rendering.py b/rlbot/managers/rendering.py index 963bd18..aec3978 100644 --- a/rlbot/managers/rendering.py +++ b/rlbot/managers/rendering.py @@ -1,7 +1,6 @@ import math from collections.abc import Callable, Sequence from contextlib import contextmanager -from typing import Optional from rlbot import flat from rlbot.interface import SocketRelay @@ -46,12 +45,10 @@ class Renderer: purple = flat.Color(128, 0, 128) teal = flat.Color(0, 128, 128) - can_render: bool = False - _logger = get_logger("renderer") _used_group_ids: set[int] = set() - _group_id: Optional[int] = None + _group_id: int | None = None _current_renders: list[flat.RenderMessage] = [] _default_color = white @@ -63,6 +60,11 @@ def __init__(self, game_interface: SocketRelay): self._send_msg: Callable[[flat.RenderGroup | flat.RemoveRenderGroup], None] = ( game_interface.send_msg ) + self._game_interface = game_interface + + @property + def can_render(self) -> bool: + return self._game_interface.can_render def set_resolution(self, screen_width: float, screen_height: float): """ diff --git a/rlbot/managers/script.py b/rlbot/managers/script.py index 8aea134..afb9944 100644 --- a/rlbot/managers/script.py +++ b/rlbot/managers/script.py @@ -1,6 +1,5 @@ import os from traceback import print_exc -from typing import Optional from rlbot import flat from rlbot.interface import ( @@ -35,10 +34,10 @@ class Script: _has_match_settings = False _has_field_info = False - _latest_packet: Optional[flat.GamePacket] = None + _latest_packet: flat.GamePacket | None = None _latest_prediction = flat.BallPrediction() - def __init__(self, default_agent_id: Optional[str] = None): + def __init__(self, default_agent_id: str | None = None): agent_id = os.environ.get("RLBOT_AGENT_ID") or default_agent_id if agent_id is None: @@ -59,9 +58,6 @@ def __init__(self, default_agent_id: Optional[str] = None): self._handle_ball_prediction ) self._game_interface.packet_handlers.append(self._handle_packet) - self._game_interface.rendering_status_handlers.append( - self.rendering_status_update - ) self.renderer = Renderer(self._game_interface) @@ -103,9 +99,6 @@ def _try_initialize(self): def _handle_match_config(self, match_config: flat.MatchConfiguration): self.match_config = match_config self._has_match_settings = True - self.can_render = ( - match_config.enable_rendering == flat.DebugRendering.OnByDefault - ) self._try_initialize() @@ -188,19 +181,10 @@ def _handle_match_communication(self, match_comm: flat.MatchComm): match_comm.team_only, ) - def rendering_status_update(self, update: flat.RenderingStatus): - """ - Called when the server sends a rendering status update for ANY bot or script. - - By default, this will update `self.renderer.can_render` if appropriate. - """ - if not update.is_bot and update.index == self.index: - self.renderer.can_render = update.status - def update_rendering_status( self, status: bool, - index: Optional[int] = None, + index: int | None = None, is_bot: bool = False, ): """ @@ -221,7 +205,7 @@ def handle_match_comm( index: int, team: int, content: bytes, - display: Optional[str], + display: str | None, team_only: bool, ): """ @@ -231,7 +215,7 @@ def handle_match_comm( """ def send_match_comm( - self, content: bytes, display: Optional[str] = None, team_only: bool = False + self, content: bytes, display: str | None = None, team_only: bool = False ): """ Emits a match communication message to other bots and scripts. @@ -254,7 +238,7 @@ def set_game_state( self, balls: dict[int, flat.DesiredBallState] = {}, cars: dict[int, flat.DesiredCarState] = {}, - match_info: Optional[flat.DesiredMatchInfo] = None, + match_info: flat.DesiredMatchInfo | None = None, commands: list[str] = [], ): """ diff --git a/rlbot/utils/__init__.py b/rlbot/utils/__init__.py index c478c2e..86e683e 100644 --- a/rlbot/utils/__init__.py +++ b/rlbot/utils/__init__.py @@ -1,12 +1,10 @@ -from typing import Optional - from rlbot import flat def fill_desired_game_state( balls: dict[int, flat.DesiredBallState] = {}, cars: dict[int, flat.DesiredCarState] = {}, - match_info: Optional[flat.DesiredMatchInfo] = None, + match_info: flat.DesiredMatchInfo | None = None, commands: list[str] = [], ) -> flat.DesiredGameState: """ diff --git a/rlbot/utils/gateway.py b/rlbot/utils/gateway.py index d0ace75..0565d32 100644 --- a/rlbot/utils/gateway.py +++ b/rlbot/utils/gateway.py @@ -1,9 +1,6 @@ -import os import socket -import stat import subprocess from pathlib import Path -from typing import Optional import psutil @@ -15,21 +12,21 @@ import shlex -def find_main_executable_path( - main_executable_path: Path, main_executable_name: str -) -> tuple[Path, Optional[Path]]: - main_executable_path = main_executable_path.absolute().resolve() +def find_file(base_dir: Path, file_name: str) -> Path | None: + """ + Looks for a file called `file_name` in the given `base_dir` directory and its subdirectories. + Returns the path to the file, or None if it was not found. + """ - # check if the path is directly to the main executable - if main_executable_path.is_file(): - return main_executable_path.parent, main_executable_path + base_dir = base_dir.absolute().resolve() + assert base_dir.exists() and base_dir.is_dir(), f"'{base_dir}' is not a directory!" - # search subdirectories for the main executable - for path in main_executable_path.glob(f"**/{main_executable_name}"): + # Search subdirectories for the file + for path in base_dir.glob(f"**/{file_name}"): if path.is_file(): - return path.parent, path + return path - return main_executable_path, None + return None def is_port_accessible(port: int): @@ -52,47 +49,25 @@ def find_open_server_port() -> int: ) -def launch( - main_executable_path: Path, main_executable_name: str -) -> tuple[subprocess.Popen[bytes], int]: - directory, path = find_main_executable_path( - main_executable_path, main_executable_name - ) - - if path is None or not os.access(path, os.F_OK): - raise FileNotFoundError( - f"Unable to find RLBotServer at '{main_executable_path}'. " - "Is your antivirus messing you up? Check " - "https://github.com/RLBot/RLBot/wiki/Antivirus-Notes." - ) - - if not os.access(path, os.X_OK): - os.chmod(path, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) - - if not os.access(path, os.X_OK): - raise PermissionError( - "Unable to execute RLBotServer due to file permissions! Is your antivirus messing you up? " - f"Check https://github.com/RLBot/RLBot/wiki/Antivirus-Notes. The exact path is {path}" - ) - +def launch(exe_path: Path) -> tuple[subprocess.Popen[bytes], int]: port = find_open_server_port() if CURRENT_OS == "Windows": - args = [str(path), str(port)] + args = [str(exe_path), str(port)] else: - args = f"{shlex.quote(path.as_posix())} {port}" # on Unix, when shell=True, args must be a string for flags to reach the executable - DEFAULT_LOGGER.info("Launching RLBotServer with via %s", args) + args = f"{shlex.quote(exe_path.as_posix())} {port}" # on Unix, when shell=True, args must be a string for flags to reach the executable + DEFAULT_LOGGER.info("Launching RLBotServer via %s", args) - return subprocess.Popen(args, shell=True, cwd=directory), port + return subprocess.Popen(args, shell=True, cwd=exe_path.parent), port def find_server_process( - main_executable_name: str, -) -> tuple[Optional[psutil.Process], int]: + exe_name: str, +) -> tuple[psutil.Process | None, int]: logger = DEFAULT_LOGGER for proc in psutil.process_iter(): try: - if proc.name() != main_executable_name: + if proc.name() != exe_name: continue args = proc.cmdline() @@ -107,7 +82,7 @@ def find_server_process( except Exception as e: logger.error( "Failed to read the name of a process while hunting for %s: %s", - main_executable_name, + exe_name, e, ) diff --git a/rlbot/utils/maps.py b/rlbot/utils/maps.py index 98f14b2..9f140d0 100644 --- a/rlbot/utils/maps.py +++ b/rlbot/utils/maps.py @@ -84,6 +84,12 @@ "DFHStadium_Anniversary": "stadium_10a_p", "Holyfield": "Labs_Holyfield_Space_P", "DriftWoods_Night": "woods_night_p", + "BoostfieldMall": "Mall_Day_P", + "ParcDeParis": "Paname_Dusk_P", + "Roadblock": "Labs_Octagon_B2B_02_P", + "Mannfield_Quads": "Labs_4v4_Arena15_EuroStadium_Night_P", + "MidnightMetro_Quads": "Labs_4v4_Arena15_Blackout_P", + "SunsetDunes_Quads": "Labs_4v4_Arena15_Retro_P", } STANDARD_MAPS = [ @@ -140,4 +146,6 @@ "DFHStadium_Anniversary", "DriftWoods_Night", "NeoTokyo_Comic", + "BoostfieldMall", + "ParcDeParis", ] diff --git a/rlbot/utils/os_detector.py b/rlbot/utils/os_detector.py index 5ef4f8d..761878c 100644 --- a/rlbot/utils/os_detector.py +++ b/rlbot/utils/os_detector.py @@ -10,15 +10,15 @@ class OS(IntEnum): match platform.system(): case "Windows": - MAIN_EXECUTABLE_NAME = "RLBotServer.exe" + RLBOT_SERVER_NAME = "RLBotServer.exe" CURRENT_OS = OS.WINDOWS case "Linux": - MAIN_EXECUTABLE_NAME = "RLBotServer" + RLBOT_SERVER_NAME = "RLBotServer" CURRENT_OS = OS.LINUX case _ as unknown_os: from rlbot.utils.logging import get_logger - MAIN_EXECUTABLE_NAME = "" + RLBOT_SERVER_NAME = "" CURRENT_OS = OS.UNKNOWN get_logger("os_detector").warning("Unknown OS: %s", unknown_os) diff --git a/rlbot/version.py b/rlbot/version.py index 1a64d22..a7dae53 100644 --- a/rlbot/version.py +++ b/rlbot/version.py @@ -1 +1 @@ -__version__ = "2.0.0-beta.48" +__version__ = "2.0.0-beta.52" diff --git a/tests/many_match.py b/tests/many_match.py index 8cc093b..4942ada 100644 --- a/tests/many_match.py +++ b/tests/many_match.py @@ -4,11 +4,15 @@ from rlbot import flat from rlbot.config import load_player_config from rlbot.managers import MatchManager +from rlbot.utils.gateway import find_file +from rlbot.utils.os_detector import RLBOT_SERVER_NAME DIR = Path(__file__).parent BOT_PATH = DIR / "atba/atba.bot.toml" -RLBOT_SERVER_FOLDER = DIR / "../../core/RLBotCS/bin/Release/" +RLBOT_SERVER_PATH = find_file( + DIR / "../../core/RLBotCS/bin/Release/", RLBOT_SERVER_NAME +) num_comms = set() @@ -20,7 +24,7 @@ def handle_match_comm(comm: flat.MatchComm): if __name__ == "__main__": - match_manager = MatchManager(RLBOT_SERVER_FOLDER) + match_manager = MatchManager(RLBOT_SERVER_PATH) match_manager.rlbot_interface.match_comm_handlers.append(handle_match_comm) match_manager.ensure_server_started() match_manager.connect_and_run( diff --git a/tests/render_test/render.py b/tests/render_test/render.py index 9487bcd..3582d67 100644 --- a/tests/render_test/render.py +++ b/tests/render_test/render.py @@ -25,7 +25,7 @@ def handle_packet(self, packet: flat.GamePacket): radius = 0 if len(packet.balls) > 0: - match packet.balls[0].shape.item: + match packet.balls[0].shape: case flat.SphereShape() | flat.CylinderShape() as shape: radius = shape.diameter / 2 case flat.BoxShape() as shape: diff --git a/tests/run_forever.py b/tests/run_forever.py index 830a85a..8efe3b1 100644 --- a/tests/run_forever.py +++ b/tests/run_forever.py @@ -4,15 +4,19 @@ from rlbot import flat from rlbot.config import load_player_config from rlbot.managers import MatchManager +from rlbot.utils.gateway import find_file from rlbot.utils.maps import GAME_MAP_TO_UPK, STANDARD_MAPS +from rlbot.utils.os_detector import RLBOT_SERVER_NAME DIR = Path(__file__).parent BOT_PATH = DIR / "atba/atba.bot.toml" -RLBOT_SERVER_FOLDER = DIR / "../../core/RLBotCS/bin/Release/" +RLBOT_SERVER_PATH = find_file( + DIR / "../../core/RLBotCS/bin/Release/", RLBOT_SERVER_NAME +) if __name__ == "__main__": - match_manager = MatchManager(RLBOT_SERVER_FOLDER) + match_manager = MatchManager(RLBOT_SERVER_PATH) current_map = -1 diff --git a/tests/run_match.py b/tests/run_match.py index 8da8ebb..bf8cc96 100644 --- a/tests/run_match.py +++ b/tests/run_match.py @@ -3,14 +3,18 @@ from rlbot import flat from rlbot.managers import MatchManager +from rlbot.utils.gateway import find_file +from rlbot.utils.os_detector import RLBOT_SERVER_NAME DIR = Path(__file__).parent MATCH_CONFIG_PATH = DIR / "human_vs_atba.toml" -RLBOT_SERVER_FOLDER = DIR / "../../core/RLBotCS/bin/Release/" +RLBOT_SERVER_PATH = find_file( + DIR / "../../core/RLBotCS/bin/Release/", RLBOT_SERVER_NAME +) if __name__ == "__main__": - with MatchManager(RLBOT_SERVER_FOLDER) as man: + with MatchManager(RLBOT_SERVER_PATH) as man: man.start_match(MATCH_CONFIG_PATH) assert man.packet is not None