valence_command/
scopes.rs

1//! Scope graph for the Valence Command system.
2//!
3//! ## Breakdown
4//! Each scope is a node in a graph. A path from one node to another indicates
5//! that the first scope implies the second. A dot in the scope name indicates
6//! a sub-scope. You can use this to create a hierarchy of scopes. For example,
7//! the scope "valence.command" implies "valence.command.tp". this means that if
8//! a player has the "valence.command" scope, they can use the "tp" command.
9//!
10//! You may also link scopes together in the registry. This is useful for admin
11//! scope umbrellas. For example, if the scope "valence.admin" is linked to
12//! "valence.command", It means that if a player has the "valence.admin" scope,
13//! they can use all commands under the command scope.
14//!
15//! # Example
16//! ```
17//! use valence_command::scopes::CommandScopeRegistry;
18//!
19//! let mut registry = CommandScopeRegistry::new();
20//!
21//! // add a scope to the registry
22//! registry.add_scope("valence.command.teleport");
23//!
24//! // we added 4 scopes to the registry. "valence", "valence.command", "valence.command.teleport",
25//! // and the root scope.
26//! assert_eq!(registry.scope_count(), 4);
27//!
28//! registry.add_scope("valence.admin");
29//!
30//! // add a scope to the registry with a link to another scope
31//! registry.link("valence.admin", "valence.command.teleport");
32//!
33//! // the "valence.admin" scope implies the "valence.command.teleport" scope
34//! assert_eq!(
35//!     registry.grants("valence.admin", "valence.command.teleport"),
36//!     true
37//! );
38//! ```
39
40use std::collections::{BTreeSet, HashMap};
41use std::fmt::{Debug, Formatter};
42
43use bevy_app::{App, Plugin, Update};
44use bevy_derive::{Deref, DerefMut};
45use bevy_ecs::prelude::{Component, ResMut};
46use bevy_ecs::query::Changed;
47use bevy_ecs::system::{Query, Resource};
48use petgraph::dot;
49use petgraph::dot::Dot;
50use petgraph::prelude::*;
51
52pub struct CommandScopePlugin;
53
54impl Plugin for CommandScopePlugin {
55    fn build(&self, app: &mut App) {
56        app.init_resource::<CommandScopeRegistry>()
57            .add_systems(Update, add_new_scopes);
58    }
59}
60
61/// Command scope Component for players. This is a list of scopes that a player
62/// has. If a player has a scope, they can use any command that requires
63/// that scope.
64#[derive(
65    Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Component, Default, Deref, DerefMut,
66)]
67pub struct CommandScopes(pub BTreeSet<String>);
68
69/// This system makes it a bit easier to add new scopes to the registry without
70/// having to explicitly add them to the registry on app startup.
71fn add_new_scopes(
72    mut registry: ResMut<CommandScopeRegistry>,
73    scopes: Query<&CommandScopes, Changed<CommandScopes>>,
74) {
75    for scopes in scopes.iter() {
76        for scope in scopes.iter() {
77            if !registry.string_to_node.contains_key(scope) {
78                registry.add_scope(scope);
79            }
80        }
81    }
82}
83
84impl CommandScopes {
85    /// create a new scope component
86    pub fn new() -> Self {
87        Self::default()
88    }
89
90    /// add a scope to this component
91    pub fn add(&mut self, scope: &str) {
92        self.0.insert(scope.into());
93    }
94}
95
96/// Store the scope graph and provide methods for querying it.
97#[derive(Clone, Resource)]
98pub struct CommandScopeRegistry {
99    graph: Graph<String, ()>,
100    string_to_node: HashMap<String, NodeIndex>,
101    root: NodeIndex,
102}
103
104impl Default for CommandScopeRegistry {
105    fn default() -> Self {
106        let mut graph = Graph::new();
107        let root = graph.add_node("root".to_owned());
108        Self {
109            graph,
110            string_to_node: HashMap::from([("root".to_owned(), root)]),
111            root,
112        }
113    }
114}
115
116impl Debug for CommandScopeRegistry {
117    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
118        write!(
119            f,
120            "{:?}",
121            Dot::with_config(&self.graph, &[dot::Config::EdgeNoLabel])
122        )?;
123        Ok(())
124    }
125}
126
127impl CommandScopeRegistry {
128    /// Create a new scope registry.
129    pub fn new() -> Self {
130        Self::default()
131    }
132
133    /// Add a scope to the registry.
134    ///
135    /// # Example
136    /// ```
137    /// use valence_command::CommandScopeRegistry;
138    ///
139    /// let mut registry = CommandScopeRegistry::new();
140    ///
141    /// // creates two nodes. "valence" and "command" with an edge from "valence" to "command"
142    /// registry.add_scope("valence.command");
143    /// // creates one node. "valence.command.tp" with an edge from "valence.command" to
144    /// // "valence.command.tp"
145    /// registry.add_scope("valence.command.tp");
146    ///
147    /// // the root node is always present
148    /// assert_eq!(registry.scope_count(), 4);
149    /// ```
150    pub fn add_scope<S: Into<String>>(&mut self, scope: S) {
151        let scope = scope.into();
152        if self.string_to_node.contains_key(&scope) {
153            return;
154        }
155        let mut current_node = self.root;
156        let mut prefix = String::new();
157        for part in scope.split('.') {
158            let node = self
159                .string_to_node
160                .entry(prefix.clone() + part)
161                .or_insert_with(|| {
162                    let node = self.graph.add_node(part.to_owned());
163                    self.graph.add_edge(current_node, node, ());
164                    node
165                });
166            current_node = *node;
167
168            prefix = prefix + part + ".";
169        }
170    }
171
172    /// Remove a scope from the registry.
173    ///
174    /// # Example
175    /// ```
176    /// use valence_command::CommandScopeRegistry;
177    ///
178    /// let mut registry = CommandScopeRegistry::new();
179    ///
180    /// registry.add_scope("valence.command");
181    /// registry.add_scope("valence.command.tp");
182    ///
183    /// assert_eq!(registry.scope_count(), 4);
184    ///
185    /// registry.remove_scope("valence.command.tp");
186    ///
187    /// assert_eq!(registry.scope_count(), 3);
188    /// ```
189    pub fn remove_scope(&mut self, scope: &str) {
190        if let Some(node) = self.string_to_node.remove(scope) {
191            self.graph.remove_node(node);
192        };
193    }
194
195    /// Check if a scope grants another scope.
196    ///
197    /// # Example
198    /// ```
199    /// use valence_command::CommandScopeRegistry;
200    ///
201    /// let mut registry = CommandScopeRegistry::new();
202    ///
203    /// registry.add_scope("valence.command");
204    /// registry.add_scope("valence.command.tp");
205    ///
206    /// assert!(registry.grants("valence.command", "valence.command.tp")); // command implies tp
207    /// assert!(!registry.grants("valence.command.tp", "valence.command")); // tp does not imply command
208    /// ```
209    pub fn grants(&self, scope: &str, other: &str) -> bool {
210        if scope == other {
211            return true;
212        }
213
214        let scope_idx = match self.string_to_node.get(scope) {
215            None => {
216                return false;
217            }
218            Some(idx) => *idx,
219        };
220        let other_idx = match self.string_to_node.get(other) {
221            None => {
222                return false;
223            }
224            Some(idx) => *idx,
225        };
226
227        if scope_idx == self.root {
228            return true;
229        }
230
231        // if we can reach the other scope from the scope, then the scope
232        // grants the other scope
233        let mut dfs = Dfs::new(&self.graph, scope_idx);
234        while let Some(node) = dfs.next(&self.graph) {
235            if node == other_idx {
236                return true;
237            }
238        }
239        false
240    }
241
242    /// do any of the scopes in the list grant the other scope?
243    ///
244    /// # Example
245    /// ```
246    /// use valence_command::CommandScopeRegistry;
247    ///
248    /// let mut registry = CommandScopeRegistry::new();
249    ///
250    /// registry.add_scope("valence.command");
251    /// registry.add_scope("valence.command.tp");
252    /// registry.add_scope("valence.admin");
253    ///
254    /// assert!(registry.any_grants(
255    ///     &vec!["valence.admin", "valence.command"],
256    ///     "valence.command.tp"
257    /// ));
258    /// ```
259    pub fn any_grants(&self, scopes: &Vec<&str>, other: &str) -> bool {
260        for scope in scopes {
261            if self.grants(scope, other) {
262                return true;
263            }
264        }
265        false
266    }
267
268    /// Create a link between two scopes so that one implies the other. It will
269    /// add them if they don't exist.
270    ///
271    /// # Example
272    /// ```
273    /// use valence_command::CommandScopeRegistry;
274    ///
275    /// let mut registry = CommandScopeRegistry::new();
276    ///
277    /// registry.add_scope("valence.command.tp");
278    ///
279    /// registry.link("valence.admin", "valence.command");
280    ///
281    /// assert!(registry.grants("valence.admin", "valence.command"));
282    /// assert!(registry.grants("valence.admin", "valence.command.tp"));
283    /// ```
284    pub fn link(&mut self, scope: &str, other: &str) {
285        self.add_scope(scope);
286        self.add_scope(other);
287
288        let scope_idx = self.string_to_node[scope];
289        let other_idx = self.string_to_node[other];
290
291        self.graph.add_edge(scope_idx, other_idx, ());
292    }
293
294    /// Get the number of scopes in the registry.
295    pub fn scope_count(&self) -> usize {
296        self.graph.node_count()
297    }
298}