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}