Skip to main content

rlbot/agents/
script.rs

1use rlbot_flat::flat::{
2    BallPrediction, ConnectionSettings, CoreMessage, FieldInfo, GamePacket, InitComplete,
3    MatchComm, MatchConfiguration, PingResponse, RenderingStatus,
4};
5
6use crate::{RLBotConnection, StartingInfo, util::PacketQueue};
7
8use super::AgentError;
9
10#[allow(unused_variables)]
11/// The behavior of a script: an interface with no controllables.
12///
13/// Scripts can't drive cars. They observe the match and manage it: state
14/// setting (see [`DesiredGameState`](crate::flat::DesiredGameState)), match
15/// communication, and debug rendering. [`run_script_agent`] creates one
16/// instance on one thread; your own `agent_id` is handed to [`new`] since
17/// there is no controllable to identify you by.
18///
19/// [`new`]: ScriptAgent::new
20pub trait ScriptAgent {
21    /// Create the script instance.
22    ///
23    /// Unlike the other agent kinds there is no controllable, so your
24    /// `agent_id` is passed in directly. The `packet_queue` lets you send
25    /// packets before the first tick.
26    fn new(
27        agent_id: String,
28        match_configuration: MatchConfiguration,
29        field_info: FieldInfo,
30        packet_queue: &mut PacketQueue,
31    ) -> Self;
32    /// React to the latest game state. Called for every [`GamePacket`].
33    ///
34    /// Queue management packets (state setting, comms, rendering) here.
35    ///
36    /// [`GamePacket`]: crate::flat::GamePacket
37    fn tick(&mut self, game_packet: GamePacket, packet_queue: &mut PacketQueue);
38    /// React to a match communication (quick chat / club message).
39    ///
40    /// Only fires if you passed `wants_comms: true` to [`run_script_agent`].
41    fn on_match_comm(&mut self, match_comm: MatchComm, packet_queue: &mut PacketQueue) {}
42    /// React to a ball prediction update.
43    ///
44    /// Only fires if you passed `wants_ball_predictions: true` to
45    /// [`run_script_agent`].
46    fn on_ball_prediction(
47        &mut self,
48        ball_prediction: BallPrediction,
49        packet_queue: &mut PacketQueue,
50    ) {
51    }
52    /// React to core acknowledging (or dropping) your rendered groups.
53    fn on_rendering_status(
54        &mut self,
55        rendering_status: RenderingStatus,
56        packet_queue: &mut PacketQueue,
57    ) {
58    }
59    /// React to a ping round-trip response. Useful for measuring latency.
60    fn on_ping_response(&mut self, ping: PingResponse, packet_queue: &mut PacketQueue) {}
61}
62
63/// Run a single [`ScriptAgent`] with no controllables on one thread.
64/// Ok(()) means a successful exit, i.e. core sent a disconnect.
65///
66/// # Errors
67///
68/// Returns an error if the agent panics or the connection fails.
69pub fn run_script_agent<T: ScriptAgent>(
70    agent_id: String,
71    wants_ball_predictions: bool,
72    wants_comms: bool,
73    mut connection: RLBotConnection,
74) -> Result<(), AgentError> {
75    connection.send_packet(ConnectionSettings {
76        agent_id: agent_id.clone(),
77        wants_ball_predictions,
78        wants_comms,
79        close_between_matches: true,
80    })?;
81
82    let StartingInfo {
83        controllable_team_info: _,
84        match_configuration,
85        field_info,
86    } = connection.get_starting_info()?;
87
88    let mut outgoing_queue = PacketQueue::default();
89    let mut agent = T::new(
90        agent_id,
91        match_configuration,
92        field_info,
93        &mut outgoing_queue,
94    );
95
96    outgoing_queue.push(InitComplete {});
97    connection.send_packets_enum(outgoing_queue.empty().into_iter())?;
98
99    while let Ok(packet) = connection.recv_packet() {
100        match packet {
101            CoreMessage::DisconnectSignal(_) => break,
102            CoreMessage::GamePacket(x) => {
103                agent.tick(*x, &mut outgoing_queue);
104            }
105            CoreMessage::MatchComm(x) => {
106                agent.on_match_comm(*x, &mut outgoing_queue);
107            }
108            CoreMessage::BallPrediction(x) => {
109                agent.on_ball_prediction(*x, &mut outgoing_queue);
110            }
111            CoreMessage::RenderingStatus(x) => {
112                agent.on_rendering_status(*x, &mut outgoing_queue);
113            }
114            CoreMessage::PingResponse(x) => {
115                agent.on_ping_response(*x, &mut outgoing_queue);
116            }
117            CoreMessage::PingRequest(x) => {
118                outgoing_queue.push(PingResponse { cookie: x.cookie });
119            }
120            CoreMessage::FieldInfo(_)
121            | CoreMessage::MatchConfiguration(_)
122            | CoreMessage::ControllableTeamInfo(_) => {
123                unreachable!("Unexpected packet; should not be able to receive this packet type.")
124            }
125        }
126
127        connection.send_packets_enum(outgoing_queue.empty().into_iter())?;
128    }
129
130    Ok(())
131}