Team Messages
Introduction
Team messages allow bots in the same team to communicate with each other during a battle. This enables coordinated strategies where a leader bot can give orders to teammate bots, or teammates can share information like enemy positions.
This article demonstrates team messaging using the MyFirstTeam sample team, which consists of:
- MyFirstLeader: A leader bot that scans for enemies and sends commands to teammates
- MyFirstDroid: Droid bots (four instances) that follow the leader's orders
Note: Droids are special bots that have more energy but no radar. They rely entirely on team messages from their leader to know where enemies are located.
How Team Messages Work
Team messages are automatically serialized to JSON format when sent and deserialized back to typed objects when received. This happens internally—you simply send and receive objects without worrying about the serialization details.
The key concepts are:
- Broadcasting: Send a message to all teammates using
broadcastTeamMessage() - Direct messaging: Send a message to a specific teammate using
sendTeamMessage() - Receiving: Override the
onTeamMessage()event handler to process incoming messages - Type matching: Message classes are matched by name between sender and receiver
Message Types
In the MyFirstTeam example, two message types are used:
| Message Type | Purpose | Sent When |
|---|---|---|
RobotColors | Synchronize team colors across all bots | At the start of a round |
Point | Share enemy position coordinates for droids to fire | When an enemy is scanned |
Sending a Batch in One Event
When several updates belong together, send them as one ordered batch. The receiving bot gets one TeamMessageEvent whose message is a TeamMessageBatch; iterate messages to process the entries. The batch remains one packet and one callback, so the server and Bot API do not create a separate event for every entry. Batching reduces per-message framing and callback work, but does not compress the JSON entries.
sendTeamMessageBatch(teammateId, List.of(new Point(250, 300), new Point(400, 180)));
@Override
public void onTeamMessage(TeamMessageEvent event) {
if (event.getMessage() instanceof TeamMessageBatch batch) {
for (Object message : batch.getMessages()) {
// Process each entry in send order.
}
}
}SendTeamMessageBatch(teammateId, new object[] { new Point(250, 300), new Point(400, 180) });
public override void OnTeamMessage(TeamMessageEvent evt) {
if (evt.Message is TeamMessageBatch batch) {
foreach (var message in batch.Messages) {
// Process each entry in send order.
}
}
}self.send_team_message_batch(teammate_id, [Point(250, 300), Point(400, 180)])
async def on_team_message(self, event: TeamMessageEvent) -> None:
if isinstance(event.message, TeamMessageBatch):
for message in event.message.messages:
# Process each entry in send order.
passthis.sendTeamMessageBatch(teammateId, [point1, point2]);
override onTeamMessage(event: TeamMessageEvent) {
if (event.message instanceof TeamMessageBatch) {
for (const message of event.message.messages) {
// Process each entry in send order.
}
}
}All members of the team must advertise batch version 1; otherwise the server rejects the sender's intent. A batch is delivered on the next turn, and entries keep their send order.
Limits and errors
Each bot may send up to 64 team-message packets per turn and up to 128 logical payloads total across ordinary messages and batches. An encoded packet may contain at most 48 KiB (49,152 UTF-8 bytes), and the compact JSON teamMessages array may contain at most 256 KiB (262,144 UTF-8 bytes) per turn. The byte counts use UTF-8 after compact JSON encoding. Each client API checks a call before enqueueing it and throws if the packet or the complete turn would exceed a limit; empty batches and null entries are rejected. The server rejects an invalid intent as a whole, so no messages from that intent are delivered. Incoming WebSocket text frames are limited to 1 MiB before JSON parsing.
Batching groups payloads in one packet and callback; it does not compress their contents. These Tank Royale per-turn limits are separate from classic Robocode's 32,768-byte limit on the original Java-serialized message object.
Legacy Robocode robots can put a serializable collection inside one broadcastMessage or sendMessage call and iterate that collection from one MessageEvent. The original Java-serialized object must fit classic Robocode's 32,768-byte message limit.
Defining Message Classes
Each bot must define its own message classes. The classes are matched by name, so they must have the same name and compatible properties on both the sender and receiver.
// Point (x,y) class
class Point {
public final double x;
public final double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
}
// Robot colors class
class RobotColors {
public Color bodyColor;
public Color tracksColor;
public Color turretColor;
public Color gunColor;
public Color radarColor;
public Color scanColor;
public Color bulletColor;
}// Point (x,y) class
class Point
{
public double X { get; set; }
public double Y { get; set; }
public Point(double x, double y)
{
X = x;
Y = y;
}
}
// Robot colors class
class RobotColors
{
public Color BodyColor { get; set; }
public Color TracksColor { get; set; }
public Color TurretColor { get; set; }
public Color GunColor { get; set; }
public Color RadarColor { get; set; }
public Color ScanColor { get; set; }
public Color BulletColor { get; set; }
}from dataclasses import dataclass
from typing import Optional
from robocode_tank_royale.bot_api import team_message_type
from robocode_tank_royale.bot_api.color import Color
# Point (x,y) class
@team_message_type
@dataclass
class Point:
x: float
y: float
# Robot colors class
@team_message_type
@dataclass
class RobotColors:
body_color: Optional[Color] = None
tracks_color: Optional[Color] = None
turret_color: Optional[Color] = None
gun_color: Optional[Color] = None
radar_color: Optional[Color] = None
scan_color: Optional[Color] = None
bullet_color: Optional[Color] = None// Point (x,y) interface
interface Point {
type: "Point";
x: number;
y: number;
}
// Robot colors interface
interface RobotColors {
type: "RobotColors";
bodyColor: string;
tracksColor: string;
turretColor: string;
gunColor: string;
radarColor: string;
scanColor: string;
bulletColor: string;
}
type TeamMessage = Point | RobotColors;Python Note: In Python, you must use the
@team_message_typedecorator to register message classes for automatic serialization and deserialization. The decorator enables typed team messages that work like Java and C#.
TypeScript Note: In TypeScript, team messages are plain JSON objects. Use
JSON.stringify()when sending andJSON.parse()when receiving. A discriminated union with atypefield is the recommended pattern for handling multiple message types.
The Leader Bot: Sending Messages
The leader bot is responsible for scanning enemies and coordinating the team. Here's how MyFirstLeader sends messages:
Sending Robot Colors at Round Start
At the beginning of each round, the leader sets up team colors and broadcasts them to all teammates:
@Override
public void run() {
// Prepare robot colors to send to teammates
var colors = new RobotColors();
colors.bodyColor = Color.RED;
colors.tracksColor = Color.CYAN;
colors.turretColor = Color.RED;
colors.gunColor = Color.YELLOW;
colors.radarColor = Color.RED;
colors.scanColor = Color.YELLOW;
colors.bulletColor = Color.YELLOW;
// Set our own colors
setBodyColor(colors.bodyColor);
setTracksColor(colors.tracksColor);
setTurretColor(colors.turretColor);
setGunColor(colors.gunColor);
setRadarColor(colors.radarColor);
setScanColor(colors.scanColor);
setBulletColor(colors.bulletColor);
// Send RobotColors object to every member in the team
broadcastTeamMessage(colors);
// Set radar to turn forever and start moving
setTurnRadarLeft(Double.POSITIVE_INFINITY);
while (isRunning()) {
forward(100);
back(100);
}
}public override void Run()
{
// Prepare robot colors to send to teammates
var colors = new RobotColors();
colors.BodyColor = Color.Red;
colors.TracksColor = Color.Cyan;
colors.TurretColor = Color.Red;
colors.GunColor = Color.Yellow;
colors.RadarColor = Color.Red;
colors.ScanColor = Color.Yellow;
colors.BulletColor = Color.Yellow;
// Set our own colors
BodyColor = colors.BodyColor;
TracksColor = colors.TracksColor;
TurretColor = colors.TurretColor;
GunColor = colors.GunColor;
RadarColor = colors.RadarColor;
ScanColor = colors.ScanColor;
BulletColor = colors.BulletColor;
// Send RobotColors object to every member in the team
BroadcastTeamMessage(colors);
// Set radar to turn forever and start moving
SetTurnRadarLeft(Double.PositiveInfinity);
while (IsRunning)
{
Forward(100);
Back(100);
}
}async def run(self) -> None:
# Prepare robot colors to send to teammates
colors = RobotColors()
colors.body_color = Color.RED
colors.tracks_color = Color.CYAN
colors.turret_color = Color.RED
colors.gun_color = Color.YELLOW
colors.radar_color = Color.RED
colors.scan_color = Color.YELLOW
colors.bullet_color = Color.YELLOW
# Set our own colors
self.body_color = colors.body_color
self.tracks_color = colors.tracks_color
self.turret_color = colors.turret_color
self.gun_color = colors.gun_color
self.radar_color = colors.radar_color
self.scan_color = colors.scan_color
self.bullet_color = colors.bullet_color
# Send RobotColors object to every member in the team
self.broadcast_team_message(colors)
# Set radar to turn forever and start moving
self.set_turn_radar_left(float("inf"))
while self.running:
await self.forward(100)
await self.back(100)override run() {
// Prepare robot colors to send to teammates
const colors: RobotColors = {
type: "RobotColors",
bodyColor: ColorUtil.toHex(Color.RED),
tracksColor: ColorUtil.toHex(Color.CYAN),
turretColor: ColorUtil.toHex(Color.RED),
gunColor: ColorUtil.toHex(Color.YELLOW),
radarColor: ColorUtil.toHex(Color.RED),
scanColor: ColorUtil.toHex(Color.YELLOW),
bulletColor: ColorUtil.toHex(Color.YELLOW),
};
// Set our own colors
this.setBodyColor(Color.RED);
this.setTracksColor(Color.CYAN);
this.setTurretColor(Color.RED);
this.setGunColor(Color.YELLOW);
this.setRadarColor(Color.RED);
this.setScanColor(Color.YELLOW);
this.setBulletColor(Color.YELLOW);
// Send RobotColors object to every member in the team
this.broadcastTeamMessage(JSON.stringify(colors));
// Set radar to turn forever and start moving
this.setTurnRadarLeft(Number.POSITIVE_INFINITY);
while (this.isRunning()) {
this.forward(100);
this.back(100);
}
}Sending Enemy Positions
When the leader scans an enemy (not a teammate), it broadcasts the enemy's position to all droids:
@Override
public void onScannedBot(ScannedBotEvent e) {
// Ignore teammates
if (isTeammate(e.getScannedBotId())) {
return;
}
// Send enemy position to teammates
broadcastTeamMessage(new Point(e.getX(), e.getY()));
}public override void OnScannedBot(ScannedBotEvent evt)
{
// Ignore teammates
if (IsTeammate(evt.ScannedBotId))
{
return;
}
// Send enemy position to teammates
BroadcastTeamMessage(new Point(evt.X, evt.Y));
}async def on_scanned_bot(self, e: ScannedBotEvent) -> None:
# Ignore teammates
if self.is_teammate(e.scanned_bot_id):
return
# Send enemy position to teammates
self.broadcast_team_message(Point(x=e.x, y=e.y))override onScannedBot(e: ScannedBotEvent) {
// Ignore teammates
if (this.isTeammate(e.scannedBotId)) {
return;
}
// Send enemy position to teammates
const point: Point = { type: "Point", x: e.x, y: e.y };
this.broadcastTeamMessage(JSON.stringify(point));
}The Droid Bot: Receiving Messages
The droid bots wait for team messages and react accordingly. Since droids have no radar, they depend entirely on the leader for target information.
Handling Team Messages
The droid overrides onTeamMessage() to handle incoming messages:
@Override
public void onTeamMessage(TeamMessageEvent e) {
var message = e.getMessage();
if (message instanceof Point) {
// Message is a point towards a target
var target = (Point) message;
// Turn body to target
turnRight(bearingTo(target.x, target.y));
// Fire hard!
fire(3);
} else if (message instanceof RobotColors) {
// Message contains new robot colors
var colors = (RobotColors) message;
setBodyColor(colors.bodyColor);
setTracksColor(colors.tracksColor);
setTurretColor(colors.turretColor);
setGunColor(colors.gunColor);
setRadarColor(colors.radarColor);
setScanColor(colors.scanColor);
setBulletColor(colors.bulletColor);
}
}public override void OnTeamMessage(TeamMessageEvent evt)
{
var message = evt.Message;
if (message is Point target)
{
// Message is a point towards a target
// Turn body to target
TurnRight(BearingTo(target.X, target.Y));
// Fire hard!
Fire(3);
}
else if (message is RobotColors colors)
{
// Message contains new robot colors
BodyColor = colors.BodyColor;
TracksColor = colors.TracksColor;
TurretColor = colors.TurretColor;
GunColor = colors.GunColor;
RadarColor = colors.RadarColor;
ScanColor = colors.ScanColor;
BulletColor = colors.BulletColor;
}
}async def on_team_message(self, e: TeamMessageEvent) -> None:
message = e.message
if isinstance(message, Point):
# Message is a point towards a target
# Turn body to target and fire hard
await self.turn_right(self.bearing_to(message.x, message.y))
await self.fire(3)
elif isinstance(message, RobotColors):
# Message contains new robot colors
self.body_color = message.body_color
self.tracks_color = message.tracks_color
self.turret_color = message.turret_color
self.gun_color = message.gun_color
self.radar_color = message.radar_color
self.scan_color = message.scan_color
self.bullet_color = message.bullet_coloroverride onTeamMessage(e: TeamMessageEvent) {
const message = JSON.parse(e.message) as TeamMessage;
if (message.type === "Point") {
// Message is a point towards a target
this.turnRight(this.bearingTo(message.x, message.y));
this.fire(3);
} else if (message.type === "RobotColors") {
// Message contains new robot colors
this.setBodyColor(ColorUtil.fromHexColor(message.bodyColor));
this.setTracksColor(ColorUtil.fromHexColor(message.tracksColor));
this.setTurretColor(ColorUtil.fromHexColor(message.turretColor));
this.setGunColor(ColorUtil.fromHexColor(message.gunColor));
this.setRadarColor(ColorUtil.fromHexColor(message.radarColor));
this.setScanColor(ColorUtil.fromHexColor(message.scanColor));
this.setBulletColor(ColorUtil.fromHexColor(message.bulletColor));
}
}The Droid Run Loop
The droid's main loop is simple—it just waits for messages:
@Override
public void run() {
System.out.println("MyFirstDroid ready");
while (isRunning()) {
go(); // Execute next turn, team messages drive the logic
}
}public override void Run()
{
Console.WriteLine("MyFirstDroid ready");
while (IsRunning)
{
Go(); // Execute next turn, team messages drive the logic
}
}async def run(self) -> None:
print("MyFirstDroid ready")
while self.running:
await self.go() # Execute next turn, team messages drive the logicoverride run() {
console.log("MyFirstDroid ready");
while (this.isRunning()) {
this.go(); // Execute next turn, team messages drive the logic
}
}Creating a Team
To create a team, you need a team configuration file (JSON) that specifies the team members:
{
"name": "MyFirstTeam",
"version": "1.0",
"authors": [
"Mathew Nelson",
"Flemming N. Larsen"
],
"license": "Apache-2.0",
"description": "A sample team. MyFirstLeader scans for enemies, and orders the droids to fire.",
"teamMembers": [
"MyFirstLeader",
"MyFirstDroid",
"MyFirstDroid",
"MyFirstDroid",
"MyFirstDroid"
]
}Note that MyFirstDroid is listed four times—this means four instances of the droid will be created in the battle.
Implementing the Droid Interface
To create a droid bot, you must implement the Droid interface (or DroidABC in Python). This signals to the game that your bot is a droid with the special characteristics (more energy, no radar):
public class MyFirstDroid extends Bot implements Droid {
// ... bot implementation
}public class MyFirstDroid : Bot, Droid
{
// ... bot implementation
}from robocode_tank_royale.bot_api.droid_abc import DroidABC
class MyFirstDroid(Bot, DroidABC):
# ... bot implementationclass MyFirstDroid extends Bot implements Droid {
// ... bot implementation
}Message Sending Methods
| Method | Description |
|---|---|
broadcastTeamMessage(message) | Send a message to all teammates |
sendTeamMessage(teammateId, message) | Send a message to a specific teammate |
Team bot IDs are numeric in Tank Royale. To show or log the corresponding team-visible name, use getBotName(botId) in Java and TypeScript, GetBotName(botId) in .NET, or get_bot_name(bot_id) in Python. The lookup returns the bot's own name or a teammate's name; it returns null/None for an opponent or an unknown ID. It is available after the game-start event. The server sends names only for the bot and its teammates.
Each Bot API can send optional BotInfo.teamMemberName metadata in bot-handshake. The server then appends the bot version and a battle-wide (n) suffix when names repeat. If teamMemberName is absent, the regular display name is used. This lets bridge APIs preserve long classic robot names without changing the bot's displayed name.
Limitations
- Maximum packets per turn: 64 per bot, including batches
- Maximum logical payloads per turn: 128, counting all messages inside batches
- Maximum packet size: 49,152 UTF-8 bytes; the compact
teamMessagesarray is limited to 262,144 UTF-8 bytes - Delivery: accepted packets arrive on the next turn; batch entries remain ordered and share one event
- Failure behavior: a client rejects an invalid call before enqueueing it, and the server rejects an invalid intent in full
- Batch compatibility: every recipient must use a Bot API that advertises batch version 1
- WebSocket input: text frames above 1 MiB are closed before the server parses the JSON
- Serialization: Messages must be serializable to JSON (no circular references)
Best Practices
Define identical message classes: Both sender and receiver must have message classes with the same name and compatible properties
Check for teammates: Always use
isTeammate()before processing scanned bots to avoid targeting your own teamKeep messages simple: Use simple data types that serialize well to JSON
Handle all message types: Your
onTeamMessage()handler should handle all expected message types gracefullyConsider message timing: Messages are processed on the next turn, so factor in potential movement when broadcasting positions
Summary
Team messaging in Tank Royale enables powerful team coordination:
- Leaders scan the battlefield and broadcast commands
- Droids (or other teammates) receive and act on those commands
- Message classes are matched by name, allowing typed communication
- Colors, positions, and custom commands can all be shared via team messages
Try running MyFirstTeam against other bots to see team coordination in action!
Advanced Team Strategies
Ready to take your team coordination to the next level? Explore advanced strategies in The Book of Robocode:
- Anti-Gravity Movement - Position based on multiple threats (useful for team positioning)
- GuessFactor Targeting - Statistical targeting for coordinated attacks
- Energy Management - Strategic resource allocation in team battles
For a hands-on guide to implementing team messages in each language, see the Team Messages Guide in the Bot API repository.
Also see Team Strategies for foundational team battle concepts and testing strategies.