-
Notifications
You must be signed in to change notification settings - Fork 3
Match Comm
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.
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.
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", orNone
to display nothing. -
team_only
: If True, only your team (blue/orange) will receive the message. For scripts, this means only other scripts.
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
is0
or1
: This is the index of the player in theGamePacket
. - If
team
is2
: This is the index of the script inself.match_config.script_configurations
.
- If
-
team
: The team that the sender is from.0
for blue,1
for orange, and2
for scripts. -
content
: The content of the message containing arbitrary data. -
display
: The message that is ebing displayed in the game in "quick chat", orNone
if nothing was displayed. -
team_only
: If True, only your team (blue/orange) received this message. For scripts, this means only other scripts.
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:
-
Save the list of all the player names (into the variable
player_names
) for later use and use inhandle_match_comm
. -
Find the first player in
packet
that's on the enemy team and isn't yet demolished, saving the index infirst_enemy_idx
. -
If
first_enemy_idx
is different from the last tick and isn'tNone
, 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.
- This match comm will have a
-
Set
self.last_enemy_idx
tofirst_enemy_idx
for us to use in the next tick.
In handle_match_comm
, we:
- Ignore messages that aren't from our team or have no content.
- 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...
- 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.
- 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.