chunkedge_command/
manager.rs

1use std::collections::{HashMap, HashSet};
2
3use bevy_app::{App, Plugin, PreUpdate};
4use bevy_ecs::entity::Entity;
5use bevy_ecs::prelude::{
6    Added, Changed, Commands, DetectChanges, Event, EventReader, EventWriter, IntoSystemConfigs,
7    Mut, Or, Query, Res,
8};
9use chunkedge_server::client::{Client, SpawnClientsSet};
10use chunkedge_server::event_loop::PacketEvent;
11use chunkedge_server::protocol::packets::play::commands_s2c::NodeData;
12use chunkedge_server::protocol::packets::play::{
13    ChatCommandC2s, ChatCommandSignedC2s, CommandsS2c,
14};
15use chunkedge_server::protocol::WritePacket;
16use chunkedge_server::EventLoopPreUpdate;
17use petgraph::graph::NodeIndex;
18use petgraph::prelude::EdgeRef;
19use petgraph::{Direction, Graph};
20use tracing::{debug, info, trace, warn};
21
22use crate::graph::{CommandEdgeType, CommandGraph, CommandNode};
23use crate::parsers::ParseInput;
24use crate::scopes::{CommandScopePlugin, CommandScopes};
25use crate::{CommandRegistry, CommandScopeRegistry, CommandSystemSet, ModifierValue};
26
27pub struct CommandPlugin;
28
29impl Plugin for CommandPlugin {
30    fn build(&self, app: &mut App) {
31        app.add_plugins(CommandScopePlugin)
32            .add_event::<CommandExecutionEvent>()
33            .add_event::<CommandProcessedEvent>()
34            .add_systems(PreUpdate, insert_scope_component.after(SpawnClientsSet))
35            .add_systems(
36                EventLoopPreUpdate,
37                (
38                    update_command_tree,
39                    command_tree_update_with_client,
40                    read_incoming_packets.before(CommandSystemSet),
41                    parse_incoming_commands.in_set(CommandSystemSet),
42                ),
43            );
44
45        let graph: CommandGraph = CommandGraph::new();
46        let modifiers = HashMap::new();
47        let parsers = HashMap::new();
48        let executables = HashSet::new();
49
50        app.insert_resource(CommandRegistry {
51            graph,
52            parsers,
53            modifiers,
54            executables,
55        });
56    }
57}
58
59/// This event is sent when a command is sent (you can send this with any
60/// entity)
61#[derive(Debug, Clone, PartialEq, Eq, Hash, Event)]
62pub struct CommandExecutionEvent {
63    /// the command that was executed eg. "teleport @p 0 ~ 0"
64    pub command: String,
65    /// usually the Client entity but it could be a command block or something
66    /// (whatever the library user wants)
67    pub executor: Entity,
68}
69
70/// This will only be sent if the command was successfully parsed and an
71/// executable was found
72#[derive(Debug, Clone, PartialEq, Eq, Event)]
73pub struct CommandProcessedEvent {
74    /// the command that was executed eg. "teleport @p 0 ~ 0"
75    pub command: String,
76    /// usually the Client entity but it could be a command block or something
77    /// (whatever the library user wants)
78    pub executor: Entity,
79    /// the modifiers that were applied to the command
80    pub modifiers: HashMap<ModifierValue, ModifierValue>,
81    /// the node that was executed
82    pub node: NodeIndex,
83}
84
85fn insert_scope_component(mut clients: Query<Entity, Added<Client>>, mut commands: Commands) {
86    for client in &mut clients {
87        commands.entity(client).insert(CommandScopes::new());
88    }
89}
90
91fn read_incoming_packets(
92    mut packets: EventReader<PacketEvent>,
93    mut event_writer: EventWriter<CommandExecutionEvent>,
94) {
95    for packet in packets.read() {
96        let client = packet.client;
97
98        if let Some(unsigned) = packet.decode::<ChatCommandC2s>() {
99            event_writer.send(CommandExecutionEvent {
100                command: unsigned.command.to_string(),
101                executor: client,
102            });
103        } else if let Some(signed) = packet.decode::<ChatCommandSignedC2s>() {
104            // TODO: verify command signatures.
105            // As per this gist: https://gist.github.com/kennytv/ed783dd244ca0321bbd882c347892874
106            // It looks like the client only sends the signed version if a command requires
107            // it (in vanilla thats /say for example).
108            event_writer.send(CommandExecutionEvent {
109                command: signed.command.to_string(),
110                executor: client,
111            });
112        }
113    }
114}
115
116#[allow(clippy::type_complexity)]
117fn command_tree_update_with_client(
118    command_registry: Res<CommandRegistry>,
119    scope_registry: Res<CommandScopeRegistry>,
120    mut updated_clients: Query<
121        (&mut Client, &CommandScopes),
122        Or<(Added<Client>, Changed<CommandScopes>)>,
123    >,
124) {
125    update_client_command_tree(
126        &command_registry,
127        scope_registry,
128        &mut updated_clients.iter_mut().collect(),
129    );
130}
131
132fn update_command_tree(
133    command_registry: Res<CommandRegistry>,
134    scope_registry: Res<CommandScopeRegistry>,
135    mut clients: Query<(&mut Client, &CommandScopes)>,
136) {
137    if command_registry.is_changed() {
138        update_client_command_tree(
139            &command_registry,
140            scope_registry,
141            &mut clients.iter_mut().collect(),
142        );
143    }
144}
145
146fn update_client_command_tree(
147    command_registry: &Res<CommandRegistry>,
148    scope_registry: Res<CommandScopeRegistry>,
149    updated_clients: &mut Vec<(Mut<Client>, &CommandScopes)>,
150) {
151    for (ref mut client, client_scopes) in updated_clients {
152        let time = std::time::Instant::now();
153
154        let old_graph = &command_registry.graph;
155        let mut new_graph = Graph::new();
156
157        // collect a new graph into only nodes that are allowed to be executed
158        let root = old_graph.root;
159
160        let mut to_visit = vec![(None, root)];
161        let mut already_visited = HashSet::new(); // prevent recursion
162        let mut old_to_new = HashMap::new();
163        let mut new_root = None;
164
165        while let Some((parent, node)) = to_visit.pop() {
166            if already_visited.contains(&(parent.map(|(node_id, _)| node_id), node)) {
167                continue;
168            }
169            already_visited.insert((parent.map(|(node_id, _)| node_id), node));
170            let node_scopes = &old_graph.graph[node].scopes;
171            if !node_scopes.is_empty() {
172                let mut has_scope = false;
173                for scope in node_scopes {
174                    if scope_registry.any_grants(
175                        &client_scopes.0.iter().map(|scope| scope.as_str()).collect(),
176                        scope,
177                    ) {
178                        has_scope = true;
179                        break;
180                    }
181                }
182                if !has_scope {
183                    continue;
184                }
185            }
186
187            let new_node = *old_to_new
188                .entry(node)
189                .or_insert_with(|| new_graph.add_node(old_graph.graph[node].clone()));
190
191            for neighbor in old_graph.graph.edges_directed(node, Direction::Outgoing) {
192                to_visit.push((Some((new_node, neighbor.weight())), neighbor.target()));
193            }
194
195            if let Some(parent) = parent {
196                new_graph.add_edge(parent.0, new_node, *parent.1);
197            } else {
198                new_root = Some(new_node);
199            }
200        }
201
202        match new_root {
203            Some(new_root) => {
204                let command_graph = CommandGraph {
205                    graph: new_graph,
206                    root: new_root,
207                };
208                let packet: CommandsS2c = command_graph.into();
209
210                client.write_packet(&packet);
211            }
212            None => {
213                warn!(
214                    "Client has no permissions to execute any commands so we sent them nothing. \
215                     It is generally a bad idea to scope the root node of the command graph as it \
216                     can cause undefined behavior. For example, if the player has permission to \
217                     execute a command before you change the scope of the root node, the packet \
218                     will not be sent to the client and so the client will still think they can \
219                     execute the command."
220                )
221            }
222        }
223
224        debug!("command tree update took {:?}", time.elapsed());
225    }
226}
227
228fn parse_incoming_commands(
229    mut event_reader: EventReader<CommandExecutionEvent>,
230    mut event_writer: EventWriter<CommandProcessedEvent>,
231    command_registry: Res<CommandRegistry>,
232    scope_registry: Res<CommandScopeRegistry>,
233    entity_scopes: Query<&CommandScopes>,
234) {
235    for command_event in event_reader.read() {
236        let executor = command_event.executor;
237        // these are the leafs of the graph that are executable under this command
238        // group
239        let executable_leafs = command_registry
240            .executables
241            .iter()
242            .collect::<Vec<&NodeIndex>>();
243        let root = command_registry.graph.root;
244
245        let command_input = &*command_event.command;
246        let graph = &command_registry.graph.graph;
247        let input = ParseInput::new(command_input);
248
249        let mut to_be_executed = Vec::new();
250
251        let mut args = Vec::new();
252        let mut modifiers_to_be_executed = Vec::new();
253
254        parse_command_args(
255            &mut args,
256            &mut modifiers_to_be_executed,
257            input,
258            graph,
259            &executable_leafs,
260            command_registry.as_ref(),
261            &mut to_be_executed,
262            root,
263            executor,
264            &entity_scopes,
265            scope_registry.as_ref(),
266            false,
267        );
268
269        let mut modifiers = HashMap::new();
270        for (node, modifier) in modifiers_to_be_executed {
271            command_registry.modifiers[&node](modifier, &mut modifiers);
272        }
273
274        for node in to_be_executed {
275            trace!("executing node: {node:?}");
276            event_writer.send(CommandProcessedEvent {
277                command: args.join(" "),
278                executor,
279                modifiers: modifiers.clone(),
280                node,
281            });
282        }
283        info!(
284            "Command dispatched: /{} (debug logs for more data)",
285            command_event.command
286        );
287        debug!("Command modifiers: {:?}", modifiers);
288    }
289}
290
291#[allow(clippy::too_many_arguments)]
292/// recursively parse the command args.
293fn parse_command_args(
294    command_args: &mut Vec<String>,
295    modifiers_to_be_executed: &mut Vec<(NodeIndex, String)>,
296    mut input: ParseInput,
297    graph: &Graph<CommandNode, CommandEdgeType>,
298    executable_leafs: &[&NodeIndex],
299    command_registry: &CommandRegistry,
300    to_be_executed: &mut Vec<NodeIndex>,
301    current_node: NodeIndex,
302    executor: Entity,
303    scopes: &Query<&CommandScopes>,
304    scope_registry: &CommandScopeRegistry,
305    coming_from_redirect: bool,
306) -> bool {
307    let node_scopes = &graph[current_node].scopes;
308    let default_scopes = CommandScopes::new();
309    let client_scopes: Vec<&str> = scopes
310        .get(executor)
311        .unwrap_or(&default_scopes)
312        .0
313        .iter()
314        .map(|scope| scope.as_str())
315        .collect();
316    // if empty, we assume the node is global
317    if !node_scopes.is_empty() {
318        let mut has_scope = false;
319        for scope in node_scopes {
320            if scope_registry.any_grants(&client_scopes, scope) {
321                has_scope = true;
322                break;
323            }
324        }
325        if !has_scope {
326            return false;
327        }
328    }
329
330    if !coming_from_redirect {
331        // we want to skip whitespace before matching the node
332        input.skip_whitespace();
333        match &graph[current_node].data {
334            // no real need to check for root node
335            NodeData::Root => {
336                if command_registry.modifiers.contains_key(&current_node) {
337                    modifiers_to_be_executed.push((current_node, String::new()));
338                }
339            }
340            // if the node is a literal, we want to match the name of the literal
341            // to the input
342            NodeData::Literal { name } => {
343                if input.match_next(name) {
344                    if !input.match_next(" ") && !input.is_done() {
345                        return false;
346                    } // we want to pop the whitespace after the literal
347                    if command_registry.modifiers.contains_key(&current_node) {
348                        modifiers_to_be_executed.push((current_node, String::new()));
349                    }
350                } else {
351                    return false;
352                }
353            }
354            // if the node is an argument, we want to parse the argument
355            NodeData::Argument { .. } => {
356                let Some(parser) = command_registry.parsers.get(&current_node) else {
357                    return false;
358                };
359
360                // we want to save the input before and after parsing
361                // this is so we can save the argument to the command args
362                let pre_input = input.clone().into_inner();
363                let valid = parser(&mut input);
364                if valid {
365                    // If input.len() > pre_input.len() the parser replaced the input
366                    let Some(arg) = pre_input
367                        .get(..pre_input.len().wrapping_sub(input.len()))
368                        .map(|s| s.to_owned())
369                    else {
370                        panic!(
371                            "Parser replaced input with another string. This is not allowed. \
372                             Attempting to parse: {}",
373                            input.into_inner()
374                        );
375                    };
376
377                    if command_registry.modifiers.contains_key(&current_node) {
378                        modifiers_to_be_executed.push((current_node, arg.clone()));
379                    }
380                    command_args.push(arg);
381                } else {
382                    return false;
383                }
384            }
385        }
386    } else {
387        command_args.clear();
388    }
389
390    input.skip_whitespace();
391    if input.is_done() && executable_leafs.contains(&&current_node) {
392        to_be_executed.push(current_node);
393        return true;
394    }
395
396    let mut all_invalid = true;
397    for neighbor in graph.neighbors(current_node) {
398        let pre_input = input.clone();
399        let mut args = command_args.clone();
400        let mut modifiers = modifiers_to_be_executed.clone();
401        let valid = parse_command_args(
402            &mut args,
403            &mut modifiers,
404            input.clone(),
405            graph,
406            executable_leafs,
407            command_registry,
408            to_be_executed,
409            neighbor,
410            executor,
411            scopes,
412            scope_registry,
413            {
414                let edge = graph.find_edge(current_node, neighbor).unwrap();
415                matches!(&graph[edge], CommandEdgeType::Redirect)
416            },
417        );
418        if valid {
419            *command_args = args;
420            *modifiers_to_be_executed = modifiers;
421            all_invalid = false;
422        } else {
423            input = pre_input;
424        }
425    }
426    if all_invalid {
427        return false;
428    }
429    true
430}