Skip to content

Commit 72f3815

Browse files
authored
Merge pull request #7 from VirxEC/beta.9
Update to the newest RLBot v5 spec
2 parents 140e30a + 31a173d commit 72f3815

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+283
-271
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ description = "A high performance Python interface for communicating with RLBot
88
dynamic = ["version"]
99
requires-python = ">= 3.11"
1010
dependencies = [
11-
"rlbot_flatbuffers~=0.9.1",
11+
"rlbot_flatbuffers~=0.10.0",
1212
"psutil==6.*",
1313
]
1414
readme = "README.md"

rlbot/interface.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class SocketDataType(IntEnum):
2020
"""
2121

2222
NONE = 0
23-
GAME_TICK_PACKET = 1
23+
GAME_PACKET = 1
2424
FIELD_INFO = 2
2525
START_COMMAND = 3
2626
MATCH_SETTINGS = 4
@@ -34,6 +34,7 @@ class SocketDataType(IntEnum):
3434
STOP_COMMAND = 12
3535
SET_LOADOUT = 13
3636
INIT_COMPLETE = 14
37+
CONTROLLABLE_TEAM_INFO = 15
3738

3839

3940
MAX_SIZE_2_BYTES = 2**16 - 1
@@ -65,16 +66,23 @@ class SocketRelay:
6566
_should_continue = True
6667

6768
on_connect_handlers: list[Callable[[], None]] = []
68-
packet_handlers: list[Callable[[flat.GameTickPacket], None]] = []
69+
packet_handlers: list[Callable[[flat.GamePacket], None]] = []
6970
field_info_handlers: list[Callable[[flat.FieldInfo], None]] = []
7071
match_settings_handlers: list[Callable[[flat.MatchSettings], None]] = []
7172
match_communication_handlers: list[Callable[[flat.MatchComm], None]] = []
7273
ball_prediction_handlers: list[Callable[[flat.BallPrediction], None]] = []
74+
controllable_team_info_handlers: list[
75+
Callable[[flat.ControllableTeamInfo], None]
76+
] = []
7377
raw_handlers: list[Callable[[SocketMessage], None]] = []
7478

7579
def __init__(
76-
self, connection_timeout: float = 120, logger: Optional[logging.Logger] = None
80+
self,
81+
agent_id: str,
82+
connection_timeout: float = 120,
83+
logger: Optional[logging.Logger] = None,
7784
):
85+
self.agent_id = agent_id
7886
self.connection_timeout = connection_timeout
7987
self.logger = get_logger("interface") if logger is None else logger
8088

@@ -95,8 +103,8 @@ def send_bytes(self, data: bytes, data_type: SocketDataType):
95103
message = int_to_bytes(data_type) + int_to_bytes(size) + data
96104
self.socket.sendall(message)
97105

98-
def send_init_complete(self, init_complete: flat.InitComplete):
99-
self.send_bytes(init_complete.pack(), SocketDataType.INIT_COMPLETE)
106+
def send_init_complete(self):
107+
self.send_bytes(bytes(), SocketDataType.INIT_COMPLETE)
100108

101109
def send_set_loadout(self, set_loadout: flat.SetLoadout):
102110
self.send_bytes(set_loadout.pack(), SocketDataType.SET_LOADOUT)
@@ -191,6 +199,7 @@ def connect(
191199
handler()
192200

193201
flatbuffer = flat.ConnectionSettings(
202+
self.agent_id,
194203
wants_ball_predictions,
195204
wants_match_communications,
196205
close_after_match,
@@ -260,9 +269,9 @@ def handle_incoming_message(self, incoming_message: SocketMessage):
260269
match incoming_message.type:
261270
case SocketDataType.NONE:
262271
self._should_continue = False
263-
case SocketDataType.GAME_TICK_PACKET:
272+
case SocketDataType.GAME_PACKET:
264273
if len(self.packet_handlers) > 0:
265-
packet = flat.GameTickPacket.unpack(incoming_message.data)
274+
packet = flat.GamePacket.unpack(incoming_message.data)
266275
for handler in self.packet_handlers:
267276
handler(packet)
268277
case SocketDataType.FIELD_INFO:
@@ -285,6 +294,13 @@ def handle_incoming_message(self, incoming_message: SocketMessage):
285294
ball_prediction = flat.BallPrediction.unpack(incoming_message.data)
286295
for handler in self.ball_prediction_handlers:
287296
handler(ball_prediction)
297+
case SocketDataType.CONTROLLABLE_TEAM_INFO:
298+
if len(self.controllable_team_info_handlers) > 0:
299+
player_mappings = flat.ControllableTeamInfo.unpack(
300+
incoming_message.data
301+
)
302+
for handler in self.controllable_team_info_handlers:
303+
handler(player_mappings)
288304

289305
def disconnect(self):
290306
if not self.is_connected:

rlbot/managers/bot.py

Lines changed: 58 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,19 @@ class Bot:
2727
_initialized_bot = False
2828
_has_match_settings = False
2929
_has_field_info = False
30+
_has_player_mapping = False
3031

31-
_latest_packet: Optional[flat.GameTickPacket] = None
32+
_latest_packet: Optional[flat.GamePacket] = None
3233
_latest_prediction = flat.BallPrediction()
3334

34-
def __init__(self):
35-
spawn_id = os.environ.get("RLBOT_SPAWN_IDS")
35+
def __init__(self, default_agent_id: Optional[str] = None):
36+
agent_id = os.environ.get("RLBOT_AGENT_ID") or default_agent_id
3637

37-
if spawn_id is None:
38-
self.logger.warning("RLBOT_SPAWN_IDS environment variable not set")
39-
else:
40-
self.spawn_id = int(spawn_id)
41-
self.logger.info("Spawn ID: %s", self.spawn_id)
38+
if agent_id is None:
39+
self.logger.critical("RLBOT_AGENT_ID environment variable is not set")
40+
exit(1)
4241

43-
self._game_interface = SocketRelay(logger=self.logger)
42+
self._game_interface = SocketRelay(agent_id, logger=self.logger)
4443
self._game_interface.match_settings_handlers.append(self._handle_match_settings)
4544
self._game_interface.field_info_handlers.append(self._handle_field_info)
4645
self._game_interface.match_communication_handlers.append(
@@ -49,13 +48,23 @@ def __init__(self):
4948
self._game_interface.ball_prediction_handlers.append(
5049
self._handle_ball_prediction
5150
)
51+
self._game_interface.controllable_team_info_handlers.append(
52+
self._handle_player_mappings
53+
)
5254
self._game_interface.packet_handlers.append(self._handle_packet)
5355

5456
self.renderer = Renderer(self._game_interface)
5557

56-
def _initialize_agent(self):
58+
def _initialize(self):
59+
# search match settings for our name
60+
for player in self.match_settings.player_configurations:
61+
if player.spawn_id == self.spawn_id:
62+
self.name = player.name
63+
self.logger = get_logger(self.name)
64+
break
65+
5766
try:
58-
self.initialize_agent()
67+
self.initialize()
5968
except Exception as e:
6069
self.logger.critical(
6170
"Bot %s failed to initialize due the following error: %s", self.name, e
@@ -64,75 +73,53 @@ def _initialize_agent(self):
6473
exit()
6574

6675
self._initialized_bot = True
67-
self._game_interface.send_init_complete(flat.InitComplete(self.spawn_id))
76+
self._game_interface.send_init_complete()
6877

6978
def _handle_match_settings(self, match_settings: flat.MatchSettings):
7079
self.match_settings = match_settings
7180
self._has_match_settings = True
7281

73-
# search match settings for our spawn id
74-
for player in self.match_settings.player_configurations:
75-
if player.spawn_id == self.spawn_id:
76-
self.team = player.team
77-
self.name = player.name
78-
self.logger = get_logger(self.name)
79-
break
80-
81-
if self.spawn_id == 0:
82-
match player.variety.item:
83-
case flat.RLBot():
84-
self.team = player.team
85-
self.name = player.name
86-
self.logger = get_logger(self.name)
87-
break
88-
89-
if not self._initialized_bot and self._has_field_info:
90-
self._initialize_agent()
82+
if (
83+
not self._initialized_bot
84+
and self._has_field_info
85+
and self._has_player_mapping
86+
):
87+
self._initialize()
9188

9289
def _handle_field_info(self, field_info: flat.FieldInfo):
9390
self.field_info = field_info
9491
self._has_field_info = True
9592

96-
if not self._initialized_bot and self._has_match_settings:
97-
self._initialize_agent()
93+
if (
94+
not self._initialized_bot
95+
and self._has_match_settings
96+
and self._has_player_mapping
97+
):
98+
self._initialize()
99+
100+
def _handle_player_mappings(self, player_mappings: flat.ControllableTeamInfo):
101+
self.team = player_mappings.team
102+
controllable = player_mappings.controllables[0]
103+
self.spawn_id = controllable.spawn_id
104+
self.index = controllable.index
105+
self._has_player_mapping = True
106+
107+
if (
108+
not self._initialized_bot
109+
and self._has_match_settings
110+
and self._has_field_info
111+
):
112+
self._initialize()
98113

99114
def _handle_ball_prediction(self, ball_prediction: flat.BallPrediction):
100115
self._latest_prediction = ball_prediction
101116

102-
def _handle_packet(self, packet: flat.GameTickPacket):
117+
def _handle_packet(self, packet: flat.GamePacket):
103118
self._latest_packet = packet
104119

105-
def _packet_processor(self, packet: flat.GameTickPacket):
106-
if (
107-
self.index == -1
108-
or len(packet.players) <= self.index
109-
or packet.players[self.index].spawn_id != self.spawn_id
110-
):
111-
# spawn id should only be 0 if RLBOT_SPAWN_IDS was not set
112-
if self.spawn_id == 0:
113-
# in this case, if there's only one player, we can assume it's us
114-
player_index = -1
115-
for i, player in enumerate(packet.players):
116-
# skip human players/psyonix bots
117-
if not player.is_bot:
118-
continue
119-
120-
if player_index != -1:
121-
self.logger.error(
122-
"Multiple bots in the game, please set RLBOT_SPAWN_IDS"
123-
)
124-
return
125-
126-
player_index = i
127-
self.index = player_index
128-
129-
for i, player in enumerate(packet.players):
130-
if player.spawn_id == self.spawn_id:
131-
self.index = i
132-
break
133-
134-
if self.index == -1:
135-
return
120+
def _packet_processor(self, packet: flat.GamePacket):
121+
if len(packet.players) <= self.index:
122+
return
136123

137124
self.ball_prediction = self._latest_prediction
138125

@@ -162,9 +149,9 @@ def run(
162149

163150
# custom message handling logic
164151
# this reads all data in the socket until there's no more immediately available
165-
# checks if there was a GameTickPacket in the data, and if so, processes it
152+
# checks if there was a GamePacket in the data, and if so, processes it
166153
# then sets the socket to non-blocking and waits for more data
167-
# if there was no GameTickPacket, it sets to blocking and waits for more data
154+
# if there was no GamePacket, it sets to blocking and waits for more data
168155
while True:
169156
try:
170157
self._game_interface.handle_incoming_messages(True)
@@ -271,23 +258,21 @@ def set_loadout(self, loadout: flat.PlayerLoadout, spawn_id: int):
271258
"""
272259
Sets the loadout of a bot.
273260
274-
For use as a loadout generator, call inside of `initialize_agent`.
275-
Will be ignored if called outside of `initialize_agent` when state setting is disabled.
261+
For use as a loadout generator, call inside of `initialize`.
262+
Will be ignored if called outside of `initialize` when state setting is disabled.
276263
"""
277264
self._game_interface.send_set_loadout(flat.SetLoadout(spawn_id, loadout))
278265

279-
def initialize_agent(self):
266+
def initialize(self):
280267
"""
281268
Called for all heaver initialization that needs to happen.
282-
Field info and match settings are fully loaded at this point, and won't return garbage data.
283-
284-
NOTE: `self.index` is not set at this point, and should not be used. `self.team` and `self.name` _are_ set with correct information.
269+
Field info, match settings, name, index, and team are fully loaded at this point, and won't return garbage data.
285270
"""
286271

287272
def retire(self):
288273
"""Called after the game ends"""
289274

290-
def get_output(self, packet: flat.GameTickPacket) -> flat.ControllerState:
275+
def get_output(self, packet: flat.GamePacket) -> flat.ControllerState:
291276
"""
292277
Where all the logic of your bot gets its input and returns its output.
293278
"""

0 commit comments

Comments
 (0)