Skip to content

Migration

VirxEC edited this page Sep 17, 2024 · 8 revisions

RLBot v4 to v5

Legend: ✨=new, πŸ› =reworked/changed, β›”=removed

πŸ›  .cfg -> .toml

Instead of using .cfg files with a format that's only parsed properly by Python, RLBot v5 now uses the standard .toml format. This changes makes it easy to grab your language's off-the-shelf TOML parser to read the configuration file and get the same data that RLBot would.

To see how TOML works, check out the TOML spec.

todo: example of migrating bot.cfg to bot.toml and appearance.cfg to loadout.toml

πŸ›  main.py

In RLBot v5, every bot is now more similar to v4's StandaloneBot. This means that your bot MUST run it's run method at the bottom of the file, or else IT WILL NOT RUN.

Here is a minimal example of a bot in RLBot v5:

from rlbot.flat import *
from rlbot.managers import Bot


class MyBot(Bot):
    def initialize_agent(self):
        """
        Called for all heaver initialization that needs to happen.
        Field info and match settings are fully loaded at this point, and won't return garbage data.

        NOTE: `self.index` is not set at this point, and should not be used. `self.team` and `self.name` _are_ set with correct information.
        """

        # A little known feature of even v4,
        # bots have a `logger` attribute that can be used to log messages.
        self.logger.info("Setting default values that require more match info!")

    def get_output(self, packet: GameTickPacket) -> ControllerState:
        """
        Where all the logic of your bot gets its input and returns its output.
        """

        return ControllerState()


if __name__ == "__main__":
    # This is the entry point for the bot.
    MyBot().run()

πŸ›  BaseAgent/StandaloneBot -> Bot

There have been several changes to how you access data like field info, ball prediction, etc.

  • πŸ›  def __init__(self, name: str, team: int, index: int) -> def __init__(self)

    • These variables are no longer known at this time.

    • NOTE: This method is similar-ish to the "early start" of v4, but you probably shouldn't use it. If you want to define default variables, the following is more Pythonic:

      class MyBot(Bot):
          my_variable = None
          counter = 1

      These variables can then be accessed like normal via self.my_variable and self.counter.

      If you need to perform more complex initialization, it's recommended you use initialize_agent instead.

  • πŸ›  initialize_agent - This method blocks the match from starting until it's finished running. All heavy initialization should be done here, as the purpose of the match not starting is to ensure that all bots had time to boot up and are ready to go.

  • πŸ›  self.get_field_info() -> self.field_info - Example:

    from rlbot.flat import *
    from rlbot.managers import Bot
    
    
    class MyBot(Bot):
        def initialize_agent(self):
            # Log a yellow warning if the number of boost pads is not 34 pads in a standard map!
            if len(self.field_info.boost_pads) != 34:
                self.logger.warning(
                    "The standard number of boost pads is 34, but this map has %d:%s",
                    len(self.field_info.boost_pads),
                    "\n".join(map(str, self.field_info.boost_pads)),
                )
    
        def get_output(self, packet: GameTickPacket) -> ControllerState:
            return ControllerState()
    
    
    if __name__ == "__main__":
        MyBot().run()
  • πŸ›  self.get_match_settings() -> self.match_settings - Example:

    from rlbot.flat import *
    from rlbot.managers import Bot
    
    
    class MyBot(Bot):
        gravity = -650
    
        def initialize_agent(self):
            match self.match_settings.mutator_settings.gravity_option:
                case GravityOption.Low:
                    self.gravity /= 2
                case GravityOption.High:
                    self.gravity *= 1.75
                case GravityOption.Super_High:
                    self.gravity *= 5
                case GravityOption.Reverse:
                    self.gravity *= -1
    
        def get_output(self, packet: GameTickPacket) -> ControllerState:
            return ControllerState()
    
    
    if __name__ == "__main__":
        MyBot().run()
  • πŸ›  self.get_ball_prediction_struct() -> self.ball_prediction

  • β›” def is_hot_reload_enabled(self) -> bool - This method is no longer needed, and as such as been removed.

  • πŸ›  def set_game_state(self, game_state: GameState) - This method has been replaced by the following, where the GameState wrapper has been removed:

    def set_game_state(
        self,
        balls: dict[int, DesiredBallState] = {},
        cars: dict[int, DesiredCarState] = {},
        game_info: Optional[DesiredGameInfoState] = None,
        commands: list[ConsoleCommand] = [],
    )

    For balls and cars, the int key is the index of the ball or car you want to modify.

    Example usage, where the game speed is set to 2x:

    from rlbot.flat import DesiredGameInfoState
    
    # ...
    
    self.set_game_state(
        game_info=DesiredGameInfoState(game_speed=2)
    )
  • πŸ›  QuickChats & MatchComms have been combined into one

    • def handle_quick_chat(self, index: int, team: int, quick_chat: QuickChats) - This method has been replaced by the following, where content is a bytes object that contains the message, and display is an optional str that was owned in-game as a v4 QuickChat used to:

      from rlbot.flat import *
      
      # ...
      
      def handle_match_communication(
          self,
          index: int,
          team: int,
          content: bytes,
          display: Optional[str],
          team_only: bool,
      ):
          # Be careful with `content`, it can be anything! Make sure to validate it before using it.
      
          self.logger.info(f"Received match communication from index {index}! {display}")
    • def send_quick_chat(self, team_only: bool, quick_chat: QuickChats) -> def send_match_comm(self, content: bytes, display: Optional[str] = None, team_only: bool = False) - Example:

      from rlbot.flat import *
      
      # ...
      
      # Print "What a save!" in the chat for all players to see
      # Note: `b""` is an empty byte string, which is the equivalent of `None` for bytes.
      # We're using it here to show that we don't need to send any extra data with the message.
      self.send_match_comm(b"", "What a save!")
      
      # Send "15 bot_has_ball" to only our team, with no message to print to the screen
      self.send_match_comm(b"15 bot_has_ball", team_only=True)

πŸ›  BallPrediction

  • β›” num_slices has been removed due to it not being needed anymore.
    • The length of the list slices is now limited to the number of slices in the prediction. Iterating over the list under the end is now the proper way to access all the data.

πŸ›  FieldInfoPacket -> FieldInfo

  • β›” num_boosts and num_goals have been removed due to them not being needed anymore.
    • The length of the lists boost_pads and goals are now limited to the number of boosts and goals on the field. Iterating over the lists under the end is now the proper way to access all the data.

πŸ›  MatchSettings

  • ✨ auto_start_bots - Whether or not RLBot should automatically start the bots & scripts. If set to False, they will not start until the user manually starts them - HOWEVER, the match will start IMMEDIATELY and will not wait for the bots to connect!

  • ✨ script_configurations - The scripts that are running in the match.

  • ✨ freeplay - Whether or not to start the match in Freeplay instead of a normal Exhibition match. May be useful for testing purposes.

  • ✨ launcher & game_path - How RLBot should start the game. The options are:

    • Launcher.Steam - Start the game through Steam.
      • game_path does nothing
    • Launcher.Epic - Windows only - Start the game through the Epic Games Store.
      • No game_path required anymore!
    • Launcher.Custom - Start the game through a custom method. Currently:
      • game_path = "" - The game will not be started by RLBot.
      • game_path = "legendary" - Start the game through the Epic Games Store via the Legendary launcher. Required to use EGS on Linux.
  • β›” game_map - Use game_map_upk instead. If you don't know the file names:

    import random
    
    from rlbot.utils.maps import GAME_MAP_TO_UPK, STANDARD_MAPS
    
    # grab random map name from the list STANDARD_MAPS
    random_map = random.choice(STANDARD_MAPS)
    
    # convert the map name to the upk file name
    game_map_upk = GAME_MAP_TO_UPK[random_map]
  • πŸ›  mutator_settings - New options!

    Interested in what mutators go with what games modes? Check out this list!

    • ✨ multi_ball
      • MultiBall.One
      • MultiBall.Two
      • MultiBall.Four
      • MultiBall.Six
    • ✨ max_time_option
      • MaxTimeOption.Default
      • MaxTimeOption.Eleven_Minutes
    • ✨ game_event_option
      • GameEventOption.Default
      • GameEventOption.Haunted
      • GameEventOption.Rugby
    • ✨ audio_option
      • AudioOption.Default
      • AudioOption.Haunted
    • πŸ›  max_score
      • ✨ MaxScore.Seven
    • πŸ›  ball_type_option
      • ✨ BallTypeOption.Beachball
      • ✨ BallTypeOption.Anniversary
      • ✨ BallTypeOption.Haunted
      • ✨ BallTypeOption.Ekin
      • ✨ BallTypeOption.SpookyCube
    • πŸ›  ball_weight_option
      • ✨ BallWeightOption.Curve_Ball
      • ✨ BallWeightOption.Beach_Ball_Curve
      • ✨ BallWeightOption.Magnus_FutBall
    • πŸ›  ball_size_option
      • ✨ BallSizeOption.Medium
    • πŸ›  ball_size_option
      • ✨ BallBouncinessOption.LowishBounciness
    • πŸ›  rumble_option
      • ✨ RumbleOption.Haunted_Ball_Beam
      • ✨ RumbleOption.Tactical
      • ✨ RumbleOption.BatmanRumble
    • πŸ›  boost_strength_option
      • ✨ BoostStrengthOption.Five
    • πŸ›  gravity_option
      • ✨ GravityOption.Reverse

πŸ›  GameTickPacket

  • πŸ›  game_cars -> players

    • πŸ›  PlayerInfo
      • ✨ latest_touch - The last time the player touched the ball.
        • Will be None if the player has not touched the ball since the last kickoff.
        • Contains the ball_index of the ball that was touched.
      • ✨ air_state - The current state of the car in the air. Possible values are:
        • AirState.OnGround - All 4 wheels are touching the ground.
        • AirState.Jumping - Lasts while jump is being held by the player
        • AirState.DoubleJumping - Lasts for ~13 ticks.
        • AirState.Dodging - Lasts for the duration of the torque applied by the dodge, or ~79 ticks.
        • AirState.InAir - The car is in the air, but not in any of the other states.
      • ✨ dodge_timeout - -1 when the a dodge/double jump is available for an infinite amount of time. This includes when the car in on the ground and when the car fell of a surface and didn't jump. Otherwise, it's the time remaining until the dodge/double jump expires and can no longer be used - 0 if a dodge/double jump is currently unavailable.
      • ✨ last_input - The last controller input the player used.
      • ✨ last_spectated - If the player was the last one to be watched by a spectator
      • ✨ accolades - A list of the accolades (as strings) the player earned in the previous tick. Here are some examples of different accolades:
        • Win, Loss, TimePlayed
        • Shot, Assist, Center, Clear, PoolShot
        • Goal, AerialGoal, BicycleGoal, BulletGoal, BackwardsGoal, LongGoal, OvertimeGoal, TurtleGoal
        • AerialHit, BicycleHit, BulletHit, JuggleHit, FirstTouch, BallHit
        • Save, EpicSave, FreezeSave
        • HatTrick, Savior, Playmaker, MVP
        • FastestGoal, SlowestGoal, FurthestGoal, OwnGoal
        • MostBallTouches, FewestBallTouches, MostBoostPickups, FewestBoostPickups, BoostPickups
        • CarTouches, Demolition, Demolish
        • LowFive, HighFive
      • πŸ›  is_demolished -> demolished_timeout - -1 if the player is not demolished, otherwise the time remaining until the player respawns.
      • β›” has_wheel_contact, jumped, and double_jumped have been removed due to them not being needed anymore.
        • has_wheel_contact is directly replaced by air_state == AirState.OnGround
        • jumped is directly replaced by checking if dodge_timeout is not -1, which means that the car has jumped.
        • double_jumped is not directly replaced by anything, but if dodge_timeout is 0 then the car either double jumped, dodged, or the time to do so has expired for the bot.
  • πŸ›  game_boosts -> boost_pads

  • πŸ›  game_teams -> teams

  • β›” num_cars, num_boosts, and num_teams have been removed due to them not being needed anymore.

  • The length of the lists players, boost_pad_states, and teams are now limited to the number of cars and boosts in the match. Iterating over the lists under the end is now the proper way to access all the data.

  • πŸ›  ball -> balls - balls is now a list, and RLBot officially supports multiple balls in a single match. However, there are also times in a normal game where this list has 0 items - it's recommended that this is checked for near the start of your logic, so your bot doesn't accidentally throw hundreds of errors.

    def get_output(self, packet: GameTickPacket) -> ControllerState:
        if len(packet.balls) == 0:
            return ControllerState()
    
        # Your logic here
        # ...
  • πŸ›  BallInfo

    • β›” latest_touch has been removed due to it now being tracked in PlayerInfo
    • β›” drop_shot_info has been removed due to RLBot not getting any dropshot data.
    • πŸ›  collision_shape -> shape
      • Old collision_shape type:

        class CollisionShape:
            type: ShapeType
            box: BoxShape
            sphere: SphereShape
            cylinder: CylinderShape
      • New shape type:

        class CollisionShape:
            item: Optional[BoxShape | SphereShape | CylinderShape]
  • πŸ›  game_info - Several arguments have been replaced by game_state_type

    • β›” is_round_active, is_kickoff_pause, and is_match_ended
      • is_round_active is directly replaced by game_state_type == GameStateType.Active
      • is_kickoff_pause is directly replaced by game_state_type == GameStateType.Kickoff
      • is_match_ended is directly replaced by game_state_type == GameStateType.Ended
    • ✨ game_state_type - The current state of the game. The options are:
      • GameStateType.Inactive - The game is not running.
      • GameStateType.Countdown - The 3.. 2.. 1.. countdown before the a kickoff.
      • GameStateType.Kickoff - After the countdown, but before the game timer starts counting down again. Usually the timer resumes after the ball gets hit by a car.
      • GameStateType.Active - Normal game play.
      • GameStateType.GoalScored - A goal has been scored and the goal animation is playing.
      • GameStateType.Replay - The goal replay is playing.
      • GameStateType.Paused - The game is paused.
      • GameStateType.Ended - The match finished and is on the end screen.
Clone this wiki locally