diff --git a/runner.py b/runner.py index da30f8809..0e3b4bd90 100644 --- a/runner.py +++ b/runner.py @@ -1,10 +1,9 @@ import sys import os.path sys.path.insert(0, os.path.realpath(os.path.dirname(os.path.abspath(__file__)) + '/src/main/python/')) -from rlbot.utils import python_version_check -from rlbot import runner as framework_runner if __name__ == '__main__': - framework_runner.main() + from rlbot import runner as framework_runner + framework_runner.main() diff --git a/src/main/python/rlbot/agents/base_agent.py b/src/main/python/rlbot/agents/base_agent.py index a62c14b84..74fdd0c7b 100644 --- a/src/main/python/rlbot/agents/base_agent.py +++ b/src/main/python/rlbot/agents/base_agent.py @@ -1,4 +1,3 @@ -import flatbuffers from rlbot.botmanager.helper_process_request import HelperProcessRequest from rlbot.parsing.custom_config import ConfigObject, ConfigHeader from rlbot.utils.game_state_util import GameState diff --git a/src/main/python/rlbot/agents/base_dotnet_agent.py b/src/main/python/rlbot/agents/base_dotnet_agent.py index dadd53212..6a0f021eb 100644 --- a/src/main/python/rlbot/agents/base_dotnet_agent.py +++ b/src/main/python/rlbot/agents/base_dotnet_agent.py @@ -1,5 +1,4 @@ import os -import msvcrt import socket import time @@ -21,8 +20,7 @@ def __init__(self, name, team, index): def run_independently(self, terminate_request_event): while not terminate_request_event.is_set(): - message = "add\n{0}\n{1}\n{2}\n{3}".format(self.name, self.team, self.index, game_interface.get_dll_directory()) - + message = f"add\n{self.name}\n{self.team,}\n{self.index}\n{game_interface.get_dll_directory()}" try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("127.0.0.1", self.port)) @@ -52,20 +50,21 @@ def get_extra_pids(self): for proc in psutil.process_iter(): for conn in proc.connections(): if conn.laddr.port == self.port: - self.logger.debug('.NET socket server for {} appears to have pid {}'.format(self.name, proc.pid)) + self.logger.debug(f".NET socket server for {self.name} appears to have pid {proc.pid}") return [proc.pid] if self.is_executable_configured(): return [] time.sleep(1) if self.dotnet_executable_path is None: - self.logger.info( - "Can't auto-start .NET executable because no executable is configured. Please start the .NET bot manually!") + self.logger.info("Can't auto-start .NET executable because no executable is configured. " + "Please start the .NET bot manually!") else: - self.logger.info("Can't auto-start .NET executable because {} is not found. Please start the .NET bot manually!".format(self.dotnet_executable_path)) + self.logger.info(f"Can't auto-start .NET executable because {self.dotnet_executable_path} " + "is not found. Please start the .NET bot manually!") def retire(self): port = self.read_port_from_file() - message = "remove\n{0}".format(self.index) + message = f"remove\n{self.index}" try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -75,7 +74,7 @@ def retire(self): except ConnectionRefusedError: self.logger.warn("Could not connect to server!") self.is_retired = True - + def read_port_from_file(self): try: location = self.get_port_file_path() diff --git a/src/main/python/rlbot/agents/base_java_agent.py b/src/main/python/rlbot/agents/base_java_agent.py index 6eb900642..16b185091 100644 --- a/src/main/python/rlbot/agents/base_java_agent.py +++ b/src/main/python/rlbot/agents/base_java_agent.py @@ -4,11 +4,9 @@ import psutil from py4j.java_gateway import GatewayParameters from py4j.java_gateway import JavaGateway -from rlbot.agents.base_agent import BOT_CONFIG_AGENT_HEADER, BOT_CONFIG_MODULE_HEADER from rlbot.agents.base_independent_agent import BaseIndependentAgent from rlbot.botmanager.helper_process_request import HelperProcessRequest -from rlbot.parsing.custom_config import ConfigHeader, ConfigObject from rlbot.utils.logging_utils import get_logger from rlbot.utils.structures import game_interface @@ -69,7 +67,7 @@ def get_extra_pids(self): for proc in psutil.process_iter(): for conn in proc.connections(): if conn.laddr.port == self.port: - self.logger.debug('py4j server for {} appears to have pid {}'.format(self.name, proc.pid)) + self.logger.debug(f'py4j server for {self.name} appears to have pid {proc.pid}') return [proc.pid] if self.is_executable_configured(): # The helper process will start java and report the PID. Nothing to do here. @@ -79,8 +77,8 @@ def get_extra_pids(self): self.logger.info( "Can't auto-start java because no executable is configured. Please start java manually!") else: - self.logger.info("Can't auto-start java because {} is not found. Please start java manually!" - .format(self.java_executable_path)) + self.logger.info(f"Can't auto-start java because {self.java_executable_path} is not found. " + "Please start java manually!") def retire(self): try: diff --git a/src/main/python/rlbot/agents/human/controller_input.py b/src/main/python/rlbot/agents/human/controller_input.py index 505cd3b86..0b90bf813 100644 --- a/src/main/python/rlbot/agents/human/controller_input.py +++ b/src/main/python/rlbot/agents/human/controller_input.py @@ -1,10 +1,13 @@ from inputs import get_gamepad # pip install inputs import threading + def deadzone(normalized_axis): - if abs(normalized_axis) < 0.1: return 0.0 + if abs(normalized_axis) < 0.1: + return 0.0 return normalized_axis + class ControllerInput: def __init__(self): # self.throttle = 0.0 @@ -28,15 +31,24 @@ def main_poll_loop(self): events = get_gamepad() # Blocking for event in events: # print(repr((event.ev_type, event.code, event.state))) - if False: pass - elif event.ev_type=='Absolute' and event.code=='ABS_RZ': self._gas_pedal = deadzone(event.state / 255.0) - elif event.ev_type=='Absolute' and event.code=='ABS_Z': self._brake_pedal = deadzone(event.state / 255.0) - elif event.ev_type=='Absolute' and event.code=='ABS_X': self.steer = self.yaw = deadzone(event.state / 32768.0) - elif event.ev_type=='Absolute' and event.code=='ABS_Y': self.pitch = deadzone(-event.state / 32768.0) - elif event.ev_type=='Key' and event.code=='BTN_THUMBL': self.roll = -event.state - elif event.ev_type=='Key' and event.code=='BTN_SOUTH': self.jump = event.state - elif event.ev_type=='Key' and event.code=='BTN_TR': self.boost = event.state - elif event.ev_type=='Key' and event.code=='BTN_WEST': self.handbrake = event.state + if False: + pass + elif event.ev_type == 'Absolute' and event.code == 'ABS_RZ': + self._gas_pedal = deadzone(event.state / 255.0) + elif event.ev_type == 'Absolute' and event.code == 'ABS_Z': + self._brake_pedal = deadzone(event.state / 255.0) + elif event.ev_type == 'Absolute' and event.code == 'ABS_X': + self.steer = self.yaw = deadzone(event.state / 32768.0) + elif event.ev_type == 'Absolute' and event.code == 'ABS_Y': + self.pitch = deadzone(-event.state / 32768.0) + elif event.ev_type == 'Key' and event.code == 'BTN_THUMBL': + self.roll = -event.state + elif event.ev_type == 'Key' and event.code == 'BTN_SOUTH': + self.jump = event.state + elif event.ev_type == 'Key' and event.code == 'BTN_TR': + self.boost = event.state + elif event.ev_type == 'Key' and event.code == 'BTN_WEST': + self.handbrake = event.state controller = ControllerInput() diff --git a/src/main/python/rlbot/botmanager/bot_manager.py b/src/main/python/rlbot/botmanager/bot_manager.py index 12ed7f9b6..471eac213 100644 --- a/src/main/python/rlbot/botmanager/bot_manager.py +++ b/src/main/python/rlbot/botmanager/bot_manager.py @@ -176,7 +176,7 @@ def run(self): last_module_modification_time = new_module_modification_time agent, agent_class_file = self.reload_agent(agent, agent_class_file) except FileNotFoundError: - self.logger.error("Agent file {} was not found. Will try again.".format(agent_class_file)) + self.logger.error(f"Agent file {agent_class_file} was not found. Will try again.") time.sleep(0.5) except Exception: self.logger.error("Reloading the agent failed:\n" + traceback.format_exc()) diff --git a/src/main/python/rlbot/botmanager/bot_manager_flatbuffer.py b/src/main/python/rlbot/botmanager/bot_manager_flatbuffer.py index dfde5a7e1..33bf8f33a 100644 --- a/src/main/python/rlbot/botmanager/bot_manager_flatbuffer.py +++ b/src/main/python/rlbot/botmanager/bot_manager_flatbuffer.py @@ -19,7 +19,7 @@ def call_agent(self, agent, agent_class): agent.set_flatbuffer_binary(self.game_tick_flat_binary) player_input = agent.get_output_flatbuffer(self.game_tick_flat) if not player_input: - raise Exception('Agent "{}" did not return a player input.'.format(agent_class.__name__)) + raise Exception(f'Agent "{agent_class.__name__}" did not return a player input.') self.game_interface.update_player_input_flat(player_input) diff --git a/src/main/python/rlbot/botmanager/bot_manager_struct.py b/src/main/python/rlbot/botmanager/bot_manager_struct.py index 630a6703f..3dfc6cc89 100644 --- a/src/main/python/rlbot/botmanager/bot_manager_struct.py +++ b/src/main/python/rlbot/botmanager/bot_manager_struct.py @@ -21,7 +21,8 @@ def prepare_for_run(self): # Set up shared memory map (offset makes it so bot only writes to its own input!) and map to buffer self.bot_input = PlayerInput() # Set up shared memory for game data - self.game_tick_packet = gd.GameTickPacket() # We want to do a deep copy for game inputs so people don't mess with em + # We want to do a deep copy for game inputs so people don't mess with em + self.game_tick_packet = gd.GameTickPacket() # Set up shared memory for Ball prediction self.ball_prediction = bp.BallPrediction() # Set up shared memory for rigid body tick diff --git a/src/main/python/rlbot/gui/gui_agent.py b/src/main/python/rlbot/gui/gui_agent.py index 1c2d620cd..9f19c30dd 100644 --- a/src/main/python/rlbot/gui/gui_agent.py +++ b/src/main/python/rlbot/gui/gui_agent.py @@ -59,7 +59,7 @@ def get_agent_preset(self): def get_agent_config_path(self): return os.path.realpath(self.overall_config.getpath(PARTICIPANT_CONFIGURATION_HEADER, - PARTICIPANT_CONFIG_KEY, self.overall_index)) + PARTICIPANT_CONFIG_KEY, self.overall_index)) def set_agent_config_path(self, config_path: str): self.overall_config.set_value(PARTICIPANT_CONFIGURATION_HEADER, PARTICIPANT_CONFIG_KEY, diff --git a/src/main/python/rlbot/gui/index_manager.py b/src/main/python/rlbot/gui/index_manager.py index 9f5ec582d..4a40351a8 100644 --- a/src/main/python/rlbot/gui/index_manager.py +++ b/src/main/python/rlbot/gui/index_manager.py @@ -5,6 +5,7 @@ class IndexManager: """ Handles the indices for agents, some useful methods for handling the overall indices """ + def __init__(self, size): self.numbers = set() self.size = size diff --git a/src/main/python/rlbot/gui/mutator_editor.py b/src/main/python/rlbot/gui/mutator_editor.py index a91b6bb59..479d59f64 100644 --- a/src/main/python/rlbot/gui/mutator_editor.py +++ b/src/main/python/rlbot/gui/mutator_editor.py @@ -66,7 +66,7 @@ def create_linking_dicts(self): self.mutator_widget_to_config_name = {} for config_name, widget in self.config_name_to_mutator_widget.items(): - self.mutator_widget_to_config_name[widget] = config_name + self.mutator_widget_to_config_name[widget] = config_name self.config_name_to_options_list = { MUTATOR_MATCH_LENGTH: match_length_types, @@ -85,4 +85,4 @@ def create_linking_dicts(self): MUTATOR_GRAVITY: gravity_mutator_types, MUTATOR_DEMOLISH: demolish_mutator_types, MUTATOR_RESPAWN_TIME: respawn_time_mutator_types - } \ No newline at end of file + } diff --git a/src/main/python/rlbot/gui/preset_editors.py b/src/main/python/rlbot/gui/preset_editors.py index ccea2dbf5..93173ec1c 100644 --- a/src/main/python/rlbot/gui/preset_editors.py +++ b/src/main/python/rlbot/gui/preset_editors.py @@ -120,7 +120,6 @@ def add_new_preset(self): if list_item.data(Qt.UserRole) == preset: self.presets_listwidget.setCurrentRow(i) - def load_preset_cfg(self): file_path = QtWidgets.QFileDialog.getOpenFileName(self, 'Load Config', '', 'Config Files (*.cfg)')[0] if not os.path.isfile(file_path): @@ -170,7 +169,7 @@ def save_preset_cfg(self, time_out=0): else: preset.save_config(time_out=time_out, message_out=self.statusbar.showMessage) - def validate_name(self, name, preset, copy_index = None): + def validate_name(self, name, preset, copy_index=None): value = name if copy_index is not None: @@ -198,6 +197,7 @@ class CarCustomisationDialog(BasePresetEditor, Ui_LoadoutPresetCustomiser): """ The class extending BasePresetEditor to allow some loadout preset specific preset editing, like handling item names """ + def __init__(self, qt_gui): super().__init__(qt_gui, qt_gui.loadout_presets, qt_gui.loadout_preset_combobox, LoadoutPreset) diff --git a/src/main/python/rlbot/gui/presets.py b/src/main/python/rlbot/gui/presets.py index eb79e51d9..dcc4b5711 100644 --- a/src/main/python/rlbot/gui/presets.py +++ b/src/main/python/rlbot/gui/presets.py @@ -14,6 +14,7 @@ class Preset: """ Stores a config, the config path, a preset name and has methods to save/load the preset """ + def __init__(self, config, file_path, name): self.config = config self.config_path = file_path @@ -72,6 +73,7 @@ class LoadoutPreset(Preset): """ A class extending Preset to handle a LoadoutPreset, which is based on the looks configurations file """ + def __init__(self, name, file_path=None): super().__init__(BaseAgent._create_looks_configurations(), file_path, name) @@ -81,8 +83,10 @@ def get_required_sections(self): class AgentPreset(Preset): """ - A class extending Preset to handle an AgentPreset, which is based on the agent configuration file and which also contains the Agent class + A class extending Preset to handle an AgentPreset + which is based on the agent configuration file and which also contains the Agent class """ + def __init__(self, name, file_path=None): self.looks_path = None @@ -98,7 +102,7 @@ def __init__(self, name, file_path=None): try: self.agent_class = import_agent(python_file_path).get_loaded_class() except (ValueError, ModuleNotFoundError, FileNotFoundError) as e: - raise ValueError("Problem when processing {}: {}".format(file_path, str(e))) + raise ValueError(f"Problem when processing {file_path}: {str(e)}") super().__init__(self.agent_class.base_create_agent_configurations(), file_path, name) # Make sure the path to the python file actually gets set to that path, even if there was no config at file_path diff --git a/src/main/python/rlbot/gui/qt_root.py b/src/main/python/rlbot/gui/qt_root.py index f96edbd47..5717636fc 100644 --- a/src/main/python/rlbot/gui/qt_root.py +++ b/src/main/python/rlbot/gui/qt_root.py @@ -3,8 +3,8 @@ import sys from PyQt5.QtCore import QTimer from PyQt5 import QtCore, QtGui -from PyQt5.QtGui import QIcon -from PyQt5.QtWidgets import QFileDialog, QMainWindow, QApplication, QWidget, QComboBox, QLineEdit, QRadioButton, QSlider, QCheckBox, QMessageBox +from PyQt5.QtWidgets import QFileDialog, QMainWindow, QApplication, QWidget, QComboBox, QLineEdit, QRadioButton, QSlider +from PyQt5.QtWidgets import QCheckBox, QMessageBox import threading import configparser @@ -220,13 +220,15 @@ def bot_config_edit_event(self, value=None): if agent.get_team() != 0: agent.set_team(0) self.update_teams_listwidgets() - self.blue_listwidget.setCurrentItem(self.blue_listwidget.findItems(self.validate_name(agent.get_name(), agent), QtCore.Qt.MatchExactly)[0]) + self.blue_listwidget.setCurrentItem(self.blue_listwidget.findItems( + self.validate_name(agent.get_name(), agent), QtCore.Qt.MatchExactly)[0]) elif sender is self.orange_radiobutton and value: if agent.get_team() != 1: agent.set_team(1) self.update_teams_listwidgets() - self.orange_listwidget.setCurrentItem(self.orange_listwidget.findItems(self.validate_name(agent.get_name(), agent), QtCore.Qt.MatchExactly)[0]) + self.orange_listwidget.setCurrentItem(self.orange_listwidget.findItems( + self.validate_name(agent.get_name(), agent), QtCore.Qt.MatchExactly)[0]) elif sender is self.ign_lineedit: if agent not in self.agents: @@ -282,7 +284,7 @@ def update_bot_type_combobox(self): Also saves the new type if there is a bot selected :return: """ - bot_type = self.bot_type_combobox.currentText() + bot_type = self.bot_type_combobox.currentText() if bot_type == 'RLBot': self.rlbot_frame.setHidden(False) self.extra_line.setHidden(False) @@ -311,7 +313,7 @@ def update_bot_type_combobox(self): self.appearance_frame.setHidden(False) self.label_3.setHidden(True) self.ign_lineedit.setHidden(True) - + if self.bot_config_groupbox.isEnabled() and self.current_bot is not None: config_type = bot_type.lower().replace(" ", "_") self.current_bot.set_participant_type(config_type) @@ -327,7 +329,7 @@ def team_settings_edit_event(self, value=None): value = sender.text() if sender is self.blue_name_lineedit: - self.overall_config.set_value(TEAM_CONFIGURATION_HEADER, "Team Blue Name", value) + self.overall_config.set_value(TEAM_CONFIGURATION_HEADER, "Team Blue Name", value) elif sender is self.orange_name_lineedit: self.overall_config.set_value(TEAM_CONFIGURATION_HEADER, "Team Orange Name", value) elif sender is self.blue_color_spinbox: @@ -395,7 +397,8 @@ def load_overall_config(self, config_path=None): raw_parser.read(config_path, encoding='utf8') for section in self.overall_config.headers.keys(): if not raw_parser.has_section(section): - self.popup_message("Config file is missing the section {}, not loading it!".format(section), "Invalid Config File", QMessageBox.Warning) + self.popup_message(f"Config file is missing the section {section}, not loading it!", + "Invalid Config File", QMessageBox.Warning) return self.overall_config_path = config_path self.overall_config.parse_file(raw_parser, 10, config_directory=os.path.dirname(self.overall_config_path)) @@ -485,7 +488,8 @@ def load_selected_bot(self): self.orange_radiobutton.setChecked(True) self.ign_lineedit.setText(agent.ingame_name) - loadout_index = index_of_config_path_in_combobox(self.loadout_preset_combobox, agent.get_loadout_preset().config_path) + loadout_index = index_of_config_path_in_combobox( + self.loadout_preset_combobox, agent.get_loadout_preset().config_path) self.loadout_preset_combobox.setCurrentIndex(loadout_index or 0) self.agent_preset_combobox.blockSignals(True) @@ -565,9 +569,11 @@ def add_agent_button(self, team_index: int): self.agent_customisation.update_presets_widgets() self.update_teams_listwidgets() if agent.get_team() == 0: - self.blue_listwidget.setCurrentItem(self.blue_listwidget.findItems(self.validate_name(agent.get_name(), agent), QtCore.Qt.MatchExactly)[0]) + self.blue_listwidget.setCurrentItem(self.blue_listwidget.findItems( + self.validate_name(agent.get_name(), agent), QtCore.Qt.MatchExactly)[0]) else: - self.orange_listwidget.setCurrentItem(self.orange_listwidget.findItems(self.validate_name(agent.get_name(), agent), QtCore.Qt.MatchExactly)[0]) + self.orange_listwidget.setCurrentItem(self.orange_listwidget.findItems( + self.validate_name(agent.get_name(), agent), QtCore.Qt.MatchExactly)[0]) def load_agents(self, config_file=None): """ @@ -582,7 +588,7 @@ def load_agents(self, config_file=None): for i in range(num_participants): self.load_agent(i) except BaseException as e: - raise ValueError(str(e) + " Please check your config files!".format(self.overall_config_path)) + raise ValueError(f"{str(e)}\nPlease check your config files! {self.overall_config_path}") def load_agent(self, overall_index: int=None): """ @@ -601,7 +607,8 @@ def load_agent(self, overall_index: int=None): agent_preset = self.add_agent_preset(agent.get_agent_config_path()) agent.set_agent_preset(agent_preset) - loadout_file = self.overall_config.get(PARTICIPANT_CONFIGURATION_HEADER, PARTICIPANT_LOADOUT_CONFIG_KEY, overall_index) + loadout_file = self.overall_config.get(PARTICIPANT_CONFIGURATION_HEADER, + PARTICIPANT_LOADOUT_CONFIG_KEY, overall_index) if loadout_file is None or loadout_file == "None": loadout_preset = self.add_loadout_preset(agent_preset.looks_path) else: @@ -693,7 +700,7 @@ def add_agent_preset(self, file_path): if os.path.isfile(file_path): name = pathlib.Path(file_path).stem else: - raise FileNotFoundError("File path {} is not found!".format(file_path)) + raise FileNotFoundError(f"File path {file_path} is not found!") if name in self.agent_presets: if self.agent_presets[name].config_path == file_path: @@ -703,7 +710,7 @@ def add_agent_preset(self, file_path): for preset_name in self.agent_presets: if name in preset_name: i += 1 - name = name + ' ({})'.format(i) + name = f"{name} ({i})" preset = AgentPreset(name, file_path) self.agent_presets[preset.get_name()] = preset return preset @@ -724,7 +731,8 @@ def update_match_settings(self): self.mode_type_combobox.setCurrentText(self.overall_config.get(MATCH_CONFIGURATION_HEADER, GAME_MODE)) self.map_type_combobox.setCurrentText(self.overall_config.get(MATCH_CONFIGURATION_HEADER, GAME_MAP)) self.skip_replays_checkbox.setChecked(self.overall_config.getboolean(MATCH_CONFIGURATION_HEADER, SKIP_REPLAYS)) - self.instant_start_checkbox.setChecked(self.overall_config.getboolean(MATCH_CONFIGURATION_HEADER, INSTANT_START)) + self.instant_start_checkbox.setChecked( + self.overall_config.getboolean(MATCH_CONFIGURATION_HEADER, INSTANT_START)) def match_settings_edit_event(self, value): """ diff --git a/src/main/python/rlbot/parsing/agent_config_parser.py b/src/main/python/rlbot/parsing/agent_config_parser.py index a6fb84030..d3a368bab 100644 --- a/src/main/python/rlbot/parsing/agent_config_parser.py +++ b/src/main/python/rlbot/parsing/agent_config_parser.py @@ -34,11 +34,11 @@ def __init__(self, config_directory, config_obj: ConfigObject, config_file_name: def get_absolute_path(self, header, key): path = self.base_agent_config.get(header, key) if path is None: - raise configparser.NoSectionError("Could not find {}: {} in the provided configuration!".format(header, key)) + raise configparser.NoSectionError(f"Could not find {header}: {key} in the provided configuration!") if os.path.isabs(path): return path if self.config_directory is None: - raise ValueError("Can't locate {} because it's a relative path and we don't know where to look!".format(path)) + raise ValueError(f"Can't locate {path} because it's a relative path and we don't know where to look!") joined = os.path.join(self.config_directory, path) return os.path.realpath(joined) @@ -58,7 +58,7 @@ def add_participant_header(config_object): participant_header.add_value(PARTICIPANT_TYPE_KEY, str, default='rlbot', description="""Accepted values are "human", "rlbot", "psyonix" and "party_member_bot" - You can have up to 4 local players and they must + You can have up to 4 local players and they must be activated in game or it will crash. If no player is specified you will be spawned in as spectator! human - not controlled by the framework @@ -113,7 +113,7 @@ def get_team(config, index): def get_bot_config_bundle(bot_config_path) -> BotConfigBundle: if not os.path.isfile(bot_config_path): - raise FileNotFoundError("Could not find bot config file {}!".format(bot_config_path)) + raise FileNotFoundError(f"Could not find bot config file {bot_config_path}!") raw_bot_config = configparser.RawConfigParser() raw_bot_config.read(bot_config_path, encoding='utf8') config_directory = os.path.dirname(os.path.realpath(bot_config_path)) @@ -127,8 +127,8 @@ def validate_bot_config(config_bundle) -> None: Checks the config bundle to see whether it has all required attributes. """ if not config_bundle.name: - raise AttributeError("Bot config {} has no name configured!".format( - os.path.join(config_bundle.config_directory, config_bundle.config_file_name or ''))) + bot_config = os.path.join(config_bundle.config_directory, config_bundle.config_file_name or '') + raise AttributeError(f"Bot config {bot_config} has no name configured!") # This will raise an exception if we can't find the looks config, or if it's malformed get_looks_config(config_bundle) @@ -217,7 +217,8 @@ def load_bot_config(index, bot_configuration, config_bundle: BotConfigBundle, lo BaseAgent._parse_bot_loadout_paint(bot_configuration, looks_config_object, BOT_CONFIG_LOADOUT_PAINT_BLUE_HEADER) if team_num == 1 and looks_config_object.has_section(BOT_CONFIG_LOADOUT_PAINT_ORANGE_HEADER): - BaseAgent._parse_bot_loadout_paint(bot_configuration, looks_config_object, BOT_CONFIG_LOADOUT_PAINT_ORANGE_HEADER) + BaseAgent._parse_bot_loadout_paint(bot_configuration, looks_config_object, + BOT_CONFIG_LOADOUT_PAINT_ORANGE_HEADER) python_file = 'NO_MODULE_FOR_PARTICIPANT' bot_parameters = None diff --git a/src/main/python/rlbot/parsing/custom_config.py b/src/main/python/rlbot/parsing/custom_config.py index b1099beb1..dd49fd51d 100644 --- a/src/main/python/rlbot/parsing/custom_config.py +++ b/src/main/python/rlbot/parsing/custom_config.py @@ -281,7 +281,7 @@ def copy(self): return ConfigValue(self.type, self.default, self.description, self.value) def init_indices(self, max_index): - self.value = [None]*max_index + self.value = [None] * max_index def parse_file(self, config_parser, value_name, max_index=None): if isinstance(config_parser, ConfigHeader): diff --git a/src/main/python/rlbot/parsing/incrementing_integer.py b/src/main/python/rlbot/parsing/incrementing_integer.py index c2905c553..1cf5968f1 100644 --- a/src/main/python/rlbot/parsing/incrementing_integer.py +++ b/src/main/python/rlbot/parsing/incrementing_integer.py @@ -2,6 +2,7 @@ class IncrementingInteger: """ Keeps track of an integer value as it gets incremented. """ + def __init__(self, initial_val: int): self.value = initial_val diff --git a/src/main/python/rlbot/parsing/match_settings_config_parser.py b/src/main/python/rlbot/parsing/match_settings_config_parser.py index e4f99f16a..82e732c72 100644 --- a/src/main/python/rlbot/parsing/match_settings_config_parser.py +++ b/src/main/python/rlbot/parsing/match_settings_config_parser.py @@ -247,27 +247,32 @@ def parse_mutator_settings(mutator_settings, config): mutator_settings.overtime_option = safe_get_mutator(overtime_mutator_types, config, MUTATOR_OVERTIME) mutator_settings.series_length_option = safe_get_mutator(series_length_mutator_types, config, MUTATOR_SERIES_LENGTH) mutator_settings.game_speed_option = safe_get_mutator(game_speed_mutator_types, config, MUTATOR_GAME_SPEED) - mutator_settings.ball_max_speed_option = safe_get_mutator(ball_max_speed_mutator_types, config, MUTATOR_BALL_MAX_SPEED, {'0': 'Default'}) + mutator_settings.ball_max_speed_option = safe_get_mutator( + ball_max_speed_mutator_types, config, MUTATOR_BALL_MAX_SPEED, {'0': 'Default'}) mutator_settings.ball_type_option = safe_get_mutator(ball_type_mutator_types, config, MUTATOR_BALL_TYPE) mutator_settings.ball_weight_option = safe_get_mutator(ball_weight_mutator_types, config, MUTATOR_BALL_WEIGHT) - mutator_settings.ball_size_option = safe_get_mutator(ball_size_mutator_types, config, MUTATOR_BALL_SIZE, {'1.0': 'Default'}) - mutator_settings.ball_bounciness_option = safe_get_mutator(ball_bounciness_mutator_types, config, MUTATOR_BALL_BOUNCINESS, {'1.0': 'Default'}) + mutator_settings.ball_size_option = safe_get_mutator( + ball_size_mutator_types, config, MUTATOR_BALL_SIZE, {'1.0': 'Default'}) + mutator_settings.ball_bounciness_option = safe_get_mutator( + ball_bounciness_mutator_types, config, MUTATOR_BALL_BOUNCINESS, {'1.0': 'Default'}) mutator_settings.boost_amount_option = safe_get_mutator(boost_amount_mutator_types, config, MUTATOR_BOOST_AMOUNT) mutator_settings.rumble_option = safe_get_mutator(rumble_mutator_types, config, MUTATOR_RUMBLE) - mutator_settings.boost_strength_option = safe_get_mutator(boost_strength_mutator_types, config, MUTATOR_BOOST_STRENGTH, {'Default': '1x', '1.0': '1x'}) + mutator_settings.boost_strength_option = safe_get_mutator( + boost_strength_mutator_types, config, MUTATOR_BOOST_STRENGTH, {'Default': '1x', '1.0': '1x'}) mutator_settings.gravity_option = safe_get_mutator(gravity_mutator_types, config, MUTATOR_GRAVITY) mutator_settings.demolish_option = safe_get_mutator(demolish_mutator_types, config, MUTATOR_DEMOLISH) - mutator_settings.respawn_time_option = safe_get_mutator(respawn_time_mutator_types, config, MUTATOR_RESPAWN_TIME, {'3.0': '3 Seconds', '3': '3 Seconds'}) + mutator_settings.respawn_time_option = safe_get_mutator(respawn_time_mutator_types, config, MUTATOR_RESPAWN_TIME, { + '3.0': '3 Seconds', '3': '3 Seconds'}) -def safe_get_mutator(mutator_options, config, mutator_name, replacement_table = {}): +def safe_get_mutator(mutator_options, config, mutator_name, replacement_table={}): value = config.get(MUTATOR_CONFIGURATION_HEADER, mutator_name) if value in replacement_table: logger.warn('**************************************') - logger.warn('The value you\'ve set for {} ({}) is deprecated and will need to be changed to ' - '"{}" for the next version. Please check your rlbot.cfg!'.format(mutator_name, value, replacement_table[value])) + logger.warn(f'The value you\'ve set for {mutator_name} ({value}) is deprecated and will need to be changed to ' + f'"{replacement_table[value]}" for the next version. Please check your rlbot.cfg!') logger.warn('**************************************') time.sleep(2.0) value = replacement_table[value] @@ -276,11 +281,13 @@ def safe_get_mutator(mutator_options, config, mutator_name, replacement_table = return mutator_options.index(value) except ValueError: logger.warn('**************************************') - logger.warn('The value you\'ve set for {} ({}) is invalid, and will be ignored. Please check your rlbot.cfg!'.format(mutator_name, value)) + logger.warn(f'The value you\'ve set for {mutator_name} ({value}) is invalid, and will be ignored. ' + 'Please check your rlbot.cfg!') logger.warn('**************************************') time.sleep(2.0) return 0 + def parse_match_settings(match_settings, config): """ Parses the matching settings modifying the match settings object. diff --git a/src/main/python/rlbot/parsing/rlbot_config_parser.py b/src/main/python/rlbot/parsing/rlbot_config_parser.py index ba36ddf41..8bc6cd6b9 100644 --- a/src/main/python/rlbot/parsing/rlbot_config_parser.py +++ b/src/main/python/rlbot/parsing/rlbot_config_parser.py @@ -36,7 +36,8 @@ def create_bot_config_layout(): return config_object -def parse_configurations(start_match_configuration, config_parser, config_location, config_bundle_overrides, looks_configs): +def parse_configurations(start_match_configuration, config_parser, config_location, config_bundle_overrides, + looks_configs): bot_names = [] bot_teams = [] python_files = [] diff --git a/src/main/python/rlbot/runner.py b/src/main/python/rlbot/runner.py index d2ad25fb6..782eece33 100644 --- a/src/main/python/rlbot/runner.py +++ b/src/main/python/rlbot/runner.py @@ -1,9 +1,11 @@ from rlbot.setup_manager import SetupManager +from rlbot.utils.python_version_check import check_python_version def main(): print("starting") + check_python_version() manager = SetupManager() manager.connect_to_game() manager.load_config() diff --git a/src/main/python/rlbot/setup_manager.py b/src/main/python/rlbot/setup_manager.py index 13a0139d6..d3e3314dc 100644 --- a/src/main/python/rlbot/setup_manager.py +++ b/src/main/python/rlbot/setup_manager.py @@ -71,17 +71,20 @@ def connect_to_game(self): if self.has_started: return version.print_current_release_notes() - if not process_configuration.is_process_running(ROCKET_LEAGUE_PROCESS_INFO['program'], ROCKET_LEAGUE_PROCESS_INFO['program_name']): + if not process_configuration.is_process_running(ROCKET_LEAGUE_PROCESS_INFO['program'], + ROCKET_LEAGUE_PROCESS_INFO['program_name']): try: - webbrowser.open('steam://rungameid/{}'.format(ROCKET_LEAGUE_PROCESS_INFO['gameid'])) + webbrowser.open(f"steam://rungameid/{ROCKET_LEAGUE_PROCESS_INFO['gameid']}") except webbrowser.Error: - self.logger.info("Unable to launch Rocket League automatically. Please launch Rocket League manually to continue.") + self.logger.info( + "Unable to launch Rocket League automatically. Please launch Rocket League manually to continue.") self.game_interface.inject_dll() self.game_interface.load_interface() self.agent_metadata_queue = mp.Queue() self.has_started = True - def load_config(self, framework_config=None, config_location=DEFAULT_RLBOT_CONFIG_LOCATION, bot_configs=None, looks_configs=None): + def load_config(self, framework_config=None, config_location=DEFAULT_RLBOT_CONFIG_LOCATION, bot_configs=None, + looks_configs=None): """ :param framework_config: A config object that indicates what bots to run. May come from parsing a rlbot.cfg. :param config_location: The location of the rlbot.cfg file, which will be used to resolve relative paths. @@ -121,7 +124,6 @@ def launch_ball_prediction(self): prediction_util.copy_pitch_data_to_temp('soccar') self.ball_prediction_process = prediction_util.launch() - def launch_bot_processes(self): self.logger.debug("Launching bot processes") self.kill_sub_processes() @@ -137,7 +139,8 @@ def launch_bot_processes(self): process = mp.Process(target=SetupManager.run_agent, args=(self.quit_event, quit_callback, reload_request, self.parameters[i], str(self.start_match_configuration.player_configuration[i].name), - self.teams[i], i, self.python_files[i], self.agent_metadata_queue, queue_holder)) + self.teams[i], i, self.python_files[i], self.agent_metadata_queue, + queue_holder)) process.start() self.sub_processes.append(process) @@ -254,12 +257,12 @@ def kill_agent_process_ids(self): try: parent = psutil.Process(pid) for child in parent.children(recursive=True): # or parent.children() for recursive=False - self.logger.info("Killing {} (child of {})".format(child.pid, pid)) + self.logger.info(f"Killing {child.pid} (child of {pid})") try: child.kill() except psutil._exceptions.NoSuchProcess: self.logger.info("Already dead.") - self.logger.info("Killing {}".format(pid)) + self.logger.info(f"Killing {pid}") try: parent.kill() except psutil._exceptions.NoSuchProcess: diff --git a/src/main/python/rlbot/training/status_rendering.py b/src/main/python/rlbot/training/status_rendering.py index bd858a727..b9b7fc087 100644 --- a/src/main/python/rlbot/training/status_rendering.py +++ b/src/main/python/rlbot/training/status_rendering.py @@ -5,6 +5,7 @@ Row = namedtuple('Row', 'exercise_name status_str status_color_func') + class TrainingStatusRenderer: """ This class draws the current state of the training exercises @@ -21,8 +22,8 @@ class TrainingStatusRenderer: def __init__(self, exercise_names: List[str], renderman: RenderingManager): self.names = exercise_names self.renderman = renderman - self.rows = [ Row(name, '', renderman.black) for name in exercise_names ] - self.name_to_index = { name: i for i, name in enumerate(exercise_names) } + self.rows = [Row(name, '', renderman.black) for name in exercise_names] + self.name_to_index = {name: i for i, name in enumerate(exercise_names)} self.last_modified_index = 0 self._render() @@ -37,9 +38,11 @@ def _render(self): return self.renderman.begin_rendering(self.RENDER_GROUP) - def get_text_y(row_y:int) -> float: + + def get_text_y(row_y: int) -> float: return self.LINE_HEIGHT * row_y + self.Y_BEGIN - def draw_string(x_offset:float, row_y:int, text:str, color_func): + + def draw_string(x_offset: float, row_y: int, text: str, color_func): self.renderman.draw_string_2d( self.X_BEGIN + x_offset, get_text_y(row_y), diff --git a/src/main/python/rlbot/training/training.py b/src/main/python/rlbot/training/training.py index afefd1dff..dcfcb405f 100644 --- a/src/main/python/rlbot/training/training.py +++ b/src/main/python/rlbot/training/training.py @@ -1,9 +1,8 @@ -from datetime import datetime, timedelta +from datetime import datetime from typing import Union, Optional, Mapping, Iterator, Tuple import random import time import traceback -import threading from rlbot.setup_manager import SetupManager from rlbot.utils import rate_limiter @@ -14,27 +13,38 @@ from .status_rendering import TrainingStatusRenderer, Row # Extend Pass and/or Fail to add your own, more detailed metrics. + + class Pass: """ Indicates that the bot passed the exercise. """ + def __repr__(self): return 'PASS' + + class Fail: """ Indicates that the bot failed the exercise. """ + def __repr__(self): return 'FAIL' + class FailDueToExerciseException(Fail): """ Indicates that the test code threw an expetion. """ + def __init__(self, exception: Exception, traceback_string: str): self.exception = exception self.traceback_string = traceback_string + def __repr__(self): return 'FAIL: Exception raised by Exercise:\n' + self.traceback_string + # Note: not using Grade as a abstract base class for Pass/Fail # as there should not be Grades which are neither Pass nor Fail. Grade = Union[Pass, Fail] + class Exercise: """ Statisfy this interface to define your test cases. @@ -47,6 +57,7 @@ class Exercise: Gets the config with which this exercise should be run. It is required to be immutable. (Fixed per instance) """ + def get_config_path(self) -> str: raise NotImplementedError() @@ -58,6 +69,7 @@ def get_config_path(self) -> str: exercise, this parameter and the bots should be the only things which causes variations between runs. """ + def setup(self, rng: random.Random) -> GameState: raise NotImplementedError() @@ -70,6 +82,7 @@ def setup(self, rng: random.Random) -> GameState: If this method returns Pass() or Fail() or raises an exceptions, the run of the exercise is terminated and any metrics will be returned. """ + def on_tick(self, game_tick_packet: GameTickPacket) -> Optional[Grade]: raise NotImplementedError() @@ -82,7 +95,7 @@ def __init__(self, input_exercise: Exercise, input_seed: int, grade: Grade): self.grade = grade -def run_all_exercises(exercises: Mapping[str, Exercise], seeds:Iterator[int]=None) -> Iterator[Tuple[str, Result]]: +def run_all_exercises(exercises: Mapping[str, Exercise], seeds: Iterator[int]=None) -> Iterator[Tuple[str, Result]]: """ Runs all the given named exercises. We order the runs such the number of match changes is minimized as they're slow. @@ -100,7 +113,7 @@ def run_all_exercises(exercises: Mapping[str, Exercise], seeds:Iterator[int]=Non game_interface = setup_manager.game_interface ren = TrainingStatusRenderer( - [name for _,name, _ in run_tuples], + [name for _, name, _ in run_tuples], game_interface.renderer ) @@ -138,11 +151,12 @@ def run_all_exercises(exercises: Mapping[str, Exercise], seeds:Iterator[int]=Non yield (name, result) except KeyboardInterrupt: - pass # exit gracefully + pass # exit gracefully ren.clear_screen() setup_manager.shut_down() + def _wait_until_bots_ready(setup_manager: SetupManager): total_ready = 0 total_ready += setup_manager.try_recieve_agent_metadata() @@ -152,7 +166,8 @@ def _wait_until_bots_ready(setup_manager: SetupManager): time.sleep(0.1) total_ready += setup_manager.try_recieve_agent_metadata() -def _wait_until_good_ticks(game_interface: GameInterface, required_new_ticks:int=3): + +def _wait_until_good_ticks(game_interface: GameInterface, required_new_ticks: int=3): """Blocks until we're getting new packets, indicating that the match is ready.""" rate_limit = rate_limiter.RateLimiter(120) last_tick_game_time = None # What the tick time of the last observed tick was @@ -179,6 +194,7 @@ def _setup_match(config_path: str, manager: SetupManager): manager.launch_bot_processes() manager.start_match() + def _setup_exercise(game_interface: GameInterface, ex: Exercise, seed: int) -> Optional[Result]: # Set the game state rng = random.Random() @@ -189,11 +205,11 @@ def _setup_exercise(game_interface: GameInterface, ex: Exercise, seed: int) -> O return Result(ex, seed, FailDueToExerciseException(e, traceback.format_exc())) game_interface.set_game_state(game_state) + def _grade_exercise(game_interface: GameInterface, ex: Exercise, seed: int) -> Result: grade = None rate_limit = rate_limiter.RateLimiter(120) last_tick_game_time = None # What the tick time of the last observed tick was - last_call_real_time = datetime.now() # When we last called the Agent game_tick_packet = GameTickPacket() # We want to do a deep copy for game inputs so people don't mess with em # Run until the Exercise finishes. diff --git a/src/main/python/rlbot/utils/class_importer.py b/src/main/python/rlbot/utils/class_importer.py index 4acd5b4e7..e1ad3ee2e 100644 --- a/src/main/python/rlbot/utils/class_importer.py +++ b/src/main/python/rlbot/utils/class_importer.py @@ -61,7 +61,7 @@ def load_external_class(python_file, base_class): return BaseAgent, BaseAgent.__module__ if not os.path.isfile(python_file): - raise FileNotFoundError("Could not find file {}!".format(python_file)) + raise FileNotFoundError(f"Could not find file {python_file}!") dir_name = os.path.dirname(python_file) module_name = os.path.splitext(os.path.basename(python_file))[0] @@ -88,7 +88,6 @@ def extract_class(containing_module, base_class): if issubclass(agent[1], base_class) and agent[1].__module__ == containing_module.__name__] if len(valid_classes) == 0: - raise ModuleNotFoundError('Could not locate a suitable bot class in module {}'.format(containing_module.__file__)) + raise ModuleNotFoundError(f"Could not locate a suitable bot class in module {containing_module.__file__}") return valid_classes[0] - diff --git a/src/main/python/rlbot/utils/file_util.py b/src/main/python/rlbot/utils/file_util.py index 93e2c59e3..a9f703007 100644 --- a/src/main/python/rlbot/utils/file_util.py +++ b/src/main/python/rlbot/utils/file_util.py @@ -27,6 +27,6 @@ def contains_locked_file(directory: str): with open(file_path, 'a'): pass except IOError: - logger.debug("Locked file: {}".format(file_path)) + logger.debug(f"Locked file: {file_path}") return True return False diff --git a/src/main/python/rlbot/utils/game_state_util.py b/src/main/python/rlbot/utils/game_state_util.py index 07b15196b..c15826790 100644 --- a/src/main/python/rlbot/utils/game_state_util.py +++ b/src/main/python/rlbot/utils/game_state_util.py @@ -143,6 +143,7 @@ def convert_to_flat(self, builder): DesiredBoostState.DesiredBoostStateAddRespawnTime(builder, Float.CreateFloat(builder, self.respawn_time)) return DesiredBoostState.DesiredBoostStateEnd(builder) + class GameInfoState: def __init__(self, world_gravity_z: float = None, game_speed: float = None): diff --git a/src/main/python/rlbot/utils/public_utils.py b/src/main/python/rlbot/utils/public_utils.py index 3aa3a9492..27f42de80 100644 --- a/src/main/python/rlbot/utils/public_utils.py +++ b/src/main/python/rlbot/utils/public_utils.py @@ -19,6 +19,8 @@ def is_safe_to_upgrade(): return not contains_locked_file(get_rlbot_directory()) # https://stackoverflow.com/questions/3764291/checking-network-connection + + def have_internet(): conn = httplib.HTTPConnection("www.google.com", timeout=5) try: diff --git a/src/main/python/rlbot/utils/python_version_check.py b/src/main/python/rlbot/utils/python_version_check.py index 9efbe639a..9d2ecaf45 100644 --- a/src/main/python/rlbot/utils/python_version_check.py +++ b/src/main/python/rlbot/utils/python_version_check.py @@ -7,6 +7,11 @@ minimum_python_version = (3, 6) # Deliberately using old string formatting for compatibility. -error_string = "You appear to be using an old version of Python: %s\n RLBot requires Python %d.%d or later.\n After installing, ensure your environment point to the new Python version, then run setup.bat" % ((sys.version,) + minimum_python_version) +error_string = """You appear to be using an old version of Python: %s + RLBot requires Python %d.%d or later. + After installing, ensure your environment point to the new Python version, then run setup.bat""" % ( + (sys.version,) + minimum_python_version) -assert sys.version_info >= minimum_python_version, error_string + +def check_python_version(): + assert sys.version_info >= minimum_python_version, error_string diff --git a/src/main/python/rlbot/utils/rendering/rendering_manager.py b/src/main/python/rlbot/utils/rendering/rendering_manager.py index 92a7f2965..d4fdd9ead 100644 --- a/src/main/python/rlbot/utils/rendering/rendering_manager.py +++ b/src/main/python/rlbot/utils/rendering/rendering_manager.py @@ -311,32 +311,7 @@ def __create_vector(self, *vec) -> Vector3: import numbers if len(vec) == 1: - if isinstance(vec[0], list) or isinstance(vec[0], tuple): - if 1 < len(vec[0]) <= 3: - if isinstance(vec[0][0], numbers.Number) and isinstance(vec[0][1], numbers.Number): - x = vec[0][0] - y = vec[0][1] - else: - raise ValueError( - "Unexpected type(s) for creating vector: {0}, {1}".format(type(vec[0][0]), type(vec[0][1]))) - if len(vec[0]) == 2: - z = 0 - else: - if isinstance(vec[0][2], numbers.Number): - z = vec[0][2] - else: - raise ValueError("Unexpected type for creating vector: {0}".format(type(vec[0][2]))) - else: - raise ValueError("Unexpected list/tuple length for creating vector: {0}".format(len(vec))) - elif isinstance(vec[0], Vector3.Vector3): - x = vec[0].X() - y = vec[0].Y() - z = vec[0].Z() - elif isinstance(vec[0], GameDataStructVector3): - x = vec[0].x - y = vec[0].y - z = vec[0].z - elif hasattr(vec[0], "__getitem__"): # Support all subscriptable types. + if hasattr(vec[0], "__getitem__"): # Support all subscriptable types. try: x = float(vec[0][0]) y = float(vec[0][1]) @@ -345,13 +320,19 @@ def __create_vector(self, *vec) -> Vector3: except (ValueError, IndexError): z = 0 except ValueError: - raise ValueError( - "Unexpected type(s) for creating vector: {0}, {1}, {2}".format( - type(vec[0][0]), type(vec[0][1]), type(vec[0][2]))) + raise ValueError(f"Unexpected type(s) for creating vector: {type(vec[0][0])}, {type(vec[0][1])}") except IndexError: - raise IndexError("Unexpected IndexError when creating vector from type: {0}".format(type(vec[0]))) + raise IndexError(f"Unexpected IndexError when creating vector from type: {type(vec[0])}") + elif isinstance(vec[0], Vector3.Vector3): + x = vec[0].X() + y = vec[0].Y() + z = vec[0].Z() + elif isinstance(vec[0], GameDataStructVector3): + x = vec[0].x + y = vec[0].y + z = vec[0].z else: - raise ValueError("Unexpected type for creating vector: {0}".format(type(vec[0]))) + raise ValueError(f"Unexpected type for creating vector: {type(vec[0])}") elif len(vec) == 2 or len(vec) == 3: if isinstance(vec[0], numbers.Number) and isinstance(vec[1], numbers.Number): x = vec[0] @@ -362,9 +343,9 @@ def __create_vector(self, *vec) -> Vector3: if isinstance(vec[2], numbers.Number): z = vec[2] else: - raise ValueError("Unexpected type for creating vector: {0}".format(type(vec[0]))) + raise ValueError(f"Unexpected type for creating vector: {type(vec[0])}") else: - raise ValueError("Unexpected type(s) for creating vector: {0}, {1}".format(type(vec[0]), type(vec[1]))) + raise ValueError(f"Unexpected type(s) for creating vector: {type(vec[0])}, {type(vec[1])}") else: raise ValueError("Unexpected number of arguments for creating vector") diff --git a/src/main/python/rlbot/utils/structures/game_data_struct.py b/src/main/python/rlbot/utils/structures/game_data_struct.py index b424e04b8..b55e4b464 100644 --- a/src/main/python/rlbot/utils/structures/game_data_struct.py +++ b/src/main/python/rlbot/utils/structures/game_data_struct.py @@ -103,7 +103,8 @@ class GameInfo(Struct): ("world_gravity_z", ctypes.c_float), ("game_speed", ctypes.c_float)] -# On the c++ side this struct has a long at the beginning for locking. This flag is removed from this struct so it isn't visible to users. +# On the c++ side this struct has a long at the beginning for locking. +# This flag is removed from this struct so it isn't visible to users. class GameTickPacket(Struct): diff --git a/src/main/python/rlbot/utils/structures/game_interface.py b/src/main/python/rlbot/utils/structures/game_interface.py index 522d4804d..83789cd59 100644 --- a/src/main/python/rlbot/utils/structures/game_interface.py +++ b/src/main/python/rlbot/utils/structures/game_interface.py @@ -30,6 +30,7 @@ def caller(id, status): def get_dll_location(): return os.path.join(get_dll_directory(), 'RLBot_Core_Interface.dll') + def get_dll_32_location(): return os.path.join(get_dll_directory(), 'RLBot_Core_Interface_32.dll') @@ -202,10 +203,9 @@ def inject_dll(self): 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 {}. ' + raise FileNotFoundError(f'{file} was not found in {get_dll_directory()}. ' '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())) + 'is not removing it. See https://github.com/RLBot/RLBot/wiki/Antivirus-Notes') incode = subprocess.call([injector_dir, 'hidden']) injector_codes = ['INJECTION_SUCCESSFUL', @@ -225,7 +225,7 @@ def inject_dll(self): sys.exit() def countdown(self, countdown_timer): - self.logger.info("Waiting {} seconds for DLL to load".format(countdown_timer)) + self.logger.info(f"Waiting {countdown_timer} seconds for DLL to load") for i in range(countdown_timer): sys.stdout.write(".") sys.stdout.flush() @@ -259,7 +259,7 @@ def set_game_state(self, game_state: GameState): 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 + 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)) diff --git a/src/main/python/rlbot/utils/structures/quick_chats.py b/src/main/python/rlbot/utils/structures/quick_chats.py index 0a867624b..9238661a9 100644 --- a/src/main/python/rlbot/utils/structures/quick_chats.py +++ b/src/main/python/rlbot/utils/structures/quick_chats.py @@ -15,15 +15,15 @@ https://github.com/RLBot/RLBot/blob/master/src/main/flatbuffers/rlbot.fbs """ QuickChats = create_enum_object([chat for chat in dir(QuickChatSelection.QuickChatSelection) - if not chat.startswith('__') - and not callable(getattr(QuickChatSelection.QuickChatSelection, chat))], + if not chat.startswith('__') and not + callable(getattr(QuickChatSelection.QuickChatSelection, chat))], list_name='quick_chat_list', other_attributes=[ ('CHAT_NONE', -1), ('CHAT_EVERYONE', False), ('CHAT_TEAM_ONLY', True) - ], - attribute_object=QuickChatSelection.QuickChatSelection) +], + attribute_object=QuickChatSelection.QuickChatSelection) def send_quick_chat_flat(game_interface, index, team, team_only, quick_chat): @@ -38,6 +38,7 @@ def send_quick_chat_flat(game_interface, index, team, team_only, quick_chat): return game_interface.send_chat_flat(builder) + def send_quick_chat(queue_holder, index, team, team_only, quick_chat): """ Sends a quick chat to the general queue for everyone to pull from diff --git a/src/main/python/rlbot/utils/structures/struct.py b/src/main/python/rlbot/utils/structures/struct.py index 53bd64209..031716411 100644 --- a/src/main/python/rlbot/utils/structures/struct.py +++ b/src/main/python/rlbot/utils/structures/struct.py @@ -1,5 +1,6 @@ import ctypes + class Struct(ctypes.Structure): """ This class exists to add common python functionality to ctypes.Structure. diff --git a/src/main/python/rlbot/version.py b/src/main/python/rlbot/version.py index 7301f4fc3..057a02243 100644 --- a/src/main/python/rlbot/version.py +++ b/src/main/python/rlbot/version.py @@ -3,9 +3,11 @@ # 2) we can import it in setup.py for the same reason # 3) we can import it into your module module # https://stackoverflow.com/questions/458550/standard-way-to-embed-version-into-python-package + __version__ = '1.9.3' release_notes = { + '1.9.3': """ - *Much* faster core dll initialization! - ccman32 - Adding support for a training mode! Check out https://github.com/RLBot/RLBotTraining - DomNomNom @@ -15,14 +17,16 @@ - Various quality-of-life improvements - DomNomNom - Making the GUI load all bots in the current directory - tarehart """, + '1.8.3': """ - Allow SimpleControllerState initialization. - Marvin - Passing more params to subprocess agents. - whatisaphone - Made game data structs support comparison and repr in python. - DomNomNom - Fixing double-logging bug. - Marvin - + For whatisaphone's mouse cursor freedom, roll back to 1.8.2. """, + '1.8.1': """ - Ability to modify gravity via state setting. Ball prediction reacts properly, and bots are informed of the gravity in the game tick packet! - Marvin @@ -30,58 +34,70 @@ - Making logging more configurable. - Marvin - Fixing custom quick chats. - Marvin """, + '1.7.0': """ The big news: We now support painted items thanks to ccman32! See https://github.com/RLBot/RLBot/wiki/Bot-Customization for details. - + Other stuff: - Fix for tradeable items not displaying correctly after December update. - ccman32 - Sending invalid controller inputs will no longer make your bot freeze, plus you'll get friendly warnings. - tarehart """, + '1.6.5': """ - Support all subscriptable types for rendering. - Marvin + Support all subscriptable types for rendering. - Marvin """, + '1.6.4': """ - Fixed compatibility with December update - ccman32 + Fixed compatibility with December update - ccman32 Added a friendly warning about unsupported python versions - DomNomNom Stopped scaring people with statements about locked files - tarehart """, - '1.6.1': """ + + '1.6.1': """ Fixed GUI crash when loading certain RLBot config files with relative paths for agents. Fixed agent preset loading to allow multiple agents to saved/loaded correctly if they have the same name. - ima9rd """, - '1.6.0':""" + + '1.6.0': """ Add support for auto starting .NET executables. """, + '1.5.1': """ Fixed crash with GUI when no default RLBot.cfg file was found. - Updated GUI to launch Rocket League when clicking run if no Rocket League process is found. - ima9rd + Updated GUI to launch Rocket League when clicking run if no Rocket League process is found. - ima9rd """, + '1.5.0': """ Adding a have_internet helper function to help streamline upgrade checks. - ima9rd """, + '1.4.2': """ Adding support for auto-running java bots during tournaments. To take advantage of this in your bot, see https://github.com/RLBot/RLBotJavaExample/wiki/Auto-Launching-Java - + Plus bug fixes: - Fixed a bug where auto-run executables would crash when trying to write to stderr. - Dragging bots to another team in the GUI no longer breaks the config. """, + '1.3.0': """ Accurate ball prediction for Hoops and Dropshot modes! - Kipje13, Marvin, NeverCast, et. al. """, + '1.2.6': """ Fixed a bug where field info was not extracted properly during dropshot mode. It was reporting 2 goals rather than the expected 140. """, + + '1.2.5': """ *************************************************** * Fix for dodge cancels / half flips! - ccman32 * *************************************************** - + Plus: - Changing the rendering strategy for 3D lines that go past the camera. Formerly it was "draw it, even though it's crazy sometimes", now it will be "don't draw it". @@ -92,19 +108,22 @@ - Got rid of the libpng warning seen when using the GUI. - Giving specific error messages when cfg files are messed up. """, + '1.2.2': """ - Rearranged the GUI a bit, and made it load and track appearance configs more effectively. - Fixed bug where RUN button behavior in the GUI would not work after killing bots. """, + '1.2.0': """ - We now offer a 'RigidBodyTick' thanks to whatisaphone! It's a lower-level representation of - physics data which updates at 120Hz and is not subject to interpolation. You can still make a + physics data which updates at 120Hz and is not subject to interpolation. You can still make a great bot without it, but this feature is quite nice for the scientists among us. - + See https://github.com/RLBot/RLBotPythonExample/wiki/Rigid-Body-Tick for more details! - + - Faster way to access ball prediction data in python. - Skyborg """, + '1.1.3': """ - Faster way to access ball prediction data in python. - Skyborg - Java bots will now shut down when the python framework quits. This has been necessary recently @@ -112,9 +131,11 @@ - Shutting down the python framework will no longer attempt to kill bots twice in a row. - Clicking on the "Run" button twice in a row in the GUI will no longer spawn duplicate processes. """, + '1.1.2': """ Faster way to access ball prediction data in python. - Skyborg """, + '1.1.1': """ You can now get information about the ball's status in Dropshot mode thanks to hallo_doei! Read all about it at https://github.com/RLBot/RLBot/wiki/Dropshot @@ -126,13 +147,16 @@ ability to get game tick data during replays and the postgame. - tarehart - Fixed a bug where bots would dodge when they intended to double jump. -tarehart """, + '1.0.6': """ The latest Rocket League patch broke dodges for our bots; this update fixes it. """, + '1.0.5': """ Maximum size for a render message has been decreased again because many people experienced errors related to memory access. The limit is now only double the original. """, + '1.0.4': """ - Maximum size for a render message has been increased by a factor of 100. This means you can draw a lot of lines at once without getting errors. @@ -141,6 +165,7 @@ - Fixed a crash that would commonly happen after a match ends. As a side effect, you can no longer see up-to-date player data during instant replays. """, + '1.0.3': """ Time for the big 1.0 release! We actually left "beta" a long time ago so this isn't as big a milestone as the number implies, but we DO have two great new features! @@ -162,25 +187,28 @@ - Bug fix for people with spaces in their file path by Zaptive - Subprocess agent for future Rust support by whatisaphone """, + '0.0.32': """ More comprehensive fix for Rocket League patch 1.50. Compared to previous version: - Dropshot tile data is fixed - Boost pad data is fixed - Loadout configuration is fixed - + Thanks to ccman32 and dtracers for delivering this fix quickly! """, + '0.0.31': """ Rapid response to Rocket League patch 1.50 with the following known issues: - Dropshot tile data is missing - Boost pad data is missing - Loadout configuration is broken - + Thanks to ccman32 and dtracers for delivering this short-term fix quickly. We will follow this up with a proper fix as soon as possible. You may also choose to stay on Rocket League 1.49 and RLBot 0.0.30, ask for instructions on discord. """, + '0.0.30': """ - New core dll that is less likely to break when Rocket League is patched - ccman32 and hallo-doei - Fixed bug resulting in incorrect quickchat - dtracers @@ -220,7 +248,7 @@ def get_help_text(): def print_current_release_notes(): print(release_banner) - print("Version {}".format(__version__)) + print(f"Version {__version__}") print(get_current_release_notes()) print(get_help_text()) print("") diff --git a/src/test/python/agents/atba/atba.cfg b/src/test/python/agents/atba/atba.cfg index 0ecb4f6a4..89e7466b4 100644 --- a/src/test/python/agents/atba/atba.cfg +++ b/src/test/python/agents/atba/atba.cfg @@ -17,7 +17,7 @@ test_quickchat = False # if true bot will render dropshot info test_dropshot = True # if true bot will cause physics chaos with itself and the ball -test_state = True +test_state = False # if true the bot will render the predicted path of the ball test_ball_prediction = False # if true the bot will print RB physics state diff --git a/src/test/python/agents/atba/atba.py b/src/test/python/agents/atba/atba.py index a2fd983d7..404b511e7 100644 --- a/src/test/python/agents/atba/atba.py +++ b/src/test/python/agents/atba/atba.py @@ -4,7 +4,8 @@ from rlbot.parsing.custom_config import ConfigObject from rlbot.utils.structures.game_data_struct import GameTickPacket, DropshotTileState from rlbot.utils.structures.quick_chats import QuickChats -from rlbot.utils.game_state_util import GameState, BoostState, BallState, CarState, GameInfoState, Physics, Vector3, Rotator +from rlbot.utils.game_state_util import GameState, BoostState, BallState, CarState, GameInfoState, Physics, Vector3 +from rlbot.utils.game_state_util import Rotator class Atba(BaseAgent): diff --git a/src/test/python/integration_test/all_tests.py b/src/test/python/integration_test/all_tests.py index d3d2b02dd..219a0b0fd 100644 --- a/src/test/python/integration_test/all_tests.py +++ b/src/test/python/integration_test/all_tests.py @@ -3,6 +3,7 @@ from integration_test.tests.blah_test import test_tick_rate, test_sufficient_data + def run_all_tests(): log('Loading history...') io = HistoryIO() diff --git a/src/test/python/integration_test/framework_test_agent/framework_test_agent.py b/src/test/python/integration_test/framework_test_agent/framework_test_agent.py index b5a5b5c15..b9cebffff 100644 --- a/src/test/python/integration_test/framework_test_agent/framework_test_agent.py +++ b/src/test/python/integration_test/framework_test_agent/framework_test_agent.py @@ -7,8 +7,9 @@ An integration test bot ''' + def is_close(x, target, margin): - return abs(x-target) <= margin + return abs(x - target) <= margin class FrameworkTestAgent(FlatBot): diff --git a/src/test/python/integration_test/gather_data.py b/src/test/python/integration_test/gather_data.py index c5782bb96..0c50c7eff 100644 --- a/src/test/python/integration_test/gather_data.py +++ b/src/test/python/integration_test/gather_data.py @@ -1,7 +1,6 @@ import multiprocessing import psutil import os -import sys import configparser from rlbot.setup_manager import SetupManager @@ -27,6 +26,7 @@ def record_atba(): manager.start_match() manager.infinite_loop() # Runs terminated by timeout in other thread. + def ensure_dll_is_injected(): manager = SetupManager() manager.connect_to_game() @@ -37,11 +37,13 @@ def KILL(process): process.kill() except psutil._exceptions.NoSuchProcess as e: return + + def kill_proc_tree(pid): parent = psutil.Process(pid) children = parent.children(recursive=True) - KILL(parent) # THIS CAN NOT CONTINUE THIS CAN NOT CONTINUE THIS CAN NOT CONTINUE THIS CAN NOT CONTINUE - for child in children: # THIS CAN NOT CONTINUE THIS CAN NOT CONTINUE THIS CAN NOT CONTINUE THIS CAN NOT CONTINUE + KILL(parent) # THIS CAN NOT CONTINUE THIS CAN NOT CONTINUE THIS CAN NOT CONTINUE THIS CAN NOT CONTINUE + for child in children: # THIS CAN NOT CONTINUE THIS CAN NOT CONTINUE THIS CAN NOT CONTINUE THIS CAN NOT CONTINUE KILL(child) # THIS CAN NOT CONTINUE THIS CAN NOT CONTINUE THIS CAN NOT CONTINUE THIS CAN NOT CONTINUE gone, still_alive = psutil.wait_procs(children, timeout=5) @@ -50,7 +52,7 @@ def gather_data(timeout=20.0): log("Gathering data...") HistoryIO().clear() - # Do this synchonously, the time the process needs to startup is more consistent. + # Do this synchonously, the time the process needs to startup is more consistent. ensure_dll_is_injected() proc = multiprocessing.Process(target=record_atba) diff --git a/src/test/python/integration_test/tests/blah_test.py b/src/test/python/integration_test/tests/blah_test.py index c75fde8c1..4eb13146c 100644 --- a/src/test/python/integration_test/tests/blah_test.py +++ b/src/test/python/integration_test/tests/blah_test.py @@ -2,9 +2,11 @@ # TODO: bikeshed about testing frameworks # TODO: write more tests. eg. "atba should hit the ball" + def test_sufficient_data(history): assert len(history) > 100 + def test_tick_rate(history): ''' Checks that we're consistently running at 60 ticks/s''' fps = 60 # I know it's not frames but I find it easier to read than tps. @@ -16,16 +18,16 @@ def is_admissible(history_item): return history_item.game_tick_proto.GameInfo().IsRoundActive() intervals = [ - get_time(history[i+1]) - get_time(history[i]) - for i in range(len(history)-1) - if is_admissible(history[i+1]) and is_admissible(history[i]) + get_time(history[i + 1]) - get_time(history[i]) + for i in range(len(history) - 1) + if is_admissible(history[i + 1]) and is_admissible(history[i]) ] assert len(intervals) > 10, "Didn't get enough admissible game_tick_proto's. (got {})".format(len(intervals)) def is_interval_acceptable(interval): acceptable_margin = 0.1 / fps - return abs(interval - 1./fps) < acceptable_margin - acceptables = [ is_interval_acceptable(interval) for interval in intervals ] + return abs(interval - 1. / fps) < acceptable_margin + acceptables = [is_interval_acceptable(interval) for interval in intervals] average_interval = sum(intervals) / len(intervals) proportion_acceptable = sum(acceptables) / len(acceptables) @@ -37,5 +39,5 @@ def is_interval_acceptable(interval): # print (i) assert tick_rate_is_good, 'Not running at a consistent {} fps. Only {} out of {} frames were on time ({} %). average_interval={:3f} ({:3f} fps)'.format( - fps, sum(acceptables), len(acceptables), proportion_acceptable*100, average_interval, 1/average_interval + fps, sum(acceptables), len(acceptables), proportion_acceptable * 100, average_interval, 1 / average_interval ) diff --git a/src/test/python/runner_integration_test.py b/src/test/python/runner_integration_test.py index 8f361a8fd..50a19a3c0 100644 --- a/src/test/python/runner_integration_test.py +++ b/src/test/python/runner_integration_test.py @@ -5,9 +5,11 @@ from integration_test.gather_data import gather_data from integration_test.all_tests import run_all_tests + def main(): gather_data() # Note: you can comment this out if you want to analyze a history.pb.bin run_all_tests() + if __name__ == '__main__': main()