1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00

add support for custom suggestions in azalea-brigadier and cleanup a bit

This commit is contained in:
mat 2025-05-28 15:46:58 -08:00
parent 3d340f585a
commit da73b4316d
12 changed files with 155 additions and 117 deletions

View file

@ -17,25 +17,25 @@ impl ArgumentType for Double {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> { fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
let start = reader.cursor; let start = reader.cursor;
let result = reader.read_double()?; let result = reader.read_double()?;
if let Some(minimum) = self.minimum { if let Some(minimum) = self.minimum
if result < minimum { && result < minimum
reader.cursor = start; {
return Err(BuiltInExceptions::DoubleTooSmall { reader.cursor = start;
found: result, return Err(BuiltInExceptions::DoubleTooSmall {
min: minimum, found: result,
} min: minimum,
.create_with_context(reader));
} }
.create_with_context(reader));
} }
if let Some(maximum) = self.maximum { if let Some(maximum) = self.maximum
if result > maximum { && result > maximum
reader.cursor = start; {
return Err(BuiltInExceptions::DoubleTooBig { reader.cursor = start;
found: result, return Err(BuiltInExceptions::DoubleTooBig {
max: maximum, found: result,
} max: maximum,
.create_with_context(reader));
} }
.create_with_context(reader));
} }
Ok(Arc::new(result)) Ok(Arc::new(result))
} }

View file

@ -17,25 +17,25 @@ impl ArgumentType for Float {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> { fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
let start = reader.cursor; let start = reader.cursor;
let result = reader.read_float()?; let result = reader.read_float()?;
if let Some(minimum) = self.minimum { if let Some(minimum) = self.minimum
if result < minimum { && result < minimum
reader.cursor = start; {
return Err(BuiltInExceptions::FloatTooSmall { reader.cursor = start;
found: result, return Err(BuiltInExceptions::FloatTooSmall {
min: minimum, found: result,
} min: minimum,
.create_with_context(reader));
} }
.create_with_context(reader));
} }
if let Some(maximum) = self.maximum { if let Some(maximum) = self.maximum
if result > maximum { && result > maximum
reader.cursor = start; {
return Err(BuiltInExceptions::FloatTooBig { reader.cursor = start;
found: result, return Err(BuiltInExceptions::FloatTooBig {
max: maximum, found: result,
} max: maximum,
.create_with_context(reader));
} }
.create_with_context(reader));
} }
Ok(Arc::new(result)) Ok(Arc::new(result))
} }

View file

@ -17,25 +17,25 @@ impl ArgumentType for Integer {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> { fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
let start = reader.cursor; let start = reader.cursor;
let result = reader.read_int()?; let result = reader.read_int()?;
if let Some(minimum) = self.minimum { if let Some(minimum) = self.minimum
if result < minimum { && result < minimum
reader.cursor = start; {
return Err(BuiltInExceptions::IntegerTooSmall { reader.cursor = start;
found: result, return Err(BuiltInExceptions::IntegerTooSmall {
min: minimum, found: result,
} min: minimum,
.create_with_context(reader));
} }
.create_with_context(reader));
} }
if let Some(maximum) = self.maximum { if let Some(maximum) = self.maximum
if result > maximum { && result > maximum
reader.cursor = start; {
return Err(BuiltInExceptions::IntegerTooBig { reader.cursor = start;
found: result, return Err(BuiltInExceptions::IntegerTooBig {
max: maximum, found: result,
} max: maximum,
.create_with_context(reader));
} }
.create_with_context(reader));
} }
Ok(Arc::new(result)) Ok(Arc::new(result))
} }

View file

@ -17,25 +17,25 @@ impl ArgumentType for Long {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> { fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
let start = reader.cursor; let start = reader.cursor;
let result = reader.read_long()?; let result = reader.read_long()?;
if let Some(minimum) = self.minimum { if let Some(minimum) = self.minimum
if result < minimum { && result < minimum
reader.cursor = start; {
return Err(BuiltInExceptions::LongTooSmall { reader.cursor = start;
found: result, return Err(BuiltInExceptions::LongTooSmall {
min: minimum, found: result,
} min: minimum,
.create_with_context(reader));
} }
.create_with_context(reader));
} }
if let Some(maximum) = self.maximum { if let Some(maximum) = self.maximum
if result > maximum { && result > maximum
reader.cursor = start; {
return Err(BuiltInExceptions::LongTooBig { reader.cursor = start;
found: result, return Err(BuiltInExceptions::LongTooBig {
max: maximum, found: result,
} max: maximum,
.create_with_context(reader));
} }
.create_with_context(reader));
} }
Ok(Arc::new(result)) Ok(Arc::new(result))
} }

View file

@ -9,10 +9,20 @@ use crate::{
tree::{Command, CommandNode}, tree::{Command, CommandNode},
}; };
#[derive(Debug, Clone)] #[derive(Debug)]
pub enum ArgumentBuilderType { pub enum ArgumentBuilderType<S> {
Literal(Literal), Literal(Literal),
Argument(Argument), Argument(Argument<S>),
}
impl<S> Clone for ArgumentBuilderType<S> {
fn clone(&self) -> Self {
match self {
ArgumentBuilderType::Literal(literal) => ArgumentBuilderType::Literal(literal.clone()),
ArgumentBuilderType::Argument(argument) => {
ArgumentBuilderType::Argument(argument.clone())
}
}
}
} }
/// A node that hasn't yet been built. /// A node that hasn't yet been built.
@ -30,7 +40,7 @@ pub struct ArgumentBuilder<S> {
/// A node that isn't yet built. /// A node that isn't yet built.
impl<S> ArgumentBuilder<S> { impl<S> ArgumentBuilder<S> {
pub fn new(value: ArgumentBuilderType) -> Self { pub fn new(value: ArgumentBuilderType<S>) -> Self {
Self { Self {
arguments: CommandNode { arguments: CommandNode {
value, value,

View file

@ -12,7 +12,7 @@ impl Literal {
} }
} }
impl From<Literal> for ArgumentBuilderType { impl<S> From<Literal> for ArgumentBuilderType<S> {
fn from(literal: Literal) -> Self { fn from(literal: Literal) -> Self {
Self::Literal(literal) Self::Literal(literal)
} }

View file

@ -1,25 +1,35 @@
use std::{any::Any, fmt::Debug, sync::Arc}; use std::{
any::Any,
fmt::{self, Debug},
sync::Arc,
};
use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType}; use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType};
use crate::{ use crate::{
arguments::ArgumentType, arguments::ArgumentType,
context::CommandContext,
exceptions::CommandSyntaxException, exceptions::CommandSyntaxException,
string_reader::StringReader, string_reader::StringReader,
suggestion::{Suggestions, SuggestionsBuilder}, suggestion::{SuggestionProvider, Suggestions, SuggestionsBuilder},
}; };
/// An argument node type. The `T` type parameter is the type of the argument, /// An argument node type. The `T` type parameter is the type of the argument,
/// which can be anything. /// which can be anything.
#[derive(Clone)] pub struct Argument<S> {
pub struct Argument {
pub name: String, pub name: String,
parser: Arc<dyn ArgumentType + Send + Sync>, parser: Arc<dyn ArgumentType + Send + Sync>,
custom_suggestions: Option<Arc<dyn SuggestionProvider<S> + Send + Sync>>,
} }
impl Argument { impl<S> Argument<S> {
pub fn new(name: &str, parser: Arc<dyn ArgumentType + Send + Sync>) -> Self { pub fn new(
name: &str,
parser: Arc<dyn ArgumentType + Send + Sync>,
custom_suggestions: Option<Arc<dyn SuggestionProvider<S> + Send + Sync>>,
) -> Self {
Self { Self {
name: name.to_string(), name: name.to_string(),
parser, parser,
custom_suggestions,
} }
} }
@ -27,11 +37,16 @@ impl Argument {
self.parser.parse(reader) self.parser.parse(reader)
} }
pub fn list_suggestions(&self, builder: SuggestionsBuilder) -> Suggestions { pub fn list_suggestions(
// TODO: custom suggestions &self,
// https://github.com/Mojang/brigadier/blob/master/src/main/java/com/mojang/brigadier/tree/ArgumentCommandNode.java#L71 context: CommandContext<S>,
builder: SuggestionsBuilder,
self.parser.list_suggestions(builder) ) -> Suggestions {
if let Some(s) = &self.custom_suggestions {
s.get_suggestions(context, builder)
} else {
self.parser.list_suggestions(builder)
}
} }
pub fn examples(&self) -> Vec<String> { pub fn examples(&self) -> Vec<String> {
@ -39,14 +54,14 @@ impl Argument {
} }
} }
impl From<Argument> for ArgumentBuilderType { impl<S> From<Argument<S>> for ArgumentBuilderType<S> {
fn from(argument: Argument) -> Self { fn from(argument: Argument<S>) -> Self {
Self::Argument(argument) Self::Argument(argument)
} }
} }
impl Debug for Argument { impl<S> Debug for Argument<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Argument") f.debug_struct("Argument")
.field("name", &self.name) .field("name", &self.name)
// .field("parser", &self.parser) // .field("parser", &self.parser)
@ -59,5 +74,15 @@ pub fn argument<S>(
name: &str, name: &str,
parser: impl ArgumentType + Send + Sync + 'static, parser: impl ArgumentType + Send + Sync + 'static,
) -> ArgumentBuilder<S> { ) -> ArgumentBuilder<S> {
ArgumentBuilder::new(Argument::new(name, Arc::new(parser)).into()) ArgumentBuilder::new(Argument::new(name, Arc::new(parser), None).into())
}
impl<S> Clone for Argument<S> {
fn clone(&self) -> Self {
Self {
name: self.name.clone(),
parser: self.parser.clone(),
custom_suggestions: self.custom_suggestions.clone(),
}
}
} }

View file

@ -54,23 +54,22 @@ impl CommandSyntaxException {
} }
pub fn context(&self) -> Option<String> { pub fn context(&self) -> Option<String> {
if let Some(input) = &self.input { if let Some(input) = &self.input
if let Some(cursor) = self.cursor { && let Some(cursor) = self.cursor
let mut builder = String::new(); {
let cursor = cmp::min(input.len(), cursor); let mut builder = String::new();
let cursor = cmp::min(input.len(), cursor);
if cursor > CONTEXT_AMOUNT { if cursor > CONTEXT_AMOUNT {
builder.push_str("..."); builder.push_str("...");
}
builder.push_str(
&input
[(cmp::max(0, cursor as isize - CONTEXT_AMOUNT as isize) as usize)..cursor],
);
builder.push_str("<--[HERE]");
return Some(builder);
} }
builder.push_str(
&input[(cmp::max(0, cursor as isize - CONTEXT_AMOUNT as isize) as usize)..cursor],
);
builder.push_str("<--[HERE]");
return Some(builder);
} }
None None
} }

View file

@ -1,3 +1,4 @@
mod suggestion_provider;
mod suggestions; mod suggestions;
mod suggestions_builder; mod suggestions_builder;
@ -12,6 +13,7 @@ use std::{
use azalea_buf::AzaleaWrite; use azalea_buf::AzaleaWrite;
#[cfg(feature = "azalea-buf")] #[cfg(feature = "azalea-buf")]
use azalea_chat::FormattedText; use azalea_chat::FormattedText;
pub use suggestion_provider::SuggestionProvider;
pub use suggestions::Suggestions; pub use suggestions::Suggestions;
pub use suggestions_builder::SuggestionsBuilder; pub use suggestions_builder::SuggestionsBuilder;

View file

@ -0,0 +1,10 @@
use super::{Suggestions, SuggestionsBuilder};
use crate::context::CommandContext;
pub trait SuggestionProvider<S> {
fn get_suggestions(
&self,
context: CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions;
}

View file

@ -25,7 +25,7 @@ pub type Command<S> = Option<Arc<dyn Fn(&CommandContext<S>) -> i32 + Send + Sync
/// An ArgumentBuilder that has been built. /// An ArgumentBuilder that has been built.
#[non_exhaustive] #[non_exhaustive]
pub struct CommandNode<S> { pub struct CommandNode<S> {
pub value: ArgumentBuilderType, pub value: ArgumentBuilderType<S>,
// this is a BTreeMap because children need to be ordered when getting command suggestions // this is a BTreeMap because children need to be ordered when getting command suggestions
pub children: BTreeMap<String, Arc<RwLock<CommandNode<S>>>>, pub children: BTreeMap<String, Arc<RwLock<CommandNode<S>>>>,
@ -66,7 +66,7 @@ impl<S> CommandNode<S> {
} }
/// Gets the argument, or panics. You should use match if you're not certain /// Gets the argument, or panics. You should use match if you're not certain
/// about the type. /// about the type.
pub fn argument(&self) -> &Argument { pub fn argument(&self) -> &Argument<S> {
match self.value { match self.value {
ArgumentBuilderType::Argument(ref argument) => argument, ArgumentBuilderType::Argument(ref argument) => argument,
_ => panic!("CommandNode::argument() called on non-argument node"), _ => panic!("CommandNode::argument() called on non-argument node"),
@ -214,9 +214,7 @@ impl<S> CommandNode<S> {
pub fn list_suggestions( pub fn list_suggestions(
&self, &self,
// context is here because that's how it is in mojang's brigadier, but we haven't context: CommandContext<S>,
// implemented custom suggestions yet so this is unused rn
_context: CommandContext<S>,
builder: SuggestionsBuilder, builder: SuggestionsBuilder,
) -> Suggestions { ) -> Suggestions {
match &self.value { match &self.value {
@ -231,7 +229,7 @@ impl<S> CommandNode<S> {
Suggestions::default() Suggestions::default()
} }
} }
ArgumentBuilderType::Argument(argument) => argument.list_suggestions(builder), ArgumentBuilderType::Argument(argument) => argument.list_suggestions(context, builder),
} }
} }
} }
@ -239,7 +237,7 @@ impl<S> CommandNode<S> {
impl<S> Debug for CommandNode<S> { impl<S> Debug for CommandNode<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CommandNode") f.debug_struct("CommandNode")
.field("value", &self.value) // .field("value", &self.value)
.field("children", &self.children) .field("children", &self.children)
.field("command", &self.command.is_some()) .field("command", &self.command.is_some())
// .field("requirement", &self.requirement) // .field("requirement", &self.requirement)

View file

@ -1,11 +1,6 @@
use std::sync::Arc; use std::{ops::Deref, sync::Arc};
use azalea_brigadier::{ use azalea_brigadier::prelude::*;
arguments::integer_argument_type::integer,
builder::{literal_argument_builder::literal, required_argument_builder::argument},
command_dispatcher::CommandDispatcher,
context::CommandContext,
};
use bevy_app::App; use bevy_app::App;
use bevy_ecs::{prelude::*, system::RunSystemOnce}; use bevy_ecs::{prelude::*, system::RunSystemOnce};
use parking_lot::Mutex; use parking_lot::Mutex;
@ -145,8 +140,7 @@ impl DispatchStorage {
/// ///
/// Spawns a number of entities with the [`SpawnedEntity`] component. /// Spawns a number of entities with the [`SpawnedEntity`] component.
fn command_spawn_entity_num(context: &CommandContext<WorldAccessor>) -> i32 { fn command_spawn_entity_num(context: &CommandContext<WorldAccessor>) -> i32 {
let num = context.argument("entities").unwrap(); let num = get_integer(context, "entities").unwrap();
let num = *num.downcast_ref::<i32>().unwrap();
for _ in 0..num { for _ in 0..num {
context.source.lock().spawn(SpawnedEntity); context.source.lock().spawn(SpawnedEntity);
@ -187,7 +181,7 @@ impl WorldAccessor {
struct SpawnedEntity; struct SpawnedEntity;
/// Implemented for convenience. /// Implemented for convenience.
impl std::ops::Deref for WorldAccessor { impl Deref for WorldAccessor {
type Target = Arc<Mutex<World>>; type Target = Arc<Mutex<World>>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.world &self.world