Skip to content

Match Comm

Virx edited this page Feb 18, 2025 · 2 revisions

Basics

Sending

from rlbot.flat import ControllerState, GamePacket
from rlbot.managers import Bot


class QuickChatExampleAgent(Bot):
    def get_output(self, packet: GamePacket) -> ControllerState:
        # There won't be any content of the message for other bots,
        # but "I got it!" will be display for a human to see!
        self.send_match_comm(b"", "I got it!")

        return ControllerState()


if __name__ == "__main__":
    QuickChatExampleAgent("rlbot_community/quickchat_example").run()

This is just a simple example that spams "I got it!" to the screen for a human to read.

The method send_match_comm exists in the Bot class, so our QuickChatExampleAgent automatically inherits this class method when deriving from Bot.

The empty content (the b"") part of the messages means that other bots will likely ignore it. A standard format for this field like TMCP should be used for this, but the field can contain any information at all. It does not have to be valid text.

Receiving

from rlbot.flat import ControllerState, GamePacket
from rlbot.managers import Bot


class QuickChatExampleAgent(Bot):
    def get_output(self, packet: GamePacket) -> ControllerState:
        return ControllerState()

    def handle_match_comm(
        self,
        index: int,
        team: int,
        content: bytes,
        display: Optional[str],
        team_only: bool,
    ):
        if display is None:
            return

        if team == 2:
            print(f"Script @ index {index} (team {team}) said \"{display}\"")
        else:
            print(f"Bot @ index {index} (team {team}) said \"{display}\"")


if __name__ == "__main__":
    QuickChatExampleAgent("rlbot_community/quickchat_example").run()

This example prints out every message that a human can see, and correctly identifies bots vs scripts.

Method definitions

self.send_match_comm

Emits a match communication message to other bots and scripts.

  • For Bot & Script:

    def send_match_comm(
        self,
        content: bytes,
        display: Optional[str] = None,
        team_only: bool = False
    ): ...
  • For Hivemind:

    def send_match_comm(
        self,
        index: int,
        content: bytes,
        display: Optional[str] = None,
        team_only: bool = False,
    ): ...
    • index: This additional argument is the index of the bot that the message should be sent from.

Args:

  • content: The content of the message containing arbitrary data.
  • display: The message to be displayed in the game in "quick chat", or None to display nothing.
  • team_only: If True, only your team (blue/orange) will receive the message. For scripts, this means only other scripts.

self.handle_match_comm

Called when a match communication message is received.

def handle_match_comm(
    self,
    index: int,
    team: int,
    content: bytes,
    display: Optional[str],
    team_only: bool,
): ...
  • index: The sender's index.
    • If team is 0 or 1: This is the index of the player in the GamePacket .
    • If team is 2: This is the index of the script in self.match_config.script_configurations.
  • team: The team that the sender is from. 0 for blue, 1 for orange, and 2 for scripts.
  • content: The content of the message containing arbitrary data.
  • display: The message that is ebing displayed in the game in "quick chat", or None if nothing was displayed.
  • team_only: If True, only your team (blue/orange) received this message. For scripts, this means only other scripts.

A realistic example

The field content can be anything. It's just a bunch of raw bytes in a row. How can you do something useful with this? The following example sends & receives JSON, which is much easier to work with:

import json

from rlbot.flat import ControllerState, GamePacket
from rlbot.managers import Bot


class QuickChatExampleAgent(Bot):
    player_names: list[str] = []
    last_enemy_idx: int | None = None

    def handle_match_comm(
        self,
        index: int,
        team: int,
        content: bytes,
        display: str | None,
        team_only: bool,
    ):
        if team != self.team or not content:
            return

        sender_name = self.player_names[index]

        if not team_only:
            self.logger.warning(f"{sender_name} is leaking secrets to the other team!!")
            return

        try:
            msg_str = content.decode("utf-8")
            msg = json.loads(msg_str)
        except UnicodeDecodeError:
            return
        except json.JSONDecodeError:
            return

        if "target" in msg:
            self.logger.info(f"{sender_name} is targeting {msg['target']}")

    def get_output(self, packet: GamePacket) -> ControllerState:
        self.player_names = [p.name for p in packet.players]

        first_enemy_idx = None

        for i, player in enumerate(packet.players):
            if player.team != self.team and player.demolished_timeout == -1:
                first_enemy_idx = i
                break

        if first_enemy_idx is not None and first_enemy_idx != self.last_enemy_idx:
            player_name = self.player_names[first_enemy_idx]
            msg = {"target": f"{player_name} - Player[{first_enemy_idx}]"}
            msg_str = json.dumps(msg)
            
            self.send_match_comm(msg_str.encode("utf-8"), "Here's Johnny!", team_only=True)

        self.last_enemy_idx = first_enemy_idx

        return ControllerState()


if __name__ == "__main__":
    QuickChatExampleAgent("rlbot_community/quickchat_example").run()

In get_output, we:

  1. Save the list of all the player names (into the variable player_names) for later use and use in handle_match_comm.

  2. Find the first player in packet that's on the enemy team and isn't yet demolished, saving the index in first_enemy_idx.

  3. If first_enemy_idx is different from the last tick and isn't None, send out a match comm:

    • This match comm will have a contents that is serialized JSON containing our wanted information.
    • Will print out a message in the game saying "Here's Johnny!" for a human to see.
    • A copy of this message will only be sent to the other bots on our team.
  4. Set self.last_enemy_idx to first_enemy_idx for us to use in the next tick.

In handle_match_comm, we:

  1. Ignore messages that aren't from our team or have no content.
  2. Check that the sent message was team only. If it's not, we call out our teammate and ignore the message! After all, we can't have our opponents knowing what we're going to do...
  3. Try to decode the message from bytes into JSON. If it's not valid JSON, we silently ignore the message. We can't understand it.
  4. Check if the variable target is inside our JSON. If it is, we log what our teammate is targeting! Realistically, we would store this information and use it to inform our game strategy.
Clone this wiki locally