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

merge main

This commit is contained in:
mat 2025-06-02 06:36:47 -10:00
commit 538bd78476
189 changed files with 4131 additions and 3114 deletions

View file

@ -19,7 +19,9 @@ is breaking anyways, semantic versioning is not followed.
- Add auto-reconnecting which is enabled by default.
- `client.start_use_item()`.
- The pathfinder no longer avoids slabs, stairs, and dirt path blocks.
- The pathfinder now immediately recalculates if blocks are placed in its path.
- The reach distance for the pathfinder `ReachBlockPosGoal` is now configurable. (@x-osc)
- azalea-brigadier now supports suggestions, command contexts, result consumers, and returning errors with `ArgumentBuilder::executes_result`.
### Changed

View file

@ -13,15 +13,15 @@ A collection of Rust crates for making Minecraft bots, clients, and tools.
_Currently supported Minecraft version: `1.21.6-pre2`._
> [!WARNING]
> Azalea is still unfinished, though most crates are in a useable state
> Azalea is still unfinished, though most crates are in a useable state.
## Features
- [Accurate physics](https://github.com/azalea-rs/azalea/blob/main/azalea-physics/src/lib.rs) (but some features like entity pushing and elytras aren't yet implemented)
- [Accurate physics](https://github.com/azalea-rs/azalea/blob/main/azalea-physics/src/lib.rs) (but some features like entity pushing and elytras aren't implemented yet)
- [Pathfinder](https://azalea.matdoes.dev/azalea/pathfinder/index.html)
- [Swarms](https://azalea.matdoes.dev/azalea/swarm/index.html)
- [Breaking blocks](https://azalea.matdoes.dev/azalea/struct.Client.html#method.mine)
- [Block interactions & building](https://azalea.matdoes.dev/azalea/struct.Client.html#method.block_interact) (this doesn't predict the block interactions/placement on the client yet but it's usually fine)
- [Block interactions & building](https://azalea.matdoes.dev/azalea/struct.Client.html#method.block_interact) (this doesn't predict the block interactions/placement on the client yet, but it's usually fine)
- [Inventories](https://azalea.matdoes.dev/azalea/struct.Client.html#impl-ContainerClientExt-for-Client)
- [Attacking entities](https://azalea.matdoes.dev/azalea/struct.Client.html#method.attack) (but you can't get the entity at the crosshair yet)
- [Plugins](#plugins)
@ -53,7 +53,7 @@ If you'd like to chat about Azalea, you can join the Matrix space at [#azalea:ma
Here's an incomplete list of bots built using Azalea, primarily intended as a reference in addition to the existing documentation and examples:
- [ShayBox/ShaysBot](https://github.com/ShayBox/ShaysBot) - Pearl statis bot featuring a Discord bot, an HTTP API, and more.
- [ShayBox/ShaysBot](https://github.com/ShayBox/ShaysBot) - Pearl stasis bot featuring a Discord bot, an HTTP API, and more.
- [EnderKill98/statis-bot](https://github.com/EnderKill98/stasis-bot) - This bot can automatically detect thrown pearls and later walk there and pull them for you.
- [as1100k/aether](https://github.com/as1100k/aether) - Collection of Minecraft bots and plugins.
- [mat-1/potato-bot-2](https://github.com/mat-1/potato-bot-2) - Hardened Discord chat bridge created for the LiveOverflow SMP.
@ -63,7 +63,7 @@ You can see more projects built with Azalea in the [GitHub dependency graph](htt
## Plugins
Azalea has support for Bevy plugins, which can significantly alter its functionality. Here's some plugins you may find useful:
Azalea has support for Bevy plugins, which can significantly alter its functionality. Here are some plugins that you may find useful:
- [azalea-rs/azalea-viaversion](https://github.com/azalea-rs/azalea-viaversion) - Multi-version compatibility for your Azalea bots using ViaProxy.
- [azalea-rs/azalea-hax](https://github.com/azalea-rs/azalea-hax) - Anti-knockback.

View file

@ -2,6 +2,8 @@
A port of Mojang's Authlib and launcher authentication.
The default location of Azalea's cache is at `~/.minecraft/azalea-auth.json`. You can delete or modify this file if you'd like to associate an email with a different account.
# Examples
```no_run
@ -24,4 +26,4 @@ async fn main() {
}
```
Thanks to [wiki contributors](https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Microsoft_Authentication_Scheme), [Overhash](https://gist.github.com/OverHash/a71b32846612ba09d8f79c9d775bfadf), and [prismarine-auth contributors](https://github.com/PrismarineJS/prismarine-auth).
Thanks to [wiki contributors](https://minecraft.wiki/w/Microsoft_authentication), [Overhash](https://gist.github.com/OverHash/a71b32846612ba09d8f79c9d775bfadf), and [prismarine-auth contributors](https://github.com/PrismarineJS/prismarine-auth).

View file

@ -3,13 +3,14 @@
use std::{
collections::HashMap,
path::PathBuf,
time::{Instant, SystemTime, UNIX_EPOCH},
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::json;
use thiserror::Error;
use tokio::time::sleep;
use tracing::{error, trace};
use uuid::Uuid;
@ -75,8 +76,9 @@ pub async fn auth(email: &str, opts: AuthOpts<'_>) -> Result<AuthResult, AuthErr
None
};
if cached_account.is_some() && !cached_account.as_ref().unwrap().mca.is_expired() {
let account = cached_account.as_ref().unwrap();
if let Some(account) = &cached_account
&& !account.mca.is_expired()
{
// the minecraft auth data is cached and not expired, so we can just
// use that instead of doing auth all over again :)
@ -129,8 +131,8 @@ pub async fn auth(email: &str, opts: AuthOpts<'_>) -> Result<AuthResult, AuthErr
let profile: ProfileResponse = get_profile(&client, &res.minecraft_access_token).await?;
if let Some(cache_file) = opts.cache_file {
if let Err(e) = cache::set_account_in_cache(
if let Some(cache_file) = opts.cache_file
&& let Err(e) = cache::set_account_in_cache(
&cache_file,
email,
CachedAccount {
@ -142,9 +144,8 @@ pub async fn auth(email: &str, opts: AuthOpts<'_>) -> Result<AuthResult, AuthErr
},
)
.await
{
error!("{}", e);
}
{
error!("{}", e);
}
Ok(AuthResult {
@ -328,7 +329,7 @@ pub async fn get_ms_link_code(
Ok(client
.post("https://login.live.com/oauth20_connect.srf")
.form(&vec![
.form(&[
("scope", scope),
("client_id", client_id),
("response_type", "device_code"),
@ -354,17 +355,17 @@ pub async fn get_ms_auth_token(
CLIENT_ID
};
let login_expires_at = Instant::now() + std::time::Duration::from_secs(res.expires_in);
let login_expires_at = Instant::now() + Duration::from_secs(res.expires_in);
while Instant::now() < login_expires_at {
tokio::time::sleep(std::time::Duration::from_secs(res.interval)).await;
sleep(Duration::from_secs(res.interval)).await;
trace!("Polling to check if user has logged in...");
let res = client
.post(format!(
"https://login.live.com/oauth20_token.srf?client_id={client_id}"
))
.form(&vec![
.form(&[
("client_id", client_id),
("device_code", &res.device_code),
("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
@ -375,8 +376,8 @@ pub async fn get_ms_auth_token(
.await;
if let Ok(access_token_response) = res {
trace!("access_token_response: {:?}", access_token_response);
let expires_at = SystemTime::now()
+ std::time::Duration::from_secs(access_token_response.expires_in);
let expires_at =
SystemTime::now() + Duration::from_secs(access_token_response.expires_in);
return Ok(ExpiringValue {
data: access_token_response,
expires_at: expires_at
@ -428,7 +429,7 @@ pub async fn refresh_ms_auth_token(
let access_token_response_text = client
.post("https://login.live.com/oauth20_token.srf")
.form(&vec![
.form(&[
("scope", scope),
("client_id", client_id),
("grant_type", "refresh_token"),
@ -441,8 +442,7 @@ pub async fn refresh_ms_auth_token(
let access_token_response: AccessTokenResponse =
serde_json::from_str(&access_token_response_text)?;
let expires_at =
SystemTime::now() + std::time::Duration::from_secs(access_token_response.expires_in);
let expires_at = SystemTime::now() + Duration::from_secs(access_token_response.expires_in);
Ok(ExpiringValue {
data: access_token_response,
expires_at: expires_at
@ -558,7 +558,7 @@ async fn auth_with_minecraft(
.await?;
trace!("{:?}", res);
let expires_at = SystemTime::now() + std::time::Duration::from_secs(res.expires_in);
let expires_at = SystemTime::now() + Duration::from_secs(res.expires_in);
Ok(ExpiringValue {
data: res,
// to seconds since epoch

View file

@ -1,22 +1,27 @@
//! Cache auth information
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use std::{
io,
path::Path,
time::{SystemTime, UNIX_EPOCH},
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tokio::fs::File;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::{
fs::{self, File},
io::{AsyncReadExt, AsyncWriteExt},
};
use tracing::{debug, trace};
#[derive(Debug, Error)]
pub enum CacheError {
#[error("Failed to read cache file: {0}")]
Read(std::io::Error),
Read(io::Error),
#[error("Failed to write cache file: {0}")]
Write(std::io::Error),
Write(io::Error),
#[error("Failed to create cache file directory: {0}")]
MkDir(std::io::Error),
MkDir(io::Error),
#[error("Failed to parse cache file: {0}")]
Parse(serde_json::Error),
}
@ -94,7 +99,9 @@ async fn set_entire_cache(cache_file: &Path, cache: Vec<CachedAccount>) -> Resul
"Making cache file parent directory at {}",
cache_file_parent.to_string_lossy()
);
std::fs::create_dir_all(cache_file_parent).map_err(CacheError::MkDir)?;
fs::create_dir_all(cache_file_parent)
.await
.map_err(CacheError::MkDir)?;
}
let mut cache_file = File::create(cache_file).await.map_err(CacheError::Write)?;
let cache = serde_json::to_string_pretty(&cache).map_err(CacheError::Parse)?;

View file

@ -2,8 +2,7 @@
mod utils;
use std::collections::HashMap;
use std::fmt::Write;
use std::{collections::HashMap, fmt::Write};
use proc_macro::TokenStream;
use proc_macro2::TokenTree;
@ -717,10 +716,10 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
// ```
// match state_id {
// // this is just an example of how it might look, these state ids are definitely not correct
// 0|3|6 => Some(Self::Axis::X),
// 1|4|7 => Some(Self::Axis::Y),
// 2|5|8 => Some(Self::Axis::Z),
// _ => None
// 0 | 3 | 6 => Some(Self::Axis::X),
// 1 | 4 | 7 => Some(Self::Axis::Y),
// 2 | 5 | 8 => Some(Self::Axis::Z),
// _ => None,
// }
// ```
let mut property_impls = quote! {};

View file

@ -94,6 +94,12 @@ impl TryFrom<u16> for BlockState {
}
}
}
impl From<BlockState> for u32 {
/// See [`BlockState::id`].
fn from(value: BlockState) -> Self {
value.id as u32
}
}
impl AzaleaRead for BlockState {
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
@ -104,7 +110,7 @@ impl AzaleaRead for BlockState {
}
}
impl AzaleaWrite for BlockState {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
u32::azalea_write_var(&(self.id as u32), buf)
}
}

View file

@ -1,13 +1,13 @@
use std::{any::Any, sync::Arc};
use crate::{
exceptions::CommandSyntaxException,
errors::CommandSyntaxError,
string_reader::StringReader,
suggestion::{Suggestions, SuggestionsBuilder},
};
pub trait ArgumentType {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException>;
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError>;
fn list_suggestions(&self, _builder: SuggestionsBuilder) -> Suggestions {
Suggestions::default()

View file

@ -3,7 +3,7 @@ use std::{any::Any, sync::Arc};
use super::ArgumentType;
use crate::{
context::CommandContext,
exceptions::CommandSyntaxException,
errors::CommandSyntaxError,
string_reader::StringReader,
suggestion::{Suggestions, SuggestionsBuilder},
};
@ -12,7 +12,7 @@ use crate::{
struct Boolean;
impl ArgumentType for Boolean {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError> {
Ok(Arc::new(reader.read_boolean()?))
}

View file

@ -3,7 +3,7 @@ use std::{any::Any, sync::Arc};
use super::ArgumentType;
use crate::{
context::CommandContext,
exceptions::{BuiltInExceptions, CommandSyntaxException},
errors::{BuiltInError, CommandSyntaxError},
string_reader::StringReader,
};
@ -14,28 +14,28 @@ struct Double {
}
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>, CommandSyntaxError> {
let start = reader.cursor;
let result = reader.read_double()?;
if let Some(minimum) = self.minimum {
if result < minimum {
reader.cursor = start;
return Err(BuiltInExceptions::DoubleTooSmall {
found: result,
min: minimum,
}
.create_with_context(reader));
if let Some(minimum) = self.minimum
&& result < minimum
{
reader.cursor = start;
return Err(BuiltInError::DoubleTooSmall {
found: result,
min: minimum,
}
.create_with_context(reader));
}
if let Some(maximum) = self.maximum {
if result > maximum {
reader.cursor = start;
return Err(BuiltInExceptions::DoubleTooBig {
found: result,
max: maximum,
}
.create_with_context(reader));
if let Some(maximum) = self.maximum
&& result > maximum
{
reader.cursor = start;
return Err(BuiltInError::DoubleTooBig {
found: result,
max: maximum,
}
.create_with_context(reader));
}
Ok(Arc::new(result))
}

View file

@ -3,7 +3,7 @@ use std::{any::Any, sync::Arc};
use super::ArgumentType;
use crate::{
context::CommandContext,
exceptions::{BuiltInExceptions, CommandSyntaxException},
errors::{BuiltInError, CommandSyntaxError},
string_reader::StringReader,
};
@ -14,28 +14,28 @@ struct Float {
}
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>, CommandSyntaxError> {
let start = reader.cursor;
let result = reader.read_float()?;
if let Some(minimum) = self.minimum {
if result < minimum {
reader.cursor = start;
return Err(BuiltInExceptions::FloatTooSmall {
found: result,
min: minimum,
}
.create_with_context(reader));
if let Some(minimum) = self.minimum
&& result < minimum
{
reader.cursor = start;
return Err(BuiltInError::FloatTooSmall {
found: result,
min: minimum,
}
.create_with_context(reader));
}
if let Some(maximum) = self.maximum {
if result > maximum {
reader.cursor = start;
return Err(BuiltInExceptions::FloatTooBig {
found: result,
max: maximum,
}
.create_with_context(reader));
if let Some(maximum) = self.maximum
&& result > maximum
{
reader.cursor = start;
return Err(BuiltInError::FloatTooBig {
found: result,
max: maximum,
}
.create_with_context(reader));
}
Ok(Arc::new(result))
}

View file

@ -3,7 +3,7 @@ use std::{any::Any, sync::Arc};
use super::ArgumentType;
use crate::{
context::CommandContext,
exceptions::{BuiltInExceptions, CommandSyntaxException},
errors::{BuiltInError, CommandSyntaxError},
string_reader::StringReader,
};
@ -14,28 +14,28 @@ struct Integer {
}
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>, CommandSyntaxError> {
let start = reader.cursor;
let result = reader.read_int()?;
if let Some(minimum) = self.minimum {
if result < minimum {
reader.cursor = start;
return Err(BuiltInExceptions::IntegerTooSmall {
found: result,
min: minimum,
}
.create_with_context(reader));
if let Some(minimum) = self.minimum
&& result < minimum
{
reader.cursor = start;
return Err(BuiltInError::IntegerTooSmall {
found: result,
min: minimum,
}
.create_with_context(reader));
}
if let Some(maximum) = self.maximum {
if result > maximum {
reader.cursor = start;
return Err(BuiltInExceptions::IntegerTooBig {
found: result,
max: maximum,
}
.create_with_context(reader));
if let Some(maximum) = self.maximum
&& result > maximum
{
reader.cursor = start;
return Err(BuiltInError::IntegerTooBig {
found: result,
max: maximum,
}
.create_with_context(reader));
}
Ok(Arc::new(result))
}

View file

@ -3,7 +3,7 @@ use std::{any::Any, sync::Arc};
use super::ArgumentType;
use crate::{
context::CommandContext,
exceptions::{BuiltInExceptions, CommandSyntaxException},
errors::{BuiltInError, CommandSyntaxError},
string_reader::StringReader,
};
@ -14,28 +14,28 @@ struct Long {
}
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>, CommandSyntaxError> {
let start = reader.cursor;
let result = reader.read_long()?;
if let Some(minimum) = self.minimum {
if result < minimum {
reader.cursor = start;
return Err(BuiltInExceptions::LongTooSmall {
found: result,
min: minimum,
}
.create_with_context(reader));
if let Some(minimum) = self.minimum
&& result < minimum
{
reader.cursor = start;
return Err(BuiltInError::LongTooSmall {
found: result,
min: minimum,
}
.create_with_context(reader));
}
if let Some(maximum) = self.maximum {
if result > maximum {
reader.cursor = start;
return Err(BuiltInExceptions::LongTooBig {
found: result,
max: maximum,
}
.create_with_context(reader));
if let Some(maximum) = self.maximum
&& result > maximum
{
reader.cursor = start;
return Err(BuiltInError::LongTooBig {
found: result,
max: maximum,
}
.create_with_context(reader));
}
Ok(Arc::new(result))
}

View file

@ -1,9 +1,7 @@
use std::{any::Any, sync::Arc};
use super::ArgumentType;
use crate::{
context::CommandContext, exceptions::CommandSyntaxException, string_reader::StringReader,
};
use crate::{context::CommandContext, errors::CommandSyntaxError, string_reader::StringReader};
pub enum StringArgument {
/// Match up until the next space.
@ -16,7 +14,7 @@ pub enum StringArgument {
}
impl ArgumentType for StringArgument {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError> {
let result = match self {
StringArgument::SingleWord => reader.read_unquoted_string().to_string(),
StringArgument::QuotablePhrase => reader.read_string()?,

View file

@ -1,18 +1,32 @@
use std::{fmt::Debug, sync::Arc};
use std::{
fmt::{self, Debug},
sync::Arc,
};
use parking_lot::RwLock;
use super::{literal_argument_builder::Literal, required_argument_builder::Argument};
use crate::{
context::CommandContext,
errors::CommandSyntaxError,
modifier::RedirectModifier,
tree::{Command, CommandNode},
};
#[derive(Debug, Clone)]
pub enum ArgumentBuilderType {
#[derive(Debug)]
pub enum ArgumentBuilderType<S> {
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.
@ -30,7 +44,7 @@ pub struct ArgumentBuilder<S> {
/// A node that isn't yet built.
impl<S> ArgumentBuilder<S> {
pub fn new(value: ArgumentBuilderType) -> Self {
pub fn new(value: ArgumentBuilderType<S>) -> Self {
Self {
arguments: CommandNode {
value,
@ -49,9 +63,7 @@ impl<S> ArgumentBuilder<S> {
/// ```
/// # use azalea_brigadier::prelude::*;
/// # let mut subject = CommandDispatcher::<()>::new();
/// literal("foo").then(
/// literal("bar").executes(|ctx: &CommandContext<()>| 42)
/// )
/// literal("foo").then(literal("bar").executes(|ctx: &CommandContext<()>| 42))
/// # ;
/// ```
pub fn then(self, argument: ArgumentBuilder<S>) -> Self {
@ -79,6 +91,16 @@ impl<S> ArgumentBuilder<S> {
pub fn executes<F>(mut self, f: F) -> Self
where
F: Fn(&CommandContext<S>) -> i32 + Send + Sync + 'static,
{
self.command = Some(Arc::new(move |ctx: &CommandContext<S>| Ok(f(ctx))));
self
}
/// Same as [`Self::executes`] but returns a `Result<i32,
/// CommandSyntaxError>`.
pub fn executes_result<F>(mut self, f: F) -> Self
where
F: Fn(&CommandContext<S>) -> Result<i32, CommandSyntaxError> + Send + Sync + 'static,
{
self.command = Some(Arc::new(f));
self
@ -163,7 +185,7 @@ impl<S> ArgumentBuilder<S> {
}
impl<S> Debug for ArgumentBuilder<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ArgumentBuilder")
.field("arguments", &self.arguments)
// .field("command", &self.command)

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 {
Self::Literal(literal)
}

View file

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

@ -1,7 +1,7 @@
use std::{
cmp::Ordering,
collections::{HashMap, HashSet},
mem, ptr,
ptr,
rc::Rc,
sync::Arc,
};
@ -10,9 +10,10 @@ use parking_lot::RwLock;
use crate::{
builder::argument_builder::ArgumentBuilder,
context::{CommandContext, CommandContextBuilder},
exceptions::{BuiltInExceptions, CommandSyntaxException},
context::{CommandContextBuilder, ContextChain},
errors::{BuiltInError, CommandSyntaxError},
parse_results::ParseResults,
result_consumer::{DefaultResultConsumer, ResultConsumer},
string_reader::StringReader,
suggestion::{Suggestions, SuggestionsBuilder},
tree::CommandNode,
@ -30,12 +31,14 @@ where
Self: Sync + Send,
{
pub root: Arc<RwLock<CommandNode<S>>>,
consumer: Box<dyn ResultConsumer<S> + Send + Sync>,
}
impl<S> CommandDispatcher<S> {
pub fn new() -> Self {
Self {
root: Arc::new(RwLock::new(CommandNode::default())),
consumer: Box::new(DefaultResultConsumer),
}
}
@ -64,10 +67,10 @@ impl<S> CommandDispatcher<S> {
node: &Arc<RwLock<CommandNode<S>>>,
original_reader: &StringReader,
context_so_far: CommandContextBuilder<'a, S>,
) -> Result<ParseResults<'a, S>, CommandSyntaxException> {
) -> Result<ParseResults<'a, S>, CommandSyntaxError> {
let source = context_so_far.source.clone();
#[allow(clippy::mutable_key_type)] // this is fine because we don't mutate the key
let mut errors = HashMap::<Rc<CommandNode<S>>, CommandSyntaxException>::new();
let mut errors = HashMap::<Rc<CommandNode<S>>, CommandSyntaxError>::new();
let mut potentials: Vec<ParseResults<S>> = vec![];
let cursor = original_reader.cursor();
@ -83,7 +86,7 @@ impl<S> CommandDispatcher<S> {
if let Err(ex) = parse_with_context_result {
errors.insert(
Rc::new((*child.read()).clone()),
BuiltInExceptions::DispatcherParseException {
BuiltInError::DispatcherParseException {
message: ex.message(),
}
.create_with_context(&reader),
@ -94,8 +97,7 @@ impl<S> CommandDispatcher<S> {
if reader.can_read() && reader.peek() != ' ' {
errors.insert(
Rc::new((*child.read()).clone()),
BuiltInExceptions::DispatcherExpectedArgumentSeparator
.create_with_context(&reader),
BuiltInError::DispatcherExpectedArgumentSeparator.create_with_context(&reader),
);
reader.cursor = cursor;
continue;
@ -179,11 +181,11 @@ impl<S> CommandDispatcher<S> {
&self,
input: impl Into<StringReader>,
source: S,
) -> Result<i32, CommandSyntaxException> {
) -> Result<i32, CommandSyntaxError> {
let input = input.into();
let parse = self.parse(input, source);
Self::execute_parsed(parse)
self.execute_parsed(parse)
}
pub fn add_paths(
@ -235,91 +237,26 @@ impl<S> CommandDispatcher<S> {
}
/// Executes a given pre-parsed command.
pub fn execute_parsed(parse: ParseResults<S>) -> Result<i32, CommandSyntaxException> {
pub fn execute_parsed(&self, parse: ParseResults<S>) -> Result<i32, CommandSyntaxError> {
if parse.reader.can_read() {
if parse.exceptions.len() == 1 {
return Err(parse.exceptions.values().next().unwrap().clone());
}
if parse.context.range.is_empty() {
return Err(
BuiltInExceptions::DispatcherUnknownCommand.create_with_context(&parse.reader)
);
}
return Err(
BuiltInExceptions::DispatcherUnknownArgument.create_with_context(&parse.reader)
);
return Err(if parse.exceptions.len() == 1 {
parse.exceptions.values().next().unwrap().clone()
} else if parse.context.range.is_empty() {
BuiltInError::DispatcherUnknownCommand.create_with_context(&parse.reader)
} else {
BuiltInError::DispatcherUnknownArgument.create_with_context(&parse.reader)
});
}
let mut result = 0i32;
let mut successful_forks = 0;
let mut forked = false;
let mut found_command = false;
let command = parse.reader.string();
let original = parse.context.build(command);
let mut contexts = vec![original];
let mut next: Vec<CommandContext<S>> = vec![];
let original = Rc::new(parse.context.build(command));
let flat_context = ContextChain::try_flatten(original.clone());
let Some(flat_context) = flat_context else {
self.consumer.on_command_complete(original, false, 0);
return Err(BuiltInError::DispatcherUnknownCommand.create_with_context(&parse.reader));
};
while !contexts.is_empty() {
for context in &contexts {
let child = &context.child;
if let Some(child) = child {
forked |= child.forks;
if child.has_nodes() {
found_command = true;
let modifier = &context.modifier;
if let Some(modifier) = modifier {
let results = modifier(context);
match results {
Ok(results) => {
if !results.is_empty() {
next.extend(
results.iter().map(|s| child.copy_for(s.clone())),
);
}
}
_ => {
// TODO
// self.consumer.on_command_complete(context, false, 0);
if !forked {
return Err(results.err().unwrap());
}
}
}
} else {
next.push(child.copy_for(context.source.clone()));
}
}
} else if let Some(context_command) = &context.command {
found_command = true;
let value = context_command(context);
result += value;
// consumer.on_command_complete(context, true, value);
successful_forks += 1;
// TODO: allow context_command to error and handle
// those errors
}
}
// move next into contexts and clear next
mem::swap(&mut contexts, &mut next);
next.clear();
}
if !found_command {
// consumer.on_command_complete(original, false, 0);
return Err(
BuiltInExceptions::DispatcherUnknownCommand.create_with_context(&parse.reader)
);
}
// TODO: this is not how vanilla does it but it works
Ok(if successful_forks >= 2 {
successful_forks
} else {
result
})
// Ok(if forked { successful_forks } else { result })
flat_context.execute_all(original.source.clone(), self.consumer.as_ref())
}
pub fn get_all_usage(

View file

@ -1,4 +1,10 @@
use std::{any::Any, collections::HashMap, fmt::Debug, rc::Rc, sync::Arc};
use std::{
any::Any,
collections::HashMap,
fmt::{self, Debug},
rc::Rc,
sync::Arc,
};
use parking_lot::RwLock;
@ -11,15 +17,15 @@ use crate::{
/// A built `CommandContextBuilder`.
pub struct CommandContext<S> {
pub source: Arc<S>,
pub input: String,
pub arguments: HashMap<String, ParsedArgument>,
pub command: Command<S>,
pub root_node: Arc<RwLock<CommandNode<S>>>,
pub nodes: Vec<ParsedCommandNode<S>>,
pub range: StringRange,
pub child: Option<Rc<CommandContext<S>>>,
pub modifier: Option<Arc<RedirectModifier<S>>>,
pub forks: bool,
pub(super) input: String,
pub(super) arguments: HashMap<String, ParsedArgument>,
pub(super) command: Command<S>,
pub(super) root_node: Arc<RwLock<CommandNode<S>>>,
pub(super) nodes: Vec<ParsedCommandNode<S>>,
pub(super) range: StringRange,
pub(super) child: Option<Rc<CommandContext<S>>>,
pub(super) modifier: Option<Arc<RedirectModifier<S>>>,
pub(super) forks: bool,
}
impl<S> Clone for CommandContext<S> {
@ -40,7 +46,7 @@ impl<S> Clone for CommandContext<S> {
}
impl<S> Debug for CommandContext<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CommandContext")
// .field("source", &self.source)
.field("input", &self.input)
@ -59,8 +65,10 @@ impl<S> Debug for CommandContext<S> {
impl<S> CommandContext<S> {
pub fn copy_for(&self, source: Arc<S>) -> Self {
if Arc::ptr_eq(&source, &self.source) {
// fast path
return self.clone();
}
CommandContext {
source,
input: self.input.clone(),
@ -75,12 +83,52 @@ impl<S> CommandContext<S> {
}
}
pub fn child(&self) -> Option<&CommandContext<S>> {
self.child.as_ref().map(|c| c.as_ref())
}
pub fn last_child(&self) -> &CommandContext<S> {
let mut result = self;
while let Some(child) = result.child() {
result = child;
}
result
}
pub fn command(&self) -> &Command<S> {
&self.command
}
pub fn argument(&self, name: &str) -> Option<&dyn Any> {
let argument = self.arguments.get(name);
argument.map(|a| a.result.as_ref())
}
pub fn redirect_modifier(&self) -> Option<&RedirectModifier<S>> {
self.modifier.as_ref().map(|m| m.as_ref())
}
pub fn range(&self) -> &StringRange {
&self.range
}
pub fn input(&self) -> &str {
&self.input
}
pub fn root_node(&self) -> &Arc<RwLock<CommandNode<S>>> {
&self.root_node
}
pub fn nodes(&self) -> &[ParsedCommandNode<S>] {
&self.nodes
}
pub fn has_nodes(&self) -> bool {
!self.nodes.is_empty()
}
pub fn argument(&self, name: &str) -> Option<Arc<dyn Any>> {
let argument = self.arguments.get(name);
argument.map(|a| a.result.clone())
pub fn is_forked(&self) -> bool {
self.forks
}
}

View file

@ -1,4 +1,9 @@
use std::{collections::HashMap, fmt::Debug, rc::Rc, sync::Arc};
use std::{
collections::HashMap,
fmt::{self, Debug},
rc::Rc,
sync::Arc,
};
use parking_lot::RwLock;
@ -140,7 +145,7 @@ impl<'a, S> CommandContextBuilder<'a, S> {
}
impl<S> Debug for CommandContextBuilder<'_, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CommandContextBuilder")
// .field("arguments", &self.arguments)
.field("root", &self.root)

View file

@ -0,0 +1,167 @@
use std::{rc::Rc, sync::Arc};
use super::CommandContext;
use crate::{errors::CommandSyntaxError, result_consumer::ResultConsumer};
pub struct ContextChain<S> {
modifiers: Vec<Rc<CommandContext<S>>>,
executable: Rc<CommandContext<S>>,
next_stage_cache: Option<Rc<ContextChain<S>>>,
}
impl<S> ContextChain<S> {
pub fn new(modifiers: Vec<Rc<CommandContext<S>>>, executable: Rc<CommandContext<S>>) -> Self {
if executable.command.is_none() {
panic!("Last command in chain must be executable");
}
Self {
modifiers,
executable,
next_stage_cache: None,
}
}
pub fn try_flatten(root_context: Rc<CommandContext<S>>) -> Option<Self> {
let mut modifiers = Vec::new();
let mut current = root_context;
loop {
let child = current.child.clone();
let Some(child) = child else {
// Last entry must be executable command
current.command.as_ref()?;
return Some(ContextChain::new(modifiers, current));
};
modifiers.push(current);
current = child;
}
}
pub fn run_modifier(
modifier: Rc<CommandContext<S>>,
source: Arc<S>,
result_consumer: &dyn ResultConsumer<S>,
forked_mode: bool,
) -> Result<Vec<Arc<S>>, CommandSyntaxError> {
let source_modifier = modifier.redirect_modifier();
let Some(source_modifier) = source_modifier else {
return Ok(vec![source]);
};
let context_to_use = Rc::new(modifier.copy_for(source));
let err = match (source_modifier)(&context_to_use) {
Ok(res) => return Ok(res),
Err(e) => e,
};
result_consumer.on_command_complete(context_to_use, false, 0);
if forked_mode {
return Ok(vec![]);
}
Err(err)
}
pub fn run_executable(
&self,
executable: Rc<CommandContext<S>>,
source: Arc<S>,
result_consumer: &dyn ResultConsumer<S>,
forked_mode: bool,
) -> Result<i32, CommandSyntaxError> {
let context_to_use = Rc::new(executable.copy_for(source));
let Some(command) = &executable.command else {
unimplemented!();
};
let err = match (command)(&context_to_use) {
Ok(result) => {
result_consumer.on_command_complete(context_to_use, true, result);
return if forked_mode { Ok(1) } else { Ok(result) };
}
Err(err) => err,
};
result_consumer.on_command_complete(context_to_use, false, 0);
if forked_mode { Ok(0) } else { Err(err) }
}
pub fn execute_all(
&self,
source: Arc<S>,
result_consumer: &(dyn ResultConsumer<S>),
) -> Result<i32, CommandSyntaxError> {
if self.modifiers.is_empty() {
return self.run_executable(self.executable.clone(), source, result_consumer, false);
}
let mut forked_mode = false;
let mut current_sources = vec![source];
for modifier in &self.modifiers {
forked_mode |= modifier.is_forked();
let mut next_sources = Vec::new();
for source_to_run in current_sources {
next_sources.extend(Self::run_modifier(
modifier.clone(),
source_to_run.clone(),
result_consumer,
forked_mode,
)?);
}
if next_sources.is_empty() {
return Ok(0);
}
current_sources = next_sources;
}
let mut result = 0;
for execution_source in current_sources {
result += self.run_executable(
self.executable.clone(),
execution_source,
result_consumer,
forked_mode,
)?;
}
Ok(result)
}
pub fn stage(&self) -> Stage {
if self.modifiers.is_empty() {
Stage::Execute
} else {
Stage::Modify
}
}
pub fn top_context(&self) -> Rc<CommandContext<S>> {
self.modifiers
.first()
.cloned()
.unwrap_or_else(|| self.executable.clone())
}
pub fn next_stage(&mut self) -> Option<Rc<ContextChain<S>>> {
let modifier_count = self.modifiers.len();
if modifier_count == 0 {
return None;
}
if self.next_stage_cache.is_none() {
self.next_stage_cache = Some(Rc::new(ContextChain::new(
self.modifiers[1..].to_vec(),
self.executable.clone(),
)));
}
self.next_stage_cache.clone()
}
}
pub enum Stage {
Modify,
Execute,
}

View file

@ -1,5 +1,6 @@
mod command_context;
mod command_context_builder;
mod context_chain;
mod parsed_argument;
mod parsed_command_node;
mod string_range;
@ -7,6 +8,7 @@ pub mod suggestion_context;
pub use command_context::CommandContext;
pub use command_context_builder::CommandContextBuilder;
pub use context_chain::ContextChain;
pub use parsed_argument::ParsedArgument;
pub use parsed_command_node::ParsedCommandNode;
pub use string_range::StringRange;

View file

@ -1,10 +1,10 @@
use std::fmt;
use super::command_syntax_exception::CommandSyntaxException;
use super::command_syntax_error::CommandSyntaxError;
use crate::string_reader::StringReader;
#[derive(Clone, PartialEq)]
pub enum BuiltInExceptions {
pub enum BuiltInError {
DoubleTooSmall { found: f64, min: f64 },
DoubleTooBig { found: f64, max: f64 },
@ -40,114 +40,114 @@ pub enum BuiltInExceptions {
DispatcherParseException { message: String },
}
impl fmt::Debug for BuiltInExceptions {
impl fmt::Debug for BuiltInError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BuiltInExceptions::DoubleTooSmall { found, min } => {
BuiltInError::DoubleTooSmall { found, min } => {
write!(f, "Double must not be less than {min}, found {found}")
}
BuiltInExceptions::DoubleTooBig { found, max } => {
BuiltInError::DoubleTooBig { found, max } => {
write!(f, "Double must not be more than {max}, found {found}")
}
BuiltInExceptions::FloatTooSmall { found, min } => {
BuiltInError::FloatTooSmall { found, min } => {
write!(f, "Float must not be less than {min}, found {found}")
}
BuiltInExceptions::FloatTooBig { found, max } => {
BuiltInError::FloatTooBig { found, max } => {
write!(f, "Float must not be more than {max}, found {found}")
}
BuiltInExceptions::IntegerTooSmall { found, min } => {
BuiltInError::IntegerTooSmall { found, min } => {
write!(f, "Integer must not be less than {min}, found {found}")
}
BuiltInExceptions::IntegerTooBig { found, max } => {
BuiltInError::IntegerTooBig { found, max } => {
write!(f, "Integer must not be more than {max}, found {found}")
}
BuiltInExceptions::LongTooSmall { found, min } => {
BuiltInError::LongTooSmall { found, min } => {
write!(f, "Long must not be less than {min}, found {found}")
}
BuiltInExceptions::LongTooBig { found, max } => {
BuiltInError::LongTooBig { found, max } => {
write!(f, "Long must not be more than {max}, found {found}")
}
BuiltInExceptions::LiteralIncorrect { expected } => {
BuiltInError::LiteralIncorrect { expected } => {
write!(f, "Expected literal {expected}")
}
BuiltInExceptions::ReaderExpectedStartOfQuote => {
BuiltInError::ReaderExpectedStartOfQuote => {
write!(f, "Expected quote to start a string")
}
BuiltInExceptions::ReaderExpectedEndOfQuote => {
BuiltInError::ReaderExpectedEndOfQuote => {
write!(f, "Unclosed quoted string")
}
BuiltInExceptions::ReaderInvalidEscape { character } => {
BuiltInError::ReaderInvalidEscape { character } => {
write!(f, "Invalid escape sequence '{character}' in quoted string")
}
BuiltInExceptions::ReaderInvalidBool { value } => {
BuiltInError::ReaderInvalidBool { value } => {
write!(
f,
"Invalid bool, expected true or false but found '{value}'"
)
}
BuiltInExceptions::ReaderInvalidInt { value } => {
BuiltInError::ReaderInvalidInt { value } => {
write!(f, "Invalid Integer '{value}'")
}
BuiltInExceptions::ReaderExpectedInt => {
BuiltInError::ReaderExpectedInt => {
write!(f, "Expected Integer")
}
BuiltInExceptions::ReaderInvalidLong { value } => {
BuiltInError::ReaderInvalidLong { value } => {
write!(f, "Invalid long '{value}'")
}
BuiltInExceptions::ReaderExpectedLong => {
BuiltInError::ReaderExpectedLong => {
write!(f, "Expected long")
}
BuiltInExceptions::ReaderInvalidDouble { value } => {
BuiltInError::ReaderInvalidDouble { value } => {
write!(f, "Invalid double '{value}'")
}
BuiltInExceptions::ReaderExpectedDouble => {
BuiltInError::ReaderExpectedDouble => {
write!(f, "Expected double")
}
BuiltInExceptions::ReaderInvalidFloat { value } => {
BuiltInError::ReaderInvalidFloat { value } => {
write!(f, "Invalid Float '{value}'")
}
BuiltInExceptions::ReaderExpectedFloat => {
BuiltInError::ReaderExpectedFloat => {
write!(f, "Expected Float")
}
BuiltInExceptions::ReaderExpectedBool => {
BuiltInError::ReaderExpectedBool => {
write!(f, "Expected bool")
}
BuiltInExceptions::ReaderExpectedSymbol { symbol } => {
BuiltInError::ReaderExpectedSymbol { symbol } => {
write!(f, "Expected '{symbol}'")
}
BuiltInExceptions::DispatcherUnknownCommand => {
BuiltInError::DispatcherUnknownCommand => {
write!(f, "Unknown command")
}
BuiltInExceptions::DispatcherUnknownArgument => {
BuiltInError::DispatcherUnknownArgument => {
write!(f, "Incorrect argument for command")
}
BuiltInExceptions::DispatcherExpectedArgumentSeparator => {
BuiltInError::DispatcherExpectedArgumentSeparator => {
write!(
f,
"Expected whitespace to end one argument, but found trailing data"
)
}
BuiltInExceptions::DispatcherParseException { message } => {
BuiltInError::DispatcherParseException { message } => {
write!(f, "Could not parse command: {message}")
}
}
}
}
impl BuiltInExceptions {
pub fn create(self) -> CommandSyntaxException {
impl BuiltInError {
pub fn create(self) -> CommandSyntaxError {
let message = format!("{self:?}");
CommandSyntaxException::create(self, message)
CommandSyntaxError::create(self, message)
}
pub fn create_with_context(self, reader: &StringReader) -> CommandSyntaxException {
pub fn create_with_context(self, reader: &StringReader) -> CommandSyntaxError {
let message = format!("{self:?}");
CommandSyntaxException::new(self, message, reader.string(), reader.cursor())
CommandSyntaxError::new(self, message, reader.string(), reader.cursor())
}
}

View file

@ -0,0 +1,93 @@
use std::{
cmp,
fmt::{self, Debug, Write},
};
use super::builtin_errors::BuiltInError;
#[derive(Clone, PartialEq)]
pub struct CommandSyntaxError {
kind: BuiltInError,
message: String,
input: Option<String>,
cursor: Option<usize>,
}
const CONTEXT_AMOUNT: usize = 10;
impl CommandSyntaxError {
pub fn new(kind: BuiltInError, message: String, input: &str, cursor: usize) -> Self {
Self {
kind,
message,
input: Some(input.to_string()),
cursor: Some(cursor),
}
}
pub fn create(kind: BuiltInError, message: String) -> Self {
Self {
kind,
message,
input: None,
cursor: None,
}
}
pub fn message(&self) -> String {
let mut message = self.message.clone();
let context = self.context();
if let Some(context) = context {
write!(
message,
" at position {}: {context}",
self.cursor.unwrap_or(usize::MAX)
)
.unwrap();
}
message
}
pub fn raw_message(&self) -> &String {
&self.message
}
pub fn context(&self) -> Option<String> {
if let Some(input) = &self.input
&& let Some(cursor) = self.cursor
{
let mut builder = String::new();
let cursor = cmp::min(input.len(), cursor);
if cursor > CONTEXT_AMOUNT {
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);
}
None
}
pub fn kind(&self) -> &BuiltInError {
&self.kind
}
pub fn input(&self) -> &Option<String> {
&self.input
}
pub fn cursor(&self) -> Option<usize> {
self.cursor
}
}
impl Debug for CommandSyntaxError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message())
}
}

View file

@ -0,0 +1,5 @@
mod builtin_errors;
mod command_syntax_error;
pub use builtin_errors::BuiltInError;
pub use command_syntax_error::CommandSyntaxError;

View file

@ -1,95 +0,0 @@
use std::{
cmp,
fmt::{self, Write},
};
use super::builtin_exceptions::BuiltInExceptions;
#[derive(Clone, PartialEq)]
pub struct CommandSyntaxException {
pub type_: BuiltInExceptions,
message: String,
input: Option<String>,
cursor: Option<usize>,
}
const CONTEXT_AMOUNT: usize = 10;
impl CommandSyntaxException {
pub fn new(type_: BuiltInExceptions, message: String, input: &str, cursor: usize) -> Self {
Self {
type_,
message,
input: Some(input.to_string()),
cursor: Some(cursor),
}
}
pub fn create(type_: BuiltInExceptions, message: String) -> Self {
Self {
type_,
message,
input: None,
cursor: None,
}
}
pub fn message(&self) -> String {
let mut message = self.message.clone();
let context = self.context();
if let Some(context) = context {
write!(
message,
" at position {}: {}",
self.cursor.unwrap_or(usize::MAX),
context
)
.unwrap();
}
message
}
pub fn raw_message(&self) -> &String {
&self.message
}
pub fn context(&self) -> Option<String> {
if let Some(input) = &self.input {
if let Some(cursor) = self.cursor {
let mut builder = String::new();
let cursor = cmp::min(input.len(), cursor);
if cursor > CONTEXT_AMOUNT {
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);
}
}
None
}
pub fn get_type(&self) -> &BuiltInExceptions {
&self.type_
}
pub fn input(&self) -> &Option<String> {
&self.input
}
pub fn cursor(&self) -> Option<usize> {
self.cursor
}
}
impl fmt::Debug for CommandSyntaxException {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message())
}
}

View file

@ -1,5 +0,0 @@
mod builtin_exceptions;
mod command_syntax_exception;
pub use builtin_exceptions::BuiltInExceptions;
pub use command_syntax_exception::CommandSyntaxException;

View file

@ -4,9 +4,10 @@ pub mod arguments;
pub mod builder;
pub mod command_dispatcher;
pub mod context;
pub mod exceptions;
pub mod errors;
pub mod modifier;
pub mod parse_results;
pub mod result_consumer;
pub mod string_reader;
pub mod suggestion;
pub mod tree;

View file

@ -1,6 +1,6 @@
use std::sync::Arc;
use crate::{context::CommandContext, exceptions::CommandSyntaxException};
use crate::{context::CommandContext, errors::CommandSyntaxError};
pub type RedirectModifier<S> =
dyn Fn(&CommandContext<S>) -> Result<Vec<Arc<S>>, CommandSyntaxException> + Send + Sync;
dyn Fn(&CommandContext<S>) -> Result<Vec<Arc<S>>, CommandSyntaxError> + Send + Sync;

View file

@ -1,18 +1,22 @@
use std::{collections::HashMap, fmt::Debug, rc::Rc};
use std::{
collections::HashMap,
fmt::{self, Debug},
rc::Rc,
};
use crate::{
context::CommandContextBuilder, exceptions::CommandSyntaxException,
string_reader::StringReader, tree::CommandNode,
context::CommandContextBuilder, errors::CommandSyntaxError, string_reader::StringReader,
tree::CommandNode,
};
pub struct ParseResults<'a, S> {
pub context: CommandContextBuilder<'a, S>,
pub reader: StringReader,
pub exceptions: HashMap<Rc<CommandNode<S>>, CommandSyntaxException>,
pub exceptions: HashMap<Rc<CommandNode<S>>, CommandSyntaxError>,
}
impl<S> Debug for ParseResults<'_, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ParseResults")
.field("context", &self.context)
// .field("reader", &self.reader)

View file

@ -0,0 +1,12 @@
use std::rc::Rc;
use crate::context::CommandContext;
pub trait ResultConsumer<S> {
fn on_command_complete(&self, context: Rc<CommandContext<S>>, success: bool, result: i32);
}
pub struct DefaultResultConsumer;
impl<S> ResultConsumer<S> for DefaultResultConsumer {
fn on_command_complete(&self, _context: Rc<CommandContext<S>>, _success: bool, _result: i32) {}
}

View file

@ -1,6 +1,6 @@
use std::str::FromStr;
use crate::exceptions::{BuiltInExceptions, CommandSyntaxException};
use crate::errors::{BuiltInError, CommandSyntaxError};
#[derive(Clone)]
pub struct StringReader {
@ -91,19 +91,19 @@ impl StringReader {
}
}
pub fn read_int(&mut self) -> Result<i32, CommandSyntaxException> {
pub fn read_int(&mut self) -> Result<i32, CommandSyntaxError> {
let start = self.cursor;
while self.can_read() && StringReader::is_allowed_number(self.peek()) {
self.skip();
}
let number = &self.string[start..self.cursor];
if number.is_empty() {
return Err(BuiltInExceptions::ReaderExpectedInt.create_with_context(self));
return Err(BuiltInError::ReaderExpectedInt.create_with_context(self));
}
let result = i32::from_str(number);
if result.is_err() {
self.cursor = start;
return Err(BuiltInExceptions::ReaderInvalidInt {
return Err(BuiltInError::ReaderInvalidInt {
value: number.to_string(),
}
.create_with_context(self));
@ -112,19 +112,19 @@ impl StringReader {
Ok(result.unwrap())
}
pub fn read_long(&mut self) -> Result<i64, CommandSyntaxException> {
pub fn read_long(&mut self) -> Result<i64, CommandSyntaxError> {
let start = self.cursor;
while self.can_read() && StringReader::is_allowed_number(self.peek()) {
self.skip();
}
let number = &self.string[start..self.cursor];
if number.is_empty() {
return Err(BuiltInExceptions::ReaderExpectedLong.create_with_context(self));
return Err(BuiltInError::ReaderExpectedLong.create_with_context(self));
}
let result = i64::from_str(number);
if result.is_err() {
self.cursor = start;
return Err(BuiltInExceptions::ReaderInvalidLong {
return Err(BuiltInError::ReaderInvalidLong {
value: number.to_string(),
}
.create_with_context(self));
@ -133,19 +133,19 @@ impl StringReader {
Ok(result.unwrap())
}
pub fn read_double(&mut self) -> Result<f64, CommandSyntaxException> {
pub fn read_double(&mut self) -> Result<f64, CommandSyntaxError> {
let start = self.cursor;
while self.can_read() && StringReader::is_allowed_number(self.peek()) {
self.skip();
}
let number = &self.string[start..self.cursor];
if number.is_empty() {
return Err(BuiltInExceptions::ReaderExpectedDouble.create_with_context(self));
return Err(BuiltInError::ReaderExpectedDouble.create_with_context(self));
}
let result = f64::from_str(number);
if result.is_err() {
self.cursor = start;
return Err(BuiltInExceptions::ReaderInvalidDouble {
return Err(BuiltInError::ReaderInvalidDouble {
value: number.to_string(),
}
.create_with_context(self));
@ -154,19 +154,19 @@ impl StringReader {
Ok(result.unwrap())
}
pub fn read_float(&mut self) -> Result<f32, CommandSyntaxException> {
pub fn read_float(&mut self) -> Result<f32, CommandSyntaxError> {
let start = self.cursor;
while self.can_read() && StringReader::is_allowed_number(self.peek()) {
self.skip();
}
let number = &self.string[start..self.cursor];
if number.is_empty() {
return Err(BuiltInExceptions::ReaderExpectedFloat.create_with_context(self));
return Err(BuiltInError::ReaderExpectedFloat.create_with_context(self));
}
let result = f32::from_str(number);
if result.is_err() {
self.cursor = start;
return Err(BuiltInExceptions::ReaderInvalidFloat {
return Err(BuiltInError::ReaderInvalidFloat {
value: number.to_string(),
}
.create_with_context(self));
@ -193,22 +193,19 @@ impl StringReader {
&self.string[start..self.cursor]
}
pub fn read_quoted_string(&mut self) -> Result<String, CommandSyntaxException> {
pub fn read_quoted_string(&mut self) -> Result<String, CommandSyntaxError> {
if !self.can_read() {
return Ok(String::new());
}
let next = self.peek();
if !StringReader::is_quoted_string_start(next) {
return Err(BuiltInExceptions::ReaderExpectedStartOfQuote.create_with_context(self));
return Err(BuiltInError::ReaderExpectedStartOfQuote.create_with_context(self));
}
self.skip();
self.read_string_until(next)
}
pub fn read_string_until(
&mut self,
terminator: char,
) -> Result<String, CommandSyntaxException> {
pub fn read_string_until(&mut self, terminator: char) -> Result<String, CommandSyntaxError> {
let mut result = String::new();
let mut escaped = false;
while self.can_read() {
@ -219,7 +216,7 @@ impl StringReader {
escaped = false;
} else {
self.cursor -= 1;
return Err(BuiltInExceptions::ReaderInvalidEscape { character: c }
return Err(BuiltInError::ReaderInvalidEscape { character: c }
.create_with_context(self));
}
} else if c == SYNTAX_ESCAPE {
@ -231,10 +228,10 @@ impl StringReader {
}
}
Err(BuiltInExceptions::ReaderExpectedEndOfQuote.create_with_context(self))
Err(BuiltInError::ReaderExpectedEndOfQuote.create_with_context(self))
}
pub fn read_string(&mut self) -> Result<String, CommandSyntaxException> {
pub fn read_string(&mut self) -> Result<String, CommandSyntaxError> {
if !self.can_read() {
return Ok(String::new());
}
@ -246,11 +243,11 @@ impl StringReader {
Ok(self.read_unquoted_string().to_string())
}
pub fn read_boolean(&mut self) -> Result<bool, CommandSyntaxException> {
pub fn read_boolean(&mut self) -> Result<bool, CommandSyntaxError> {
let start = self.cursor;
let value = self.read_string()?;
if value.is_empty() {
return Err(BuiltInExceptions::ReaderExpectedBool.create_with_context(self));
return Err(BuiltInError::ReaderExpectedBool.create_with_context(self));
}
if value == "true" {
@ -259,15 +256,13 @@ impl StringReader {
Ok(false)
} else {
self.cursor = start;
Err(BuiltInExceptions::ReaderInvalidBool { value }.create_with_context(self))
Err(BuiltInError::ReaderInvalidBool { value }.create_with_context(self))
}
}
pub fn expect(&mut self, c: char) -> Result<(), CommandSyntaxException> {
pub fn expect(&mut self, c: char) -> Result<(), CommandSyntaxError> {
if !self.can_read() || self.peek() != c {
return Err(
BuiltInExceptions::ReaderExpectedSymbol { symbol: c }.create_with_context(self)
);
return Err(BuiltInError::ReaderExpectedSymbol { symbol: c }.create_with_context(self));
}
self.skip();
Ok(())

View file

@ -1,17 +1,21 @@
mod suggestion_provider;
mod suggestions;
mod suggestions_builder;
#[cfg(feature = "azalea-buf")]
use std::io::Write;
use std::{
cmp::Ordering,
fmt::{self, Display},
hash::Hash,
io,
};
#[cfg(feature = "azalea-buf")]
use azalea_buf::AzaleaWrite;
#[cfg(feature = "azalea-buf")]
use azalea_chat::FormattedText;
pub use suggestion_provider::SuggestionProvider;
pub use suggestions::Suggestions;
pub use suggestions_builder::SuggestionsBuilder;
@ -93,7 +97,7 @@ impl Suggestion {
}
impl SuggestionValue {
pub fn cmp_ignore_case(&self, other: &Self) -> std::cmp::Ordering {
pub fn cmp_ignore_case(&self, other: &Self) -> Ordering {
match (self, other) {
(SuggestionValue::Text(a), SuggestionValue::Text(b)) => {
a.to_lowercase().cmp(&b.to_lowercase())
@ -118,7 +122,7 @@ impl Display for SuggestionValue {
}
impl Ord for SuggestionValue {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(SuggestionValue::Text(a), SuggestionValue::Text(b)) => a.cmp(b),
(SuggestionValue::Integer(a), SuggestionValue::Integer(b)) => a.cmp(b),
@ -131,14 +135,14 @@ impl Ord for SuggestionValue {
}
}
impl PartialOrd for SuggestionValue {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[cfg(feature = "azalea-buf")]
impl AzaleaWrite for Suggestion {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
self.value.to_string().azalea_write(buf)?;
self.tooltip
.clone()

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

@ -1,6 +1,6 @@
#[cfg(feature = "azalea-buf")]
use std::io::{Cursor, Write};
use std::{collections::HashSet, hash::Hash};
use std::{collections::HashSet, hash::Hash, io};
#[cfg(feature = "azalea-buf")]
use azalea_buf::{AzBuf, AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
@ -107,7 +107,7 @@ impl AzaleaRead for Suggestions {
#[cfg(feature = "azalea-buf")]
impl AzaleaWrite for Suggestions {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
(self.range.start() as u32).azalea_write_var(buf)?;
(self.range.length() as u32).azalea_write_var(buf)?;
self.suggestions.azalea_write(buf)?;

View file

@ -1,7 +1,7 @@
use std::{
collections::{BTreeMap, HashMap},
fmt::Debug,
hash::Hash,
fmt::{self, Debug},
hash::{Hash, Hasher},
ptr,
sync::Arc,
};
@ -14,18 +14,19 @@ use crate::{
required_argument_builder::Argument,
},
context::{CommandContext, CommandContextBuilder, ParsedArgument, StringRange},
exceptions::{BuiltInExceptions, CommandSyntaxException},
errors::{BuiltInError, CommandSyntaxError},
modifier::RedirectModifier,
string_reader::StringReader,
suggestion::{Suggestions, SuggestionsBuilder},
};
pub type Command<S> = Option<Arc<dyn Fn(&CommandContext<S>) -> i32 + Send + Sync>>;
pub type Command<S> =
Option<Arc<dyn Fn(&CommandContext<S>) -> Result<i32, CommandSyntaxError> + Send + Sync>>;
/// An ArgumentBuilder that has been built.
#[non_exhaustive]
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
pub children: BTreeMap<String, Arc<RwLock<CommandNode<S>>>>,
@ -66,7 +67,7 @@ impl<S> CommandNode<S> {
}
/// Gets the argument, or panics. You should use match if you're not certain
/// about the type.
pub fn argument(&self) -> &Argument {
pub fn argument(&self) -> &Argument<S> {
match self.value {
ArgumentBuilderType::Argument(ref argument) => argument,
_ => panic!("CommandNode::argument() called on non-argument node"),
@ -149,7 +150,7 @@ impl<S> CommandNode<S> {
&self,
reader: &mut StringReader,
context_builder: &mut CommandContextBuilder<S>,
) -> Result<(), CommandSyntaxException> {
) -> Result<(), CommandSyntaxError> {
match self.value {
ArgumentBuilderType::Argument(ref argument) => {
let start = reader.cursor();
@ -176,7 +177,7 @@ impl<S> CommandNode<S> {
return Ok(());
}
Err(BuiltInExceptions::LiteralIncorrect {
Err(BuiltInError::LiteralIncorrect {
expected: literal.value.clone(),
}
.create_with_context(reader))
@ -214,9 +215,7 @@ impl<S> CommandNode<S> {
pub fn list_suggestions(
&self,
// context is here because that's how it is in mojang's brigadier, but we haven't
// implemented custom suggestions yet so this is unused rn
_context: CommandContext<S>,
context: CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
match &self.value {
@ -231,15 +230,15 @@ impl<S> CommandNode<S> {
Suggestions::default()
}
}
ArgumentBuilderType::Argument(argument) => argument.list_suggestions(builder),
ArgumentBuilderType::Argument(argument) => argument.list_suggestions(context, builder),
}
}
}
impl<S> Debug for CommandNode<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CommandNode")
.field("value", &self.value)
// .field("value", &self.value)
.field("children", &self.children)
.field("command", &self.command.is_some())
// .field("requirement", &self.requirement)
@ -269,7 +268,7 @@ impl<S> Default for CommandNode<S> {
}
impl<S> Hash for CommandNode<S> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
fn hash<H: Hasher>(&self, state: &mut H) {
// hash the children
for (k, v) in &self.children {
k.hash(state);

View file

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

View file

@ -5,7 +5,7 @@ use azalea_brigadier::{
builder::{literal_argument_builder::literal, required_argument_builder::argument},
command_dispatcher::CommandDispatcher,
context::CommandContext,
exceptions::{BuiltInExceptions, CommandSyntaxException},
errors::{BuiltInError, CommandSyntaxError},
string_reader::StringReader,
};
@ -50,10 +50,7 @@ fn execute_unknown_command() {
let execute_result = subject.execute("foo", &CommandSource {});
let err = execute_result.err().unwrap();
match err.type_ {
BuiltInExceptions::DispatcherUnknownCommand => {}
_ => panic!("Unexpected error"),
}
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownCommand);
assert_eq!(err.cursor().unwrap(), 0);
}
@ -65,10 +62,7 @@ fn execute_impermissible_command() {
let execute_result = subject.execute("foo", &CommandSource {});
let err = execute_result.err().unwrap();
match err.type_ {
BuiltInExceptions::DispatcherUnknownCommand => {}
_ => panic!("Unexpected error"),
}
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownCommand);
assert_eq!(err.cursor().unwrap(), 0);
}
@ -80,10 +74,7 @@ fn execute_empty_command() {
let execute_result = subject.execute("", &CommandSource {});
let err = execute_result.err().unwrap();
match err.type_ {
BuiltInExceptions::DispatcherUnknownCommand => {}
_ => panic!("Unexpected error"),
}
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownCommand);
assert_eq!(err.cursor().unwrap(), 0);
}
@ -95,10 +86,7 @@ fn execute_unknown_subcommand() {
let execute_result = subject.execute("foo bar", &CommandSource {});
let err = execute_result.err().unwrap();
match err.type_ {
BuiltInExceptions::DispatcherUnknownArgument => {}
_ => panic!("Unexpected error"),
}
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownArgument);
assert_eq!(err.cursor().unwrap(), 4);
}
@ -110,10 +98,7 @@ fn execute_incorrect_literal() {
let execute_result = subject.execute("foo baz", &CommandSource {});
let err = execute_result.err().unwrap();
match err.type_ {
BuiltInExceptions::DispatcherUnknownArgument => {}
_ => panic!("Unexpected error"),
}
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownArgument);
assert_eq!(err.cursor().unwrap(), 4);
}
@ -130,10 +115,7 @@ fn execute_ambiguous_incorrect_argument() {
let execute_result = subject.execute("foo unknown", &CommandSource {});
let err = execute_result.err().unwrap();
match err.type_ {
BuiltInExceptions::DispatcherUnknownArgument => {}
_ => panic!("Unexpected error"),
}
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownArgument);
assert_eq!(err.cursor().unwrap(), 4);
}
@ -245,7 +227,7 @@ fn execute_redirected_multiple_times() {
);
assert_eq!(*child2.unwrap().nodes[0].node.read(), *concrete_node.read());
assert_eq!(CommandDispatcher::execute_parsed(parse).unwrap(), 42);
assert_eq!(subject.execute_parsed(parse).unwrap(), 42);
}
#[test]
@ -255,7 +237,7 @@ fn execute_redirected() {
let source1 = Arc::new(CommandSource {});
let source2 = Arc::new(CommandSource {});
let modifier = move |_: &CommandContext<CommandSource>| -> Result<Vec<Arc<CommandSource>>, CommandSyntaxException> {
let modifier = move |_: &CommandContext<CommandSource>| -> Result<Vec<Arc<CommandSource>>, CommandSyntaxError> {
Ok(vec![source1.clone(), source2.clone()])
};
@ -281,7 +263,7 @@ fn execute_redirected() {
assert_eq!(*parent.nodes[0].node.read(), *concrete_node.read());
assert_eq!(*parent.source, CommandSource {});
assert_eq!(CommandDispatcher::execute_parsed(parse).unwrap(), 2);
assert_eq!(subject.execute_parsed(parse).unwrap(), 2);
}
#[test]
@ -297,10 +279,7 @@ fn execute_orphaned_subcommand() {
let result = subject.execute("foo 5", &CommandSource {});
assert!(result.is_err());
let result = result.unwrap_err();
assert_eq!(
*result.get_type(),
BuiltInExceptions::DispatcherUnknownCommand
);
assert_eq!(*result.kind(), BuiltInError::DispatcherUnknownCommand);
assert_eq!(result.cursor(), Some(5));
}
@ -327,10 +306,7 @@ fn parse_no_space_separator() {
let result = subject.execute("foo$", &CommandSource {});
assert!(result.is_err());
let result = result.unwrap_err();
assert_eq!(
*result.get_type(),
BuiltInExceptions::DispatcherUnknownCommand
);
assert_eq!(*result.kind(), BuiltInError::DispatcherUnknownCommand);
assert_eq!(result.cursor(), Some(0));
}
@ -348,7 +324,7 @@ fn execute_invalid_subcommand() {
assert!(result.is_err());
let result = result.unwrap_err();
// this fails for some reason, i blame mojang
// assert_eq!(*result.get_type(), BuiltInExceptions::ReaderExpectedInt);
// assert_eq!(*result.get_type(), BuiltInError::ReaderExpectedInt);
assert_eq!(result.cursor(), Some(4));
}

View file

@ -1,4 +1,4 @@
use azalea_brigadier::{exceptions::BuiltInExceptions, string_reader::StringReader};
use azalea_brigadier::{errors::BuiltInError, string_reader::StringReader};
#[test]
fn can_read() {
@ -222,7 +222,7 @@ fn read_quoted_string_no_open() {
let result = reader.read_quoted_string();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedStartOfQuote);
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedStartOfQuote);
assert_eq!(e.cursor(), Some(0));
}
}
@ -233,7 +233,7 @@ fn read_quoted_string_no_close() {
let result = reader.read_quoted_string();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedEndOfQuote);
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedEndOfQuote);
assert_eq!(e.cursor(), Some(12));
}
}
@ -245,8 +245,8 @@ fn read_quoted_string_invalid_escape() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidEscape { character: 'n' }
e.kind(),
&BuiltInError::ReaderInvalidEscape { character: 'n' }
);
assert_eq!(e.cursor(), Some(7));
}
@ -259,8 +259,8 @@ fn read_quoted_string_invalid_quote_escape() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidEscape { character: '"' }
e.kind(),
&BuiltInError::ReaderInvalidEscape { character: '"' }
);
assert_eq!(e.cursor(), Some(7));
}
@ -313,8 +313,8 @@ fn read_int_invalid() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidInt {
e.kind(),
&BuiltInError::ReaderInvalidInt {
value: "12.34".to_string()
}
);
@ -328,7 +328,7 @@ fn read_int_none() {
let result = reader.read_int();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedInt);
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedInt);
assert_eq!(e.cursor(), Some(0));
}
}
@ -372,8 +372,8 @@ fn read_long_invalid() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidLong {
e.kind(),
&BuiltInError::ReaderInvalidLong {
value: "12.34".to_string()
}
);
@ -387,7 +387,7 @@ fn read_long_none() {
let result = reader.read_long();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedLong);
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedLong);
assert_eq!(e.cursor(), Some(0));
}
}
@ -439,8 +439,8 @@ fn read_double_invalid() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidDouble {
e.kind(),
&BuiltInError::ReaderInvalidDouble {
value: "12.34.56".to_string()
}
);
@ -454,7 +454,7 @@ fn read_double_none() {
let result = reader.read_double();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedDouble);
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedDouble);
assert_eq!(e.cursor(), Some(0));
}
}
@ -506,8 +506,8 @@ fn read_float_invalid() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidFloat {
e.kind(),
&BuiltInError::ReaderInvalidFloat {
value: "12.34.56".to_string()
}
);
@ -521,7 +521,7 @@ fn read_float_none() {
let result = reader.read_float();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedFloat);
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedFloat);
assert_eq!(e.cursor(), Some(0));
}
}
@ -556,8 +556,8 @@ fn expect_incorrect() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderExpectedSymbol { symbol: 'a' }
e.kind(),
&BuiltInError::ReaderExpectedSymbol { symbol: 'a' }
);
assert_eq!(e.cursor(), Some(0));
}
@ -570,8 +570,8 @@ fn expect_none() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderExpectedSymbol { symbol: 'a' }
e.kind(),
&BuiltInError::ReaderExpectedSymbol { symbol: 'a' }
);
assert_eq!(e.cursor(), Some(0));
}
@ -591,8 +591,8 @@ fn read_boolean_incorrect() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidBool {
e.kind(),
&BuiltInError::ReaderInvalidBool {
value: "tuesday".to_string()
}
);
@ -606,7 +606,7 @@ fn read_boolean_none() {
let result = reader.read_boolean();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedBool);
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedBool);
assert_eq!(e.cursor(), Some(0));
}
}

View file

@ -17,7 +17,7 @@ fn merge_single() {
StringRange::at(5),
vec![Suggestion::new(StringRange::at(5), "ar")],
);
let merged = Suggestions::merge("foo b", &[suggestions.clone()]);
let merged = Suggestions::merge("foo b", std::slice::from_ref(&suggestions));
assert_eq!(merged, suggestions);
}

View file

@ -2,7 +2,7 @@ use std::{
backtrace::Backtrace,
collections::HashMap,
hash::Hash,
io::{Cursor, Read},
io::{self, Cursor, Read},
sync::Arc,
};
@ -30,7 +30,7 @@ pub enum BufReadError {
Io {
#[from]
#[backtrace]
source: std::io::Error,
source: io::Error,
},
#[error("Invalid UTF-8: {bytes:?} (lossy: {lossy:?})")]
InvalidUtf8 {

View file

@ -1,4 +1,4 @@
use std::io::{Cursor, Write};
use std::io::{self, Cursor, Write};
use uuid::Uuid;
@ -46,7 +46,7 @@ impl AzaleaRead for Uuid {
}
impl AzaleaWrite for Uuid {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
let [a, b, c, d] = self.to_int_array();
a.azalea_write(buf)?;
b.azalea_write(buf)?;

View file

@ -8,7 +8,7 @@ use byteorder::{BigEndian, WriteBytesExt};
use super::{MAX_STRING_LENGTH, UnsizedByteArray};
fn write_utf_with_len(buf: &mut impl Write, string: &str, len: usize) -> Result<(), io::Error> {
fn write_utf_with_len(buf: &mut impl Write, string: &str, len: usize) -> io::Result<()> {
if string.len() > len {
panic!(
"String too big (was {} bytes encoded, max {})",
@ -21,21 +21,21 @@ fn write_utf_with_len(buf: &mut impl Write, string: &str, len: usize) -> Result<
}
pub trait AzaleaWrite {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error>;
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()>;
}
pub trait AzaleaWriteVar {
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error>;
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()>;
}
impl AzaleaWrite for i32 {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
WriteBytesExt::write_i32::<BigEndian>(buf, *self)
}
}
impl AzaleaWriteVar for i32 {
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
let mut buffer = [0];
let mut value = *self;
if value == 0 {
@ -54,24 +54,24 @@ impl AzaleaWriteVar for i32 {
}
impl AzaleaWrite for UnsizedByteArray {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
buf.write_all(self)
}
}
impl<T: AzaleaWrite> AzaleaWrite for Vec<T> {
default fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
default fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
self[..].azalea_write(buf)
}
}
impl<T: AzaleaWrite> AzaleaWrite for Box<[T]> {
default fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
default fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
self[..].azalea_write(buf)
}
}
impl<T: AzaleaWrite> AzaleaWrite for [T] {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
(self.len() as u32).azalea_write_var(buf)?;
for item in self {
T::azalea_write(item, buf)?;
@ -81,7 +81,7 @@ impl<T: AzaleaWrite> AzaleaWrite for [T] {
}
impl<K: AzaleaWrite, V: AzaleaWrite> AzaleaWrite for HashMap<K, V> {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
u32::azalea_write_var(&(self.len() as u32), buf)?;
for (key, value) in self {
key.azalea_write(buf)?;
@ -93,7 +93,7 @@ impl<K: AzaleaWrite, V: AzaleaWrite> AzaleaWrite for HashMap<K, V> {
}
impl<K: AzaleaWrite, V: AzaleaWriteVar> AzaleaWriteVar for HashMap<K, V> {
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
u32::azalea_write_var(&(self.len() as u32), buf)?;
for (key, value) in self {
key.azalea_write(buf)?;
@ -105,38 +105,38 @@ impl<K: AzaleaWrite, V: AzaleaWriteVar> AzaleaWriteVar for HashMap<K, V> {
}
impl AzaleaWrite for Vec<u8> {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
(self.len() as u32).azalea_write_var(buf)?;
buf.write_all(self)
}
}
impl AzaleaWrite for String {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
write_utf_with_len(buf, self, MAX_STRING_LENGTH.into())
}
}
impl AzaleaWrite for &str {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
write_utf_with_len(buf, self, MAX_STRING_LENGTH.into())
}
}
impl AzaleaWrite for u32 {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
i32::azalea_write(&(*self as i32), buf)
}
}
impl AzaleaWriteVar for u32 {
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
i32::azalea_write_var(&(*self as i32), buf)
}
}
impl AzaleaWriteVar for i64 {
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
let mut buffer = [0];
let mut value = *self;
if value == 0 {
@ -155,25 +155,25 @@ impl AzaleaWriteVar for i64 {
}
impl AzaleaWriteVar for u64 {
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
i64::azalea_write_var(&(*self as i64), buf)
}
}
impl AzaleaWrite for u16 {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
i16::azalea_write(&(*self as i16), buf)
}
}
impl AzaleaWriteVar for u16 {
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
i32::azalea_write_var(&(*self as i32), buf)
}
}
impl<T: AzaleaWriteVar> AzaleaWriteVar for [T] {
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
u32::azalea_write_var(&(self.len() as u32), buf)?;
for i in self {
i.azalea_write_var(buf)?;
@ -182,67 +182,67 @@ impl<T: AzaleaWriteVar> AzaleaWriteVar for [T] {
}
}
impl<T: AzaleaWriteVar> AzaleaWriteVar for Vec<T> {
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
self[..].azalea_write_var(buf)
}
}
impl<T: AzaleaWriteVar> AzaleaWriteVar for Box<[T]> {
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
self[..].azalea_write_var(buf)
}
}
impl AzaleaWrite for u8 {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
WriteBytesExt::write_u8(buf, *self)
}
}
impl AzaleaWrite for i16 {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
WriteBytesExt::write_i16::<BigEndian>(buf, *self)
}
}
impl AzaleaWrite for i64 {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
WriteBytesExt::write_i64::<BigEndian>(buf, *self)
}
}
impl AzaleaWrite for u64 {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
i64::azalea_write(&(*self as i64), buf)
}
}
impl AzaleaWrite for bool {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
let byte = u8::from(*self);
byte.azalea_write(buf)
}
}
impl AzaleaWrite for i8 {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
(*self as u8).azalea_write(buf)
}
}
impl AzaleaWrite for f32 {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
WriteBytesExt::write_f32::<BigEndian>(buf, *self)
}
}
impl AzaleaWrite for f64 {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
WriteBytesExt::write_f64::<BigEndian>(buf, *self)
}
}
impl<T: AzaleaWrite> AzaleaWrite for Option<T> {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
if let Some(s) = self {
true.azalea_write(buf)?;
s.azalea_write(buf)?;
@ -254,7 +254,7 @@ impl<T: AzaleaWrite> AzaleaWrite for Option<T> {
}
impl<T: AzaleaWriteVar> AzaleaWriteVar for Option<T> {
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
if let Some(s) = self {
true.azalea_write(buf)?;
s.azalea_write_var(buf)?;
@ -267,7 +267,7 @@ impl<T: AzaleaWriteVar> AzaleaWriteVar for Option<T> {
// [T; N]
impl<T: AzaleaWrite, const N: usize> AzaleaWrite for [T; N] {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
for i in self {
i.azalea_write(buf)?;
}
@ -276,7 +276,7 @@ impl<T: AzaleaWrite, const N: usize> AzaleaWrite for [T; N] {
}
impl AzaleaWrite for simdnbt::owned::NbtTag {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
let mut data = Vec::new();
self.write(&mut data);
buf.write_all(&data)
@ -284,7 +284,7 @@ impl AzaleaWrite for simdnbt::owned::NbtTag {
}
impl AzaleaWrite for simdnbt::owned::NbtCompound {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
let mut data = Vec::new();
simdnbt::owned::NbtTag::Compound(self.clone()).write(&mut data);
buf.write_all(&data)
@ -292,7 +292,7 @@ impl AzaleaWrite for simdnbt::owned::NbtCompound {
}
impl AzaleaWrite for simdnbt::owned::Nbt {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
let mut data = Vec::new();
self.write_unnamed(&mut data);
buf.write_all(&data)
@ -303,20 +303,20 @@ impl<T> AzaleaWrite for Box<T>
where
T: AzaleaWrite,
{
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
T::azalea_write(&**self, buf)
}
}
impl<A: AzaleaWrite, B: AzaleaWrite> AzaleaWrite for (A, B) {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
self.0.azalea_write(buf)?;
self.1.azalea_write(buf)
}
}
impl<T: AzaleaWrite> AzaleaWrite for Arc<T> {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
T::azalea_write(&**self, buf)
}
}

View file

@ -1,4 +1,8 @@
use std::{fmt::Display, sync::LazyLock};
use std::{
fmt::{self, Display},
io::{self, Cursor, Write},
sync::LazyLock,
};
#[cfg(feature = "azalea-buf")]
use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError};
@ -269,13 +273,12 @@ impl<'de> Deserialize<'de> for FormattedText {
// string to with_array otherwise add the component
// to the array
let c = FormattedText::deserialize(item).map_err(de::Error::custom)?;
if let FormattedText::Text(text_component) = c {
if text_component.base.siblings.is_empty()
&& text_component.base.style.is_empty()
{
with_array.push(StringOrComponent::String(text_component.text));
continue;
}
if let FormattedText::Text(text_component) = c
&& text_component.base.siblings.is_empty()
&& text_component.base.style.is_empty()
{
with_array.push(StringOrComponent::String(text_component.text));
continue;
}
with_array.push(StringOrComponent::FormattedText(
FormattedText::deserialize(item).map_err(de::Error::custom)?,
@ -465,13 +468,12 @@ impl FormattedText {
with_array.push(StringOrComponent::String("?".to_string()));
}
} else if let Some(c) = FormattedText::from_nbt_compound(item) {
if let FormattedText::Text(text_component) = c {
if text_component.base.siblings.is_empty()
&& text_component.base.style.is_empty()
{
with_array.push(StringOrComponent::String(text_component.text));
continue;
}
if let FormattedText::Text(text_component) = c
&& text_component.base.siblings.is_empty()
&& text_component.base.style.is_empty()
{
with_array.push(StringOrComponent::String(text_component.text));
continue;
}
with_array.push(StringOrComponent::FormattedText(
FormattedText::from_nbt_compound(item)?,
@ -547,7 +549,7 @@ impl From<&simdnbt::Mutf8Str> for FormattedText {
#[cfg(feature = "azalea-buf")]
#[cfg(feature = "simdnbt")]
impl AzaleaRead for FormattedText {
fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> Result<Self, BufReadError> {
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let nbt = simdnbt::borrow::read_optional_tag(buf)?;
match nbt {
Some(nbt) => FormattedText::from_nbt_tag(nbt.as_tag()).ok_or(BufReadError::Custom(
@ -561,7 +563,7 @@ impl AzaleaRead for FormattedText {
#[cfg(feature = "azalea-buf")]
#[cfg(feature = "simdnbt")]
impl AzaleaWrite for FormattedText {
fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
let mut out = Vec::new();
simdnbt::owned::BaseNbt::write_unnamed(&(self.clone().to_compound().into()), &mut out);
buf.write_all(&out)
@ -583,7 +585,7 @@ impl From<&str> for FormattedText {
}
impl Display for FormattedText {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FormattedText::Text(c) => c.fmt(f),
FormattedText::Translatable(c) => c.fmt(f),

View file

@ -1,7 +1,7 @@
//! Contains a few ways to style numbers. At the time of writing, Minecraft only
//! uses this for rendering scoreboard objectives.
use std::io::{Cursor, Write};
use std::io::{self, Cursor, Write};
#[cfg(feature = "azalea-buf")]
use azalea_buf::{AzaleaRead, AzaleaWrite};
@ -35,7 +35,7 @@ impl AzaleaRead for NumberFormat {
#[cfg(feature = "azalea-buf")]
impl AzaleaWrite for NumberFormat {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
match self {
NumberFormat::Blank => NumberFormatKind::Blank.azalea_write(buf)?,
NumberFormat::Styled { style } => {

View file

@ -268,7 +268,7 @@ impl TextColor {
fn serialize(&self) -> String {
if let Some(name) = &self.name {
name.clone()
name.clone().to_ascii_lowercase()
} else {
self.format_value()
}
@ -595,29 +595,29 @@ impl Style {
pub fn get_html_style(&self) -> String {
let mut style = String::new();
if let Some(color) = &self.color {
style.push_str(&format!("color: {};", color.format_value()));
style.push_str(&format!("color:{};", color.format_value()));
}
if let Some(bold) = self.bold {
style.push_str(&format!(
"font-weight: {};",
"font-weight:{};",
if bold { "bold" } else { "normal" }
));
}
if let Some(italic) = self.italic {
style.push_str(&format!(
"font-style: {};",
"font-style:{};",
if italic { "italic" } else { "normal" }
));
}
if let Some(underlined) = self.underlined {
style.push_str(&format!(
"text-decoration: {};",
"text-decoration:{};",
if underlined { "underline" } else { "none" }
));
}
if let Some(strikethrough) = self.strikethrough {
style.push_str(&format!(
"text-decoration: {};",
"text-decoration:{};",
if strikethrough {
"line-through"
} else {
@ -625,10 +625,10 @@ impl Style {
}
));
}
if let Some(obfuscated) = self.obfuscated {
if obfuscated {
style.push_str("filter: blur(2px);");
}
if let Some(obfuscated) = self.obfuscated
&& obfuscated
{
style.push_str("filter:blur(2px);");
}
style

View file

@ -1,4 +1,4 @@
use std::fmt::Display;
use std::fmt::{self, Display};
use serde::{__private::ser::FlatMapSerializer, Serialize, Serializer, ser::SerializeMap};
@ -142,7 +142,7 @@ impl TextComponent {
}
impl Display for TextComponent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// this contains the final string will all the ansi escape codes
for component in FormattedText::Text(self.clone()).into_iter() {
let component_text = match &component {
@ -191,9 +191,9 @@ mod tests {
format!(
"{GREEN}Hypixel Network {END_SPAN}{RED}[1.8-1.18]<br>{END_SPAN}{BOLD_AQUA}HAPPY HOLIDAYS{END_SPAN}",
END_SPAN = "</span>",
GREEN = "<span style=\"color: #55FF55;\">",
RED = "<span style=\"color: #FF5555;\">",
BOLD_AQUA = "<span style=\"color: #55FFFF;font-weight: bold;\">",
GREEN = "<span style=\"color:#55FF55;\">",
RED = "<span style=\"color:#FF5555;\">",
BOLD_AQUA = "<span style=\"color:#55FFFF;font-weight:bold;\">",
)
);
}
@ -207,8 +207,8 @@ mod tests {
format!(
"{GREEN}&lt;b&gt;&amp;<br>{END_SPAN}{AQUA}&lt;/b&gt;{END_SPAN}",
END_SPAN = "</span>",
GREEN = "<span style=\"color: #55FF55;\">",
AQUA = "<span style=\"color: #55FFFF;\">",
GREEN = "<span style=\"color:#55FF55;\">",
AQUA = "<span style=\"color:#55FFFF;\">",
)
);
}

View file

@ -1,4 +1,4 @@
use std::fmt::{self, Display, Formatter};
use std::fmt::{self, Display};
use serde::{__private::ser::FlatMapSerializer, Serialize, Serializer, ser::SerializeMap};
#[cfg(feature = "simdnbt")]
@ -189,7 +189,7 @@ impl TranslatableComponent {
}
impl Display for TranslatableComponent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// this contains the final string will all the ansi escape codes
for component in FormattedText::Translatable(self.clone()).into_iter() {
let component_text = match &component {
@ -208,7 +208,7 @@ impl Display for TranslatableComponent {
}
impl Display for StringOrComponent {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
StringOrComponent::String(s) => write!(f, "{s}"),
StringOrComponent::FormattedText(c) => write!(f, "{c}"),

View file

@ -2,8 +2,10 @@
use std::sync::Arc;
use azalea_auth::AccessTokenResponse;
use azalea_auth::certs::{Certificates, FetchCertificatesError};
use azalea_auth::{
AccessTokenResponse,
certs::{Certificates, FetchCertificatesError},
};
use bevy_ecs::component::Component;
use parking_lot::Mutex;
use thiserror::Error;

View file

@ -1,7 +1,7 @@
use std::{
collections::HashMap,
fmt::Debug,
io, mem,
mem,
net::SocketAddr,
sync::Arc,
thread,
@ -9,7 +9,6 @@ use std::{
};
use azalea_auth::game_profile::GameProfile;
use azalea_chat::FormattedText;
use azalea_core::{
data_registry::ResolvableDataRegistry, position::Vec3, resource_location::ResourceLocation,
tick::GameTick,
@ -22,9 +21,9 @@ use azalea_entity::{
use azalea_protocol::{
ServerAddress,
common::client_information::ClientInformation,
connect::{ConnectionError, Proxy},
connect::Proxy,
packets::{
self, Packet,
Packet,
game::{self, ServerboundGamePacket},
},
resolver,
@ -46,7 +45,7 @@ use tracing::{debug, error, info, warn};
use uuid::Uuid;
use crate::{
Account, DefaultPlugins, PlayerInfo,
Account, DefaultPlugins,
attack::{self},
chunks::ChunkBatchInfo,
connection::RawConnection,
@ -54,14 +53,12 @@ use crate::{
events::Event,
interact::CurrentSequenceNumber,
inventory::Inventory,
join::{ConnectOpts, StartJoinCallback, StartJoinServerEvent},
local_player::{
GameProfileComponent, Hunger, InstanceHolder, PermissionLevel, PlayerAbilities, TabList,
},
join::{ConnectOpts, StartJoinServerEvent},
local_player::{Hunger, InstanceHolder, PermissionLevel, PlayerAbilities, TabList},
mining::{self},
movement::{LastSentLookDirection, PhysicsState},
packet::game::SendPacketEvent,
player::retroactively_add_game_profile_component,
player::{GameProfileComponent, PlayerInfo, retroactively_add_game_profile_component},
};
/// `Client` has the things that a user interacting with the library will want.
@ -89,22 +86,8 @@ pub struct Client {
pub enum JoinError {
#[error("{0}")]
Resolver(#[from] resolver::ResolverError),
#[error("{0}")]
Connection(#[from] ConnectionError),
#[error("{0}")]
ReadPacket(#[from] Box<azalea_protocol::read::ReadPacketError>),
#[error("{0}")]
Io(#[from] io::Error),
#[error("Failed to encrypt the challenge from the server for {0:?}")]
EncryptionError(packets::login::ClientboundHello),
#[error("{0}")]
SessionServer(#[from] azalea_auth::sessionserver::ClientSessionServerError),
#[error("The given address could not be parsed into a ServerAddress")]
InvalidAddress,
#[error("Couldn't refresh access token: {0}")]
Auth(#[from] azalea_auth::AuthError),
#[error("Disconnected: {reason}")]
Disconnect { reason: FormattedText },
}
pub struct StartClientOpts {
@ -168,7 +151,7 @@ impl Client {
/// # Examples
///
/// ```rust,no_run
/// use azalea_client::{Client, Account};
/// use azalea_client::{Account, Client};
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
@ -193,7 +176,7 @@ impl Client {
resolved_address,
Some(tx),
))
.await?;
.await;
Ok((client, rx))
}
@ -209,7 +192,7 @@ impl Client {
let client = Self::start_client(
StartClientOpts::new(account, address, resolved_address, Some(tx)).proxy(proxy),
)
.await?;
.await;
Ok((client, rx))
}
@ -222,25 +205,24 @@ impl Client {
connect_opts,
event_sender,
}: StartClientOpts,
) -> Result<Self, JoinError> {
) -> Self {
// send a StartJoinServerEvent
let (start_join_callback_tx, mut start_join_callback_rx) =
mpsc::unbounded_channel::<Result<Entity, JoinError>>();
mpsc::unbounded_channel::<Entity>();
ecs_lock.lock().send_event(StartJoinServerEvent {
account,
connect_opts,
event_sender,
start_join_callback_tx: Some(StartJoinCallback(start_join_callback_tx)),
start_join_callback_tx: Some(start_join_callback_tx),
});
let entity = start_join_callback_rx.recv().await.expect(
"StartJoinCallback should not be dropped before sending a message, this is a bug in Azalea",
)?;
"start_join_callback should not be dropped before sending a message, this is a bug in Azalea",
);
let client = Client::new(entity, ecs_lock.clone());
Ok(client)
Client::new(entity, ecs_lock)
}
/// Write a packet directly to the server.
@ -340,7 +322,7 @@ impl Client {
/// This will panic if the component doesn't exist on the client.
///
/// ```
/// # use azalea_client::{Client, Hunger};
/// # use azalea_client::{Client, local_player::Hunger};
/// # fn example(bot: &Client) {
/// let hunger = bot.map_component::<Hunger, _>(|h| h.food);
/// # }

View file

@ -1,10 +1,10 @@
use std::sync::Arc;
use std::{any, sync::Arc};
use azalea_world::InstanceName;
use bevy_ecs::{
component::Component,
entity::Entity,
query::QueryData,
query::{QueryFilter, ROQueryItem},
query::{QueryData, QueryFilter, ROQueryItem},
world::World,
};
use parking_lot::Mutex;
@ -29,23 +29,23 @@ impl Client {
.unwrap_or_else(|_| {
panic!(
"Our client is missing a required component {:?}",
std::any::type_name::<D>()
any::type_name::<D>()
)
})
}
/// Return a lightweight [`Entity`] for the entity that matches the given
/// predicate function.
/// Return a lightweight [`Entity`] for the first entity that matches the
/// given predicate function that is in the same [`Instance`] as the
/// client.
///
/// You can then use [`Self::entity_component`] to get components from this
/// entity.
///
/// # Example
/// Note that this will very likely change in the future.
/// ```
/// use azalea_client::{Client, GameProfileComponent};
/// use bevy_ecs::query::With;
/// use azalea_client::{Client, player::GameProfileComponent};
/// use azalea_entity::{Position, metadata::Player};
/// use bevy_ecs::query::With;
///
/// # fn example(mut bot: Client, sender_name: String) {
/// let entity = bot.entity_by::<With<Player>, (&GameProfileComponent,)>(
@ -59,11 +59,25 @@ impl Client {
/// ```
///
/// [`Entity`]: bevy_ecs::entity::Entity
/// [`Instance`]: azalea_world::Instance
pub fn entity_by<F: QueryFilter, Q: QueryData>(
&self,
predicate: impl EntityPredicate<Q, F>,
) -> Option<Entity> {
predicate.find(self.ecs.clone())
let instance_name = self.get_component::<InstanceName>()?;
predicate.find(self.ecs.clone(), &instance_name)
}
/// Same as [`Self::entity_by`] but returns a `Vec<Entity>` of all entities
/// in our instance that match the predicate.
pub fn entities_by<F: QueryFilter, Q: QueryData>(
&self,
predicate: impl EntityPredicate<Q, F>,
) -> Vec<Entity> {
let Some(instance_name) = self.get_component::<InstanceName>() else {
return vec![];
};
predicate.find_all(self.ecs.clone(), &instance_name)
}
/// Get a component from an entity. Note that this will return an owned type
@ -77,7 +91,7 @@ impl Client {
let components = q.get(&ecs, entity).unwrap_or_else(|_| {
panic!(
"Entity is missing a required component {:?}",
std::any::type_name::<Q>()
any::type_name::<Q>()
)
});
components.clone()
@ -95,35 +109,29 @@ impl Client {
}
pub trait EntityPredicate<Q: QueryData, Filter: QueryFilter> {
fn find(&self, ecs_lock: Arc<Mutex<World>>) -> Option<Entity>;
fn find(&self, ecs_lock: Arc<Mutex<World>>, instance_name: &InstanceName) -> Option<Entity>;
fn find_all(&self, ecs_lock: Arc<Mutex<World>>, instance_name: &InstanceName) -> Vec<Entity>;
}
impl<F, Q, Filter> EntityPredicate<Q, Filter> for F
impl<F, Q: QueryData, Filter: QueryFilter> EntityPredicate<Q, Filter> for F
where
F: Fn(&ROQueryItem<Q>) -> bool,
Q: QueryData,
Filter: QueryFilter,
{
fn find(&self, ecs_lock: Arc<Mutex<World>>) -> Option<Entity> {
fn find(&self, ecs_lock: Arc<Mutex<World>>, instance_name: &InstanceName) -> Option<Entity> {
let mut ecs = ecs_lock.lock();
let mut query = ecs.query_filtered::<(Entity, Q), Filter>();
query.iter(&ecs).find(|(_, q)| (self)(q)).map(|(e, _)| e)
let mut query = ecs.query_filtered::<(Entity, &InstanceName, Q), Filter>();
query
.iter(&ecs)
.find(|(_, e_instance_name, q)| *e_instance_name == instance_name && (self)(q))
.map(|(e, _, _)| e)
}
fn find_all(&self, ecs_lock: Arc<Mutex<World>>, instance_name: &InstanceName) -> Vec<Entity> {
let mut ecs = ecs_lock.lock();
let mut query = ecs.query_filtered::<(Entity, &InstanceName, Q), Filter>();
query
.iter(&ecs)
.filter(|(_, e_instance_name, q)| *e_instance_name == instance_name && (self)(q))
.map(|(e, _, _)| e)
.collect::<Vec<_>>()
}
}
// impl<'a, F, Q1, Q2> EntityPredicate<'a, (Q1, Q2)> for F
// where
// F: Fn(&<Q1 as WorldQuery>::Item<'_>, &<Q2 as WorldQuery>::Item<'_>) ->
// bool, Q1: QueryFilter,
// Q2: QueryFilter,
// {
// fn find(&self, ecs: &mut Ecs) -> Option<Entity> {
// // (self)(query)
// let mut query = ecs.query_filtered::<(Entity, Q1, Q2), ()>();
// let entity = query
// .iter(ecs)
// .find(|(_, q1, q2)| (self)(q1, q2))
// .map(|(e, _, _)| e);
// entity
// }
// }

View file

@ -11,9 +11,9 @@
mod account;
mod client;
mod entity_query;
mod local_player;
pub mod local_player;
pub mod ping;
mod player;
pub mod player;
mod plugins;
#[doc(hidden)]
@ -29,9 +29,7 @@ pub use client::{
StartClientOpts, start_ecs_runner,
};
pub use events::Event;
pub use local_player::{GameProfileComponent, Hunger, InstanceHolder, TabList};
pub use movement::{
PhysicsState, SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection,
};
pub use player::PlayerInfo;
pub use plugins::*;

View file

@ -1,6 +1,9 @@
use std::{collections::HashMap, io, sync::Arc};
use std::{
collections::HashMap,
error, io,
sync::{Arc, PoisonError},
};
use azalea_auth::game_profile::GameProfile;
use azalea_core::game_type::GameMode;
use azalea_protocol::packets::game::c_player_abilities::ClientboundPlayerAbilities;
use azalea_world::{Instance, PartialInstance};
@ -12,7 +15,7 @@ use tokio::sync::mpsc;
use tracing::error;
use uuid::Uuid;
use crate::{ClientInformation, PlayerInfo, events::Event as AzaleaEvent};
use crate::{ClientInformation, events::Event as AzaleaEvent, player::PlayerInfo};
/// A component that keeps strong references to our [`PartialInstance`] and
/// [`Instance`] for local players.
@ -36,14 +39,6 @@ pub struct InstanceHolder {
pub instance: Arc<RwLock<Instance>>,
}
/// A component only present in players that contains the [`GameProfile`] (which
/// you can use to get a player's name).
///
/// Note that it's possible for this to be missing in a player if the server
/// never sent the player info for them (though this is uncommon).
#[derive(Component, Clone, Debug, Deref, DerefMut)]
pub struct GameProfileComponent(pub GameProfile);
/// The gamemode of a local player. For a non-local player, you can look up the
/// player in the [`TabList`].
#[derive(Component, Clone, Debug, Copy)]
@ -51,6 +46,14 @@ pub struct LocalGameMode {
pub current: GameMode,
pub previous: Option<GameMode>,
}
impl From<GameMode> for LocalGameMode {
fn from(current: GameMode) -> Self {
LocalGameMode {
current,
previous: None,
}
}
}
/// A component that contains the abilities the player has, like flying
/// or instantly breaking blocks. This is only present on local players.
@ -88,7 +91,7 @@ pub struct PermissionLevel(pub u8);
/// tab list.
///
/// ```
/// # use azalea_client::TabList;
/// # use azalea_client::local_player::TabList;
/// # fn example(client: &azalea_client::Client) {
/// let tab_list = client.component::<TabList>();
/// println!("Online players:");
@ -169,13 +172,13 @@ pub enum HandlePacketError {
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Other(#[from] Box<dyn std::error::Error + Send + Sync>),
Other(#[from] Box<dyn error::Error + Send + Sync>),
#[error("{0}")]
Send(#[from] mpsc::error::SendError<AzaleaEvent>),
}
impl<T> From<std::sync::PoisonError<T>> for HandlePacketError {
fn from(e: std::sync::PoisonError<T>) -> Self {
impl<T> From<PoisonError<T>> for HandlePacketError {
fn from(e: PoisonError<T>) -> Self {
HandlePacketError::Poison(e.to_string())
}
}

View file

@ -3,12 +3,14 @@ use azalea_chat::FormattedText;
use azalea_core::game_type::GameMode;
use azalea_entity::indexing::EntityUuidIndex;
use bevy_ecs::{
component::Component,
event::EventReader,
system::{Commands, Res},
};
use derive_more::{Deref, DerefMut};
use uuid::Uuid;
use crate::{GameProfileComponent, packet::game::AddPlayerEvent};
use crate::packet::game::AddPlayerEvent;
/// A player in the tab list.
#[derive(Debug, Clone)]
@ -29,6 +31,14 @@ pub struct PlayerInfo {
pub display_name: Option<Box<FormattedText>>,
}
/// A component only present in players that contains the [`GameProfile`] (which
/// you can use to get a player's name).
///
/// Note that it's possible for this to be missing in a player if the server
/// never sent the player info for them (though this is uncommon).
#[derive(Component, Clone, Debug, Deref, DerefMut)]
pub struct GameProfileComponent(pub GameProfile);
/// Add a [`GameProfileComponent`] when an [`AddPlayerEvent`] is received.
/// Usually the `GameProfileComponent` will be added from the
/// `ClientboundGamePacket::AddPlayer` handler though.

View file

@ -70,11 +70,17 @@ fn get_delay(
auto_reconnect_delay_query: Query<&AutoReconnectDelay>,
entity: Entity,
) -> Option<Duration> {
if let Ok(c) = auto_reconnect_delay_query.get(entity) {
let delay = if let Ok(c) = auto_reconnect_delay_query.get(entity) {
Some(c.delay)
} else {
auto_reconnect_delay_res.as_ref().map(|r| r.delay)
};
if delay == Some(Duration::MAX) {
// if the duration is set to max, treat that as autoreconnect being disabled
return None;
}
delay
}
pub fn rejoin_after_delay(

View file

@ -124,9 +124,9 @@ impl ChatPacket {
}))
}
/// Whether this message was sent with /msg (or aliases). It works by
/// checking the translation key, so it won't work on servers that use their
/// own whisper system.
/// Whether this message is an incoming whisper message (i.e. someone else
/// dm'd the bot with /msg). It works by checking the translation key, so it
/// won't work on servers that use their own whisper system.
pub fn is_whisper(&self) -> bool {
match self.message() {
FormattedText::Text(_) => false,

View file

@ -102,10 +102,10 @@ pub fn request_certs_if_needed(
>,
) {
for (entity, account, only_refresh_certs_after, chat_signing_session) in query.iter_mut() {
if let Some(only_refresh_certs_after) = only_refresh_certs_after {
if only_refresh_certs_after.refresh_at > Instant::now() {
continue;
}
if let Some(only_refresh_certs_after) = only_refresh_certs_after
&& only_refresh_certs_after.refresh_at > Instant::now()
{
continue;
}
let certs = account.certs.lock();
@ -124,20 +124,18 @@ pub fn request_certs_if_needed(
};
drop(certs);
if should_refresh {
if let Some(access_token) = &account.access_token {
let task_pool = IoTaskPool::get();
if should_refresh && let Some(access_token) = &account.access_token {
let task_pool = IoTaskPool::get();
let access_token = access_token.lock().clone();
debug!("Started task to fetch certs");
let task = task_pool.spawn(async_compat::Compat::new(async move {
azalea_auth::certs::fetch_certificates(&access_token).await
}));
commands
.entity(entity)
.insert(RequestCertsTask(task))
.remove::<OnlyRefreshCertsAfter>();
}
let access_token = access_token.lock().clone();
debug!("Started task to fetch certs");
let task = task_pool.spawn(async_compat::Compat::new(async move {
azalea_auth::certs::fetch_certificates(&access_token).await
}));
commands
.entity(entity)
.insert(RequestCertsTask(task))
.remove::<OnlyRefreshCertsAfter>();
}
}
}

View file

@ -17,7 +17,7 @@ use bevy_ecs::prelude::*;
use tracing::{error, trace};
use crate::{
InstanceHolder, interact::handle_start_use_item_queued, inventory::InventorySet,
interact::handle_start_use_item_queued, inventory::InventorySet, local_player::InstanceHolder,
packet::game::SendPacketEvent, respawn::perform_respawn,
};
@ -84,14 +84,12 @@ pub fn handle_receive_chunk_events(
let shared_chunk = instance.chunks.get(&pos);
let this_client_has_chunk = partial_instance.chunks.limited_get(&pos).is_some();
if !this_client_has_chunk {
if let Some(shared_chunk) = shared_chunk {
trace!("Skipping parsing chunk {pos:?} because we already know about it");
partial_instance
.chunks
.limited_set(&pos, Some(shared_chunk));
continue;
}
if !this_client_has_chunk && let Some(shared_chunk) = shared_chunk {
trace!("Skipping parsing chunk {pos:?} because we already know about it");
partial_instance
.chunks
.limited_set(&pos, Some(shared_chunk));
continue;
}
let heightmaps = &event.packet.chunk_data.heightmaps;

View file

@ -169,9 +169,9 @@ pub struct RawConnection {
///
/// To check if we haven't disconnected from the server, use
/// [`Self::is_alive`].
network: Option<NetworkConnection>,
pub(crate) network: Option<NetworkConnection>,
pub state: ConnectionProtocol,
is_alive: bool,
pub(crate) is_alive: bool,
/// This exists for internal testing purposes and probably shouldn't be used
/// for normal bots. It's basically a way to make our client think it

View file

@ -2,13 +2,17 @@
use azalea_chat::FormattedText;
use azalea_entity::{EntityBundle, InLoadedChunk, LocalEntity, metadata::PlayerMetadataBundle};
use azalea_world::MinecraftEntityId;
use bevy_app::{App, Plugin, PostUpdate};
use bevy_ecs::prelude::*;
use derive_more::Deref;
use tracing::info;
use super::login::IsAuthenticated;
use crate::{InstanceHolder, chat_signing, client::JoinedClientBundle, connection::RawConnection};
use crate::{
chat_signing, client::JoinedClientBundle, connection::RawConnection,
local_player::InstanceHolder,
};
pub struct DisconnectPlugin;
impl Plugin for DisconnectPlugin {
@ -53,6 +57,7 @@ pub struct DisconnectEvent {
pub struct RemoveOnDisconnectBundle {
pub joined_client: JoinedClientBundle,
pub entity: EntityBundle,
pub minecraft_entity_id: MinecraftEntityId,
pub instance_holder: InstanceHolder,
pub player_metadata: PlayerMetadataBundle,
pub in_loaded_chunk: InLoadedChunk,

View file

@ -14,12 +14,12 @@ use derive_more::{Deref, DerefMut};
use tokio::sync::mpsc;
use crate::{
PlayerInfo,
chat::{ChatPacket, ChatReceivedEvent},
disconnect::DisconnectEvent,
packet::game::{
AddPlayerEvent, DeathEvent, KeepAliveEvent, RemovePlayerEvent, UpdatePlayerEvent,
},
player::PlayerInfo,
};
// (for contributors):

View file

@ -24,7 +24,7 @@ use bevy_ecs::prelude::*;
use derive_more::{Deref, DerefMut};
use tracing::warn;
use super::mining::{Mining, MiningSet};
use super::mining::Mining;
use crate::{
Client,
attack::handle_attack_event,
@ -58,12 +58,7 @@ impl Plugin for InteractPlugin {
.after(MoveEventsSet),
),
)
.add_systems(
GameTick,
handle_start_use_item_queued
.after(MiningSet)
.before(PhysicsSet),
)
.add_systems(GameTick, handle_start_use_item_queued.before(PhysicsSet))
.add_observer(handle_swing_arm_trigger);
}
}
@ -131,7 +126,7 @@ pub struct HitResultComponent(HitResult);
pub struct StartUseItemEvent {
pub entity: Entity,
pub hand: InteractionHand,
/// See [`QueuedStartUseItem::force_block`].
/// See [`StartUseItemQueued::force_block`].
pub force_block: Option<BlockPos>,
}
pub fn handle_start_use_item_event(

View file

@ -170,17 +170,17 @@ impl Inventory {
}
if let QuickCraftStatus::Add { slot } = quick_craft.status {
let slot_item = self.menu().slot(slot as usize);
if let Some(slot_item) = slot_item {
if let ItemStack::Present(carried) = &self.carried {
// minecraft also checks slot.may_place(carried) and
// menu.can_drag_to(slot)
// but they always return true so they're not relevant for us
if can_item_quick_replace(slot_item, &self.carried, true)
&& (self.quick_craft_kind == QuickCraftKind::Right
|| carried.count as usize > self.quick_craft_slots.len())
{
self.quick_craft_slots.insert(slot);
}
if let Some(slot_item) = slot_item
&& let ItemStack::Present(carried) = &self.carried
{
// minecraft also checks slot.may_place(carried) and
// menu.can_drag_to(slot)
// but they always return true so they're not relevant for us
if can_item_quick_replace(slot_item, &self.carried, true)
&& (self.quick_craft_kind == QuickCraftKind::Right
|| carried.count as usize > self.quick_craft_slots.len())
{
self.quick_craft_slots.insert(slot);
}
}
return;
@ -468,26 +468,23 @@ impl Inventory {
for i in iterator {
if target_slot_item.count < target_slot_item.kind.max_stack_size() {
let checking_slot = self.menu().slot(i).unwrap();
if let ItemStack::Present(checking_item) = checking_slot {
if can_item_quick_replace(checking_slot, &target_slot, true)
&& self.menu().may_pickup(i)
&& (round != 0
|| checking_item.count
!= checking_item.kind.max_stack_size())
{
// get the checking_slot and checking_item again but mutable
let checking_slot = self.menu_mut().slot_mut(i).unwrap();
if let ItemStack::Present(checking_item) = checking_slot
&& can_item_quick_replace(checking_slot, &target_slot, true)
&& self.menu().may_pickup(i)
&& (round != 0
|| checking_item.count != checking_item.kind.max_stack_size())
{
// get the checking_slot and checking_item again but mutable
let checking_slot = self.menu_mut().slot_mut(i).unwrap();
let taken_item =
checking_slot.split(checking_slot.count() as u32);
let taken_item = checking_slot.split(checking_slot.count() as u32);
// now extend the carried item
let target_slot = &mut self.carried;
let ItemStack::Present(target_slot_item) = target_slot else {
unreachable!("target slot is not empty but is not present");
};
target_slot_item.count += taken_item.count();
}
// now extend the carried item
let target_slot = &mut self.carried;
let ItemStack::Present(target_slot_item) = target_slot else {
unreachable!("target slot is not empty but is not present");
};
target_slot_item.count += taken_item.count();
}
}
}

View file

@ -1,4 +1,4 @@
use std::{io, net::SocketAddr, sync::Arc};
use std::{net::SocketAddr, sync::Arc};
use azalea_entity::{LocalEntity, indexing::EntityUuidIndex};
use azalea_protocol::{
@ -20,7 +20,7 @@ use tracing::{debug, warn};
use super::events::LocalPlayerEvents;
use crate::{
Account, JoinError, LocalPlayerBundle,
Account, LocalPlayerBundle,
connection::RawConnection,
packet::login::{InLoginState, SendLoginPacketEvent},
};
@ -36,7 +36,6 @@ impl Plugin for JoinPlugin {
(
handle_start_join_server_event.before(super::login::poll_auth_task),
poll_create_connection_task,
handle_connection_failed_events,
)
.chain(),
);
@ -53,7 +52,8 @@ pub struct StartJoinServerEvent {
pub connect_opts: ConnectOpts,
pub event_sender: Option<mpsc::UnboundedSender<crate::Event>>,
pub start_join_callback_tx: Option<StartJoinCallback>,
// this is mpsc instead of oneshot so it can be cloned (since it's sent in an event)
pub start_join_callback_tx: Option<mpsc::UnboundedSender<Entity>>,
}
/// Options for how the connection to the server will be made. These are
@ -79,11 +79,6 @@ pub struct ConnectionFailedEvent {
pub error: ConnectionError,
}
// this is mpsc instead of oneshot so it can be cloned (since it's sent in an
// event)
#[derive(Component, Debug, Clone)]
pub struct StartJoinCallback(pub mpsc::UnboundedSender<Result<Entity, JoinError>>);
pub fn handle_start_join_server_event(
mut commands: Commands,
mut events: EventReader<StartJoinServerEvent>,
@ -96,20 +91,20 @@ pub fn handle_start_join_server_event(
debug!("Reusing entity {entity:?} for client");
// check if it's already connected
if let Ok(conn) = connection_query.get(entity) {
if conn.is_alive() {
if let Some(start_join_callback_tx) = &event.start_join_callback_tx {
warn!(
"Received StartJoinServerEvent for {entity:?} but it's already connected. Ignoring the event but replying with Ok."
);
let _ = start_join_callback_tx.0.send(Ok(entity));
} else {
warn!(
"Received StartJoinServerEvent for {entity:?} but it's already connected. Ignoring the event."
);
}
return;
if let Ok(conn) = connection_query.get(entity)
&& conn.is_alive()
{
if let Some(start_join_callback_tx) = &event.start_join_callback_tx {
warn!(
"Received StartJoinServerEvent for {entity:?} but it's already connected. Ignoring the event but replying with Ok."
);
let _ = start_join_callback_tx.send(entity);
} else {
warn!(
"Received StartJoinServerEvent for {entity:?} but it's already connected. Ignoring the event."
);
}
return;
}
entity
@ -121,6 +116,10 @@ pub fn handle_start_join_server_event(
entity
};
if let Some(start_join_callback) = &event.start_join_callback_tx {
let _ = start_join_callback.send(entity);
}
let mut entity_mut = commands.entity(entity);
entity_mut.insert((
@ -141,9 +140,6 @@ pub fn handle_start_join_server_event(
// handle receiving packets
entity_mut.insert(LocalPlayerEvents(event_sender.clone()));
}
if let Some(start_join_callback) = &event.start_join_callback_tx {
entity_mut.insert(start_join_callback.clone());
}
let task_pool = IoTaskPool::get();
let connect_opts = event.connect_opts.clone();
@ -184,15 +180,10 @@ pub struct CreateConnectionTask(pub Task<Result<LoginConn, ConnectionError>>);
pub fn poll_create_connection_task(
mut commands: Commands,
mut query: Query<(
Entity,
&mut CreateConnectionTask,
&Account,
Option<&StartJoinCallback>,
)>,
mut query: Query<(Entity, &mut CreateConnectionTask, &Account)>,
mut connection_failed_events: EventWriter<ConnectionFailedEvent>,
) {
for (entity, mut task, account, mut start_join_callback) in query.iter_mut() {
for (entity, mut task, account) in query.iter_mut() {
if let Some(poll_res) = future::block_on(future::poll_once(&mut task.0)) {
let mut entity_mut = commands.entity(entity);
entity_mut.remove::<CreateConnectionTask>();
@ -238,29 +229,6 @@ pub fn poll_create_connection_task(
profile_id: account.uuid_or_offline(),
},
));
if let Some(cb) = start_join_callback.take() {
let _ = cb.0.send(Ok(entity));
}
}
}
}
pub fn handle_connection_failed_events(
mut events: EventReader<ConnectionFailedEvent>,
query: Query<&StartJoinCallback>,
) {
for event in events.read() {
let Ok(start_join_callback) = query.get(event.entity) else {
// the StartJoinCallback isn't required to be present, so this is fine
continue;
};
// io::Error isn't clonable, so we create a new one based on the `kind` and
// `to_string`,
let ConnectionError::Io(err) = &event.error;
let cloned_err = ConnectionError::Io(io::Error::new(err.kind(), err.to_string()));
let _ = start_join_callback.0.send(Err(cloned_err.into()));
}
}

View file

@ -5,13 +5,14 @@ use azalea_protocol::packets::login::{
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_tasks::{IoTaskPool, Task, futures_lite::future};
use thiserror::Error;
use tracing::{debug, error, trace};
use super::{
connection::RawConnection,
packet::login::{ReceiveCustomQueryEvent, ReceiveHelloEvent, SendLoginPacketEvent},
};
use crate::{Account, JoinError};
use crate::Account;
/// Some systems that run during the `login` state.
pub struct LoginPlugin;
@ -73,14 +74,24 @@ pub fn poll_auth_task(
type PrivateKey = [u8; 16];
#[derive(Component)]
pub struct AuthTask(Task<Result<(ServerboundKey, PrivateKey), JoinError>>);
pub struct AuthTask(Task<Result<(ServerboundKey, PrivateKey), AuthWithAccountError>>);
#[derive(Debug, Error)]
pub enum AuthWithAccountError {
#[error("Failed to encrypt the challenge from the server for {0:?}")]
Encryption(ClientboundHello),
#[error("{0}")]
SessionServer(#[from] ClientSessionServerError),
#[error("Couldn't refresh access token: {0}")]
Auth(#[from] azalea_auth::AuthError),
}
pub async fn auth_with_account(
account: Account,
packet: ClientboundHello,
) -> Result<(ServerboundKey, PrivateKey), JoinError> {
) -> Result<(ServerboundKey, PrivateKey), AuthWithAccountError> {
let Ok(encrypt_res) = azalea_crypto::encrypt(&packet.public_key, &packet.challenge) else {
return Err(JoinError::EncryptionError(packet));
return Err(AuthWithAccountError::Encryption(packet));
};
let key_packet = ServerboundKey {
key_bytes: encrypt_res.encrypted_public_key,

View file

@ -8,15 +8,16 @@ use azalea_world::{InstanceContainer, InstanceName};
use bevy_app::{App, Plugin, Update};
use bevy_ecs::prelude::*;
use derive_more::{Deref, DerefMut};
use tracing::{info, trace};
use crate::{
Client, InstanceHolder,
Client,
interact::{
CurrentSequenceNumber, HitResultComponent, SwingArmEvent, can_use_game_master_blocks,
check_is_interaction_restricted,
},
inventory::{Inventory, InventorySet},
local_player::{LocalGameMode, PermissionLevel, PlayerAbilities},
local_player::{InstanceHolder, LocalGameMode, PermissionLevel, PlayerAbilities},
movement::MoveEventsSet,
packet::game::SendPacketEvent,
};
@ -39,14 +40,15 @@ impl Plugin for MiningPlugin {
handle_mining_queued,
)
.chain()
.before(PhysicsSet)
.after(PhysicsSet)
.after(super::movement::send_position)
.after(super::attack::handle_attack_queued)
.in_set(MiningSet),
)
.add_systems(
Update,
(
handle_start_mining_block_event,
handle_finish_mining_block_event,
handle_stop_mining_block_event,
)
.chain()
@ -59,7 +61,8 @@ impl Plugin for MiningPlugin {
.after(crate::attack::handle_attack_event)
.after(crate::interact::handle_start_use_item_queued)
.before(crate::interact::handle_swing_arm_event),
);
)
.add_observer(handle_finish_mining_block_observer);
}
}
@ -148,17 +151,19 @@ fn handle_auto_mine(
/// Information about the block we're currently mining. This is only present if
/// we're currently mining a block.
#[derive(Component)]
#[derive(Component, Debug, Clone)]
pub struct Mining {
pub pos: BlockPos,
pub dir: Direction,
/// See [`MiningQueued::force`].
pub force: bool,
}
/// Start mining the block at the given position.
///
/// If we're looking at the block then the correct direction will be used,
/// otherwise it'll be [`Direction::Down`].
#[derive(Event)]
#[derive(Event, Debug)]
pub struct StartMiningBlockEvent {
pub entity: Entity,
pub position: BlockPos,
@ -169,33 +174,37 @@ fn handle_start_mining_block_event(
mut query: Query<&HitResultComponent>,
) {
for event in events.read() {
trace!("{event:?}");
let hit_result = query.get_mut(event.entity).unwrap();
let direction = if let Some(block_hit_result) = hit_result.as_block_hit_result_if_not_miss()
let (direction, force) = if let Some(block_hit_result) =
hit_result.as_block_hit_result_if_not_miss()
&& block_hit_result.block_pos == event.position
{
// we're looking at the block
block_hit_result.direction
(block_hit_result.direction, false)
} else {
// we're not looking at the block, arbitrary direction
Direction::Down
(Direction::Down, true)
};
commands.entity(event.entity).insert(MiningQueued {
position: event.position,
direction,
force,
});
}
}
/// Present on entities when they're going to start mining a block next tick.
#[derive(Component)]
#[derive(Component, Debug, Clone)]
pub struct MiningQueued {
pub position: BlockPos,
pub direction: Direction,
/// Whether we should mine the block regardless of whether it's reachable.
pub force: bool,
}
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn handle_mining_queued(
mut commands: Commands,
mut finish_mining_events: EventWriter<FinishMiningBlockEvent>,
mut attack_block_events: EventWriter<AttackBlockEvent>,
mut mine_block_progress_events: EventWriter<MineBlockProgressEvent>,
query: Query<(
@ -232,6 +241,7 @@ fn handle_mining_queued(
mut current_mining_pos,
) in query
{
info!("mining_queued: {mining_queued:?}");
commands.entity(entity).remove::<MiningQueued>();
let instance = instance_holder.instance.read();
@ -247,10 +257,12 @@ fn handle_mining_queued(
// is outside of the worldborder
if game_mode.current == GameMode::Creative {
finish_mining_events.write(FinishMiningBlockEvent {
commands.trigger_targets(
FinishMiningBlockEvent {
position: mining_queued.position,
},
entity,
position: mining_queued.position,
});
);
**mine_delay = 5;
} else if mining.is_none()
|| !is_same_mining_target(
@ -303,15 +315,20 @@ fn handle_mining_queued(
) >= 1.
{
// block was broken instantly
finish_mining_events.write(FinishMiningBlockEvent {
commands.trigger_targets(
FinishMiningBlockEvent {
position: mining_queued.position,
},
entity,
position: mining_queued.position,
});
);
} else {
commands.entity(entity).insert(Mining {
let mining = Mining {
pos: mining_queued.position,
dir: mining_queued.direction,
});
force: mining_queued.force,
};
trace!("inserting mining component {mining:?} for entity {entity:?}");
commands.entity(entity).insert(mining);
**current_mining_pos = Some(mining_queued.position);
**current_mining_item = held_item;
**mine_progress = 0.;
@ -332,6 +349,7 @@ fn handle_mining_queued(
sequence: sequence_number.get_and_increment(),
},
));
// vanilla really does send two swing arm packets
commands.trigger(SwingArmEvent { entity });
commands.trigger(SwingArmEvent { entity });
}
@ -366,7 +384,7 @@ fn is_same_mining_target(
}
/// A component bundle for players that can mine blocks.
#[derive(Bundle, Default)]
#[derive(Bundle, Default, Clone)]
pub struct MineBundle {
pub delay: MineDelay,
pub progress: MineProgress,
@ -376,12 +394,12 @@ pub struct MineBundle {
}
/// A component that counts down until we start mining the next block.
#[derive(Component, Debug, Default, Deref, DerefMut)]
#[derive(Component, Debug, Default, Deref, DerefMut, Clone)]
pub struct MineDelay(pub u32);
/// A component that stores the progress of the current mining operation. This
/// is a value between 0 and 1.
#[derive(Component, Debug, Default, Deref, DerefMut)]
#[derive(Component, Debug, Default, Deref, DerefMut, Clone)]
pub struct MineProgress(pub f32);
impl MineProgress {
@ -409,15 +427,14 @@ pub struct MineBlockPos(pub Option<BlockPos>);
#[derive(Component, Clone, Debug, Default, Deref, DerefMut)]
pub struct MineItem(pub ItemStack);
/// Sent when we completed mining a block.
/// A trigger that's sent when we completed mining a block.
#[derive(Event)]
pub struct FinishMiningBlockEvent {
pub entity: Entity,
pub position: BlockPos,
}
pub fn handle_finish_mining_block_event(
mut events: EventReader<FinishMiningBlockEvent>,
pub fn handle_finish_mining_block_observer(
trigger: Trigger<FinishMiningBlockEvent>,
mut query: Query<(
&InstanceName,
&LocalGameMode,
@ -428,53 +445,48 @@ pub fn handle_finish_mining_block_event(
)>,
instances: Res<InstanceContainer>,
) {
for event in events.read() {
let (instance_name, game_mode, inventory, abilities, permission_level, _sequence_number) =
query.get_mut(event.entity).unwrap();
let instance_lock = instances.get(instance_name).unwrap();
let instance = instance_lock.read();
if check_is_interaction_restricted(
&instance,
&event.position,
&game_mode.current,
inventory,
) {
continue;
}
let event = trigger.event();
if game_mode.current == GameMode::Creative {
let held_item = inventory.held_item().kind();
if matches!(
held_item,
azalea_registry::Item::Trident | azalea_registry::Item::DebugStick
) || azalea_registry::tags::items::SWORDS.contains(&held_item)
{
continue;
}
}
let Some(block_state) = instance.get_block_state(&event.position) else {
continue;
};
let registry_block = Box::<dyn Block>::from(block_state).as_registry_block();
if !can_use_game_master_blocks(abilities, permission_level)
&& matches!(
registry_block,
azalea_registry::Block::CommandBlock | azalea_registry::Block::StructureBlock
)
{
continue;
}
if block_state == BlockState::AIR {
continue;
}
// when we break a waterlogged block we want to keep the water there
let fluid_state = FluidState::from(block_state);
let block_state_for_fluid = BlockState::from(fluid_state);
instance.set_block_state(&event.position, block_state_for_fluid);
let (instance_name, game_mode, inventory, abilities, permission_level, _sequence_number) =
query.get_mut(trigger.target()).unwrap();
let instance_lock = instances.get(instance_name).unwrap();
let instance = instance_lock.read();
if check_is_interaction_restricted(&instance, &event.position, &game_mode.current, inventory) {
return;
}
if game_mode.current == GameMode::Creative {
let held_item = inventory.held_item().kind();
if matches!(
held_item,
azalea_registry::Item::Trident | azalea_registry::Item::DebugStick
) || azalea_registry::tags::items::SWORDS.contains(&held_item)
{
return;
}
}
let Some(block_state) = instance.get_block_state(&event.position) else {
return;
};
let registry_block = Box::<dyn Block>::from(block_state).as_registry_block();
if !can_use_game_master_blocks(abilities, permission_level)
&& matches!(
registry_block,
azalea_registry::Block::CommandBlock | azalea_registry::Block::StructureBlock
)
{
return;
}
if block_state == BlockState::AIR {
return;
}
// when we break a waterlogged block we want to keep the water there
let fluid_state = FluidState::from(block_state);
let block_state_for_fluid = BlockState::from(fluid_state);
instance.set_block_state(&event.position, block_state_for_fluid);
}
/// Abort mining a block.
@ -531,7 +543,6 @@ pub fn continue_mining_block(
)>,
mut commands: Commands,
mut mine_block_progress_events: EventWriter<MineBlockProgressEvent>,
mut finish_mining_events: EventWriter<FinishMiningBlockEvent>,
instances: Res<InstanceContainer>,
) {
for (
@ -558,10 +569,12 @@ pub fn continue_mining_block(
if game_mode.current == GameMode::Creative {
// TODO: worldborder check
**mine_delay = 5;
finish_mining_events.write(FinishMiningBlockEvent {
commands.trigger_targets(
FinishMiningBlockEvent {
position: mining.pos,
},
entity,
position: mining.pos,
});
);
commands.trigger(SendPacketEvent::new(
entity,
ServerboundPlayerAction {
@ -572,18 +585,20 @@ pub fn continue_mining_block(
},
));
commands.trigger(SwingArmEvent { entity });
} else if is_same_mining_target(
mining.pos,
inventory,
current_mining_pos,
current_mining_item,
) {
println!("continue mining block at {:?}", mining.pos);
} else if mining.force
|| is_same_mining_target(
mining.pos,
inventory,
current_mining_pos,
current_mining_item,
)
{
trace!("continue mining block at {:?}", mining.pos);
let instance_lock = instances.get(instance_name).unwrap();
let instance = instance_lock.read();
let target_block_state = instance.get_block_state(&mining.pos).unwrap_or_default();
println!("target_block_state: {target_block_state:?}");
trace!("target_block_state: {target_block_state:?}");
if target_block_state.is_air() {
commands.entity(entity).remove::<Mining>();
@ -604,12 +619,16 @@ pub fn continue_mining_block(
**mine_ticks += 1.;
if **mine_progress >= 1. {
commands.entity(entity).remove::<Mining>();
println!("finished mining block at {:?}", mining.pos);
finish_mining_events.write(FinishMiningBlockEvent {
// MiningQueued is removed in case we were doing an infinite loop that
// repeatedly inserts MiningQueued
commands.entity(entity).remove::<(Mining, MiningQueued)>();
trace!("finished mining block at {:?}", mining.pos);
commands.trigger_targets(
FinishMiningBlockEvent {
position: mining.pos,
},
entity,
position: mining.pos,
});
);
commands.trigger(SendPacketEvent::new(
entity,
ServerboundPlayerAction {
@ -631,10 +650,11 @@ pub fn continue_mining_block(
});
commands.trigger(SwingArmEvent { entity });
} else {
println!("switching mining target to {:?}", mining.pos);
trace!("switching mining target to {:?}", mining.pos);
commands.entity(entity).insert(MiningQueued {
position: mining.pos,
direction: mining.dir,
force: false,
});
}
}

View file

@ -1,14 +1,15 @@
use std::backtrace::Backtrace;
use std::{backtrace::Backtrace, io};
use azalea_core::position::Vec3;
use azalea_core::tick::GameTick;
use azalea_entity::{Attributes, Jumping, metadata::Sprinting};
use azalea_entity::{InLoadedChunk, LastSentPosition, LookDirection, Physics, Position};
use azalea_core::{position::Vec3, tick::GameTick};
use azalea_entity::{
Attributes, InLoadedChunk, Jumping, LastSentPosition, LookDirection, Physics, Position,
metadata::Sprinting,
};
use azalea_physics::{PhysicsSet, ai_step};
use azalea_protocol::packets::game::{ServerboundPlayerCommand, ServerboundPlayerInput};
use azalea_protocol::packets::{
Packet,
game::{
ServerboundPlayerCommand, ServerboundPlayerInput,
s_move_player_pos::ServerboundMovePlayerPos,
s_move_player_pos_rot::ServerboundMovePlayerPosRot,
s_move_player_rot::ServerboundMovePlayerRot,
@ -20,15 +21,14 @@ use bevy_app::{App, Plugin, Update};
use bevy_ecs::prelude::*;
use thiserror::Error;
use crate::client::Client;
use crate::packet::game::SendPacketEvent;
use crate::{client::Client, packet::game::SendPacketEvent};
#[derive(Error, Debug)]
pub enum MovePlayerError {
#[error("Player is not in world")]
PlayerNotInWorld(Backtrace),
#[error("{0}")]
Io(#[from] std::io::Error),
Io(#[from] io::Error),
}
impl From<MoveEntityError> for MovePlayerError {

View file

@ -3,21 +3,23 @@ mod events;
use std::io::Cursor;
use azalea_entity::LocalEntity;
use azalea_protocol::packets::ConnectionProtocol;
use azalea_protocol::packets::config::*;
use azalea_protocol::read::ReadPacketError;
use azalea_protocol::read::deserialize_packet;
use azalea_protocol::{
packets::{ConnectionProtocol, config::*},
read::{ReadPacketError, deserialize_packet},
};
use bevy_ecs::prelude::*;
pub use events::*;
use tracing::{debug, warn};
use super::as_system;
use crate::client::InConfigState;
use crate::connection::RawConnection;
use crate::disconnect::DisconnectEvent;
use crate::packet::game::KeepAliveEvent;
use crate::packet::game::ResourcePackEvent;
use crate::{InstanceHolder, declare_packet_handlers};
use crate::{
client::InConfigState,
connection::RawConnection,
declare_packet_handlers,
disconnect::DisconnectEvent,
local_player::InstanceHolder,
packet::game::{KeepAliveEvent, ResourcePackEvent},
};
pub fn process_raw_packet(
ecs: &mut World,

View file

@ -12,7 +12,7 @@ use parking_lot::RwLock;
use tracing::{error, trace};
use uuid::Uuid;
use crate::{PlayerInfo, client::InGameState, connection::RawConnection};
use crate::{client::InGameState, connection::RawConnection, player::PlayerInfo};
/// An event that's sent when we receive a packet.
/// ```
@ -21,10 +21,7 @@ use crate::{PlayerInfo, client::InGameState, connection::RawConnection};
/// # use bevy_ecs::event::EventReader;
///
/// fn handle_packets(mut events: EventReader<ReceiveGamePacketEvent>) {
/// for ReceiveGamePacketEvent {
/// entity,
/// packet,
/// } in events.read() {
/// for ReceiveGamePacketEvent { entity, packet } in events.read() {
/// match packet.as_ref() {
/// ClientboundGamePacket::LevelParticles(p) => {
/// // ...

View file

@ -20,7 +20,7 @@ pub use events::*;
use tracing::{debug, error, trace, warn};
use crate::{
ClientInformation, PlayerInfo,
ClientInformation,
chat::{ChatPacket, ChatReceivedEvent},
chunks,
connection::RawConnection,
@ -29,11 +29,10 @@ use crate::{
inventory::{
ClientSideCloseContainerEvent, Inventory, MenuOpenedEvent, SetContainerContentEvent,
},
local_player::{
GameProfileComponent, Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, TabList,
},
local_player::{Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, TabList},
movement::{KnockbackEvent, KnockbackType},
packet::as_system,
player::{GameProfileComponent, PlayerInfo},
};
pub fn process_packet(ecs: &mut World, player: Entity, packet: &ClientboundGamePacket) {
@ -1236,7 +1235,7 @@ impl GamePacketHandler<'_> {
// TODO: handle ContainerSetData packet
// this is used for various things like the furnace progress
// bar
// see https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol#Set_Container_Property
// see https://minecraft.wiki/w/Java_Edition_protocol/Packets#Set_Container_Property
// as_system::<Query<&mut Inventory>>(self.ecs, |mut query| {
// let inventory = query.get_mut(self.player).unwrap();
@ -1423,8 +1422,12 @@ impl GamePacketHandler<'_> {
)>(
self.ecs,
|(mut commands, mut query, mut events, mut instance_container, mut loaded_by_query)| {
let (mut instance_holder, game_profile, client_information, instance_name) =
query.get_mut(self.player).unwrap();
let Ok((mut instance_holder, game_profile, client_information, instance_name)) =
query.get_mut(self.player)
else {
warn!("Got respawn packet but player doesn't have the required components");
return;
};
let new_instance_name = p.common.dimension.clone();

View file

@ -17,8 +17,8 @@ use tracing::{debug, error};
use super::as_system;
use crate::{
Account, GameProfileComponent, InConfigState, connection::RawConnection,
declare_packet_handlers, disconnect::DisconnectEvent,
Account, InConfigState, connection::RawConnection, declare_packet_handlers,
disconnect::DisconnectEvent, player::GameProfileComponent,
};
pub fn process_packet(ecs: &mut World, player: Entity, packet: &ClientboundLoginPacket) {

View file

@ -1,35 +1,37 @@
use std::{fmt::Debug, sync::Arc};
use azalea_auth::game_profile::GameProfile;
use azalea_block::BlockState;
use azalea_buf::AzaleaWrite;
use azalea_core::delta::PositionDelta8;
use azalea_core::game_type::{GameMode, OptionalGameType};
use azalea_core::position::{ChunkPos, Vec3};
use azalea_core::resource_location::ResourceLocation;
use azalea_core::tick::GameTick;
use azalea_entity::metadata::PlayerMetadataBundle;
use azalea_protocol::packets::common::CommonPlayerSpawnInfo;
use azalea_protocol::packets::config::{ClientboundFinishConfiguration, ClientboundRegistryData};
use azalea_protocol::packets::game::c_level_chunk_with_light::ClientboundLevelChunkPacketData;
use azalea_protocol::packets::game::c_light_update::ClientboundLightUpdatePacketData;
use azalea_protocol::packets::game::{
ClientboundAddEntity, ClientboundLevelChunkWithLight, ClientboundLogin, ClientboundRespawn,
use azalea_core::{
delta::PositionDelta8,
game_type::{GameMode, OptionalGameType},
position::{ChunkPos, Vec3},
resource_location::ResourceLocation,
tick::GameTick,
};
use azalea_protocol::packets::{ConnectionProtocol, Packet, ProtocolPacket};
use azalea_registry::{DimensionType, EntityKind};
use azalea_world::palette::{PalettedContainer, PalettedContainerKind};
use azalea_world::{Chunk, Instance, MinecraftEntityId, Section};
use azalea_entity::metadata::PlayerMetadataBundle;
use azalea_protocol::packets::{
ConnectionProtocol, Packet, ProtocolPacket,
common::CommonPlayerSpawnInfo,
config::{ClientboundFinishConfiguration, ClientboundRegistryData},
game::{
ClientboundAddEntity, ClientboundLevelChunkWithLight, ClientboundLogin, ClientboundRespawn,
c_level_chunk_with_light::ClientboundLevelChunkPacketData,
c_light_update::ClientboundLightUpdatePacketData,
},
};
use azalea_registry::{Biome, DimensionType, EntityKind};
use azalea_world::{Chunk, Instance, MinecraftEntityId, Section, palette::PalettedContainer};
use bevy_app::App;
use bevy_ecs::component::Mutable;
use bevy_ecs::{prelude::*, schedule::ExecutorKind};
use bevy_ecs::{component::Mutable, prelude::*, schedule::ExecutorKind};
use parking_lot::RwLock;
use simdnbt::owned::{NbtCompound, NbtTag};
use uuid::Uuid;
use crate::connection::RawConnection;
use crate::disconnect::DisconnectEvent;
use crate::{
ClientInformation, GameProfileComponent, InConfigState, InstanceHolder, LocalPlayerBundle,
ClientInformation, InConfigState, LocalPlayerBundle, connection::RawConnection,
disconnect::DisconnectEvent, local_player::InstanceHolder, player::GameProfileComponent,
};
/// A way to simulate a client in a server, used for some internal tests.
@ -257,8 +259,8 @@ pub fn make_basic_empty_chunk(
for _ in 0..section_count {
sections.push(Section {
block_count: 0,
states: PalettedContainer::new(PalettedContainerKind::BlockStates),
biomes: PalettedContainer::new(PalettedContainerKind::Biomes),
states: PalettedContainer::<BlockState>::new(),
biomes: PalettedContainer::<Biome>::new(),
});
}
sections.azalea_write(&mut chunk_bytes).unwrap();

View file

@ -1,4 +1,4 @@
use azalea_client::{InConfigState, InGameState, InstanceHolder, test_simulation::*};
use azalea_client::{InConfigState, InGameState, local_player::InstanceHolder, test_simulation::*};
use azalea_core::{position::ChunkPos, resource_location::ResourceLocation};
use azalea_entity::LocalEntity;
use azalea_protocol::packets::{

View file

@ -28,11 +28,11 @@ fn reply_to_ping_with_pong() {
simulation
.app
.add_observer(move |trigger: Trigger<SendConfigPacketEvent>| {
if trigger.sent_by == simulation.entity {
if let ServerboundConfigPacket::Pong(packet) = &trigger.packet {
assert_eq!(packet.id, 321);
*reply_count_clone.lock() += 1;
}
if trigger.sent_by == simulation.entity
&& let ServerboundConfigPacket::Pong(packet) = &trigger.packet
{
assert_eq!(packet.id, 321);
*reply_count_clone.lock() += 1;
}
});
@ -63,11 +63,11 @@ fn reply_to_ping_with_pong() {
simulation
.app
.add_observer(move |trigger: Trigger<SendPacketEvent>| {
if trigger.sent_by == simulation.entity {
if let ServerboundGamePacket::Pong(packet) = &trigger.packet {
assert_eq!(packet.id, 123);
*reply_count_clone.lock() += 1;
}
if trigger.sent_by == simulation.entity
&& let ServerboundGamePacket::Pong(packet) = &trigger.packet
{
assert_eq!(packet.id, 123);
*reply_count_clone.lock() += 1;
}
});

View file

@ -1,4 +1,4 @@
use std::io::{Cursor, Write};
use std::io::{self, Cursor, Write};
use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite, BufReadError};
@ -124,26 +124,31 @@ impl From<Vec<u8>> for BitSet {
}
}
/// A list of bits with a known fixed size.
/// A compact fixed-size array of bits.
///
/// The `N` is the number of bytes reserved for the bitset. You're encouraged to
/// use it like `FixedBitSet<{ 20_usize.div_ceil(8) }>` if you need 20 bits.
/// The `N` is the number of bits reserved for the bitset. You're encouraged to
/// use it like `FixedBitSet<20>` if you need 20 bits.
///
/// TODO: this should be changed back to bits once this is resolved:
/// <https://github.com/rust-lang/rust/issues/133199#issuecomment-2531645526>
///
/// Note that this is primarily meant for fast serialization and deserialization
/// for Minecraft, if you don't need that you should use the `fixedbitset` crate
/// since it's approximately 20% faster (since it stores the data as usizes
/// instead of u8s).
/// Note that this is optimized for fast serialization and deserialization for
/// Minecraft, and may not be as performant as it could be for other purposes.
/// Notably, the internal representation is an array of `u8`s even though
/// `usize` would be slightly faster.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FixedBitSet<const N: usize> {
data: [u8; N],
pub struct FixedBitSet<const N: usize>
where
[u8; bits_to_bytes(N)]: Sized,
{
data: [u8; bits_to_bytes(N)],
}
impl<const N: usize> FixedBitSet<N> {
pub fn new() -> Self {
FixedBitSet { data: [0; N] }
impl<const N: usize> FixedBitSet<N>
where
[u8; bits_to_bytes(N)]: Sized,
{
pub const fn new() -> Self {
FixedBitSet {
data: [0; bits_to_bytes(N)],
}
}
#[inline]
@ -157,29 +162,42 @@ impl<const N: usize> FixedBitSet<N> {
}
}
impl<const N: usize> AzaleaRead for FixedBitSet<N> {
impl<const N: usize> AzaleaRead for FixedBitSet<N>
where
[u8; bits_to_bytes(N)]: Sized,
{
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let mut data = [0; N];
for item in data.iter_mut().take(N) {
let mut data = [0; bits_to_bytes(N)];
for item in data.iter_mut().take(bits_to_bytes(N)) {
*item = u8::azalea_read(buf)?;
}
Ok(FixedBitSet { data })
}
}
impl<const N: usize> AzaleaWrite for FixedBitSet<N> {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
for i in 0..N {
impl<const N: usize> AzaleaWrite for FixedBitSet<N>
where
[u8; bits_to_bytes(N)]: Sized,
{
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
for i in 0..bits_to_bytes(N) {
self.data[i].azalea_write(buf)?;
}
Ok(())
}
}
impl<const N: usize> Default for FixedBitSet<N> {
impl<const N: usize> Default for FixedBitSet<N>
where
[u8; bits_to_bytes(N)]: Sized,
{
fn default() -> Self {
Self::new()
}
}
pub const fn bits_to_bytes(n: usize) -> usize {
n.div_ceil(8)
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -1,6 +1,6 @@
use std::{
fmt::{Debug, Error, Formatter},
io::{Cursor, Write},
fmt::{self, Debug},
io::{self, Cursor, Write},
};
use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError};
@ -18,7 +18,7 @@ pub enum Err {
}
impl Debug for Err {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Err::InvalidDifficulty(s) => write!(f, "Invalid difficulty: {s}"),
}
@ -73,7 +73,7 @@ impl AzaleaRead for Difficulty {
}
impl AzaleaWrite for Difficulty {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
u8::azalea_write(&self.id(), buf)
}
}

View file

@ -1,4 +1,4 @@
use std::io::Cursor;
use std::io::{self, Cursor, Write};
use azalea_buf::{AzaleaRead, AzaleaReadLimited, AzaleaReadVar, AzaleaWrite};
@ -9,7 +9,7 @@ pub struct Filterable<T> {
}
impl<T: AzaleaWrite> azalea_buf::AzaleaWrite for Filterable<T> {
fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
self.raw.azalea_write(buf)?;
self.filtered.azalea_write(buf)?;
Ok(())

View file

@ -1,4 +1,4 @@
use std::io::{Cursor, Write};
use std::io::{self, Cursor, Write};
use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, BufReadError};
use azalea_chat::translatable_component::TranslatableComponent;
@ -108,7 +108,7 @@ impl AzaleaRead for GameMode {
}
impl AzaleaWrite for GameMode {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
u8::azalea_write(&self.to_id(), buf)
}
}
@ -138,7 +138,7 @@ impl AzaleaRead for OptionalGameType {
}
impl AzaleaWrite for OptionalGameType {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
GameMode::to_optional_id(*self).azalea_write(buf)
}
}

View file

@ -1,3 +1,5 @@
#![allow(incomplete_features)]
#![feature(generic_const_exprs)]
#![doc = include_str!("../README.md")]
pub mod aabb;

View file

@ -1,5 +1,5 @@
use std::{
fmt::{self, Display, Formatter},
fmt::{self, Display},
str::FromStr,
};
@ -12,7 +12,7 @@ pub enum ObjectiveCriteria {
}
impl Display for ObjectiveCriteria {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ObjectiveCriteria::Integer => write!(f, "integer"),
ObjectiveCriteria::Hearts => write!(f, "hearts"),

View file

@ -3,19 +3,18 @@
//! The most common ones are [`Vec3`] and [`BlockPos`], which are usually used
//! for entity positions and block positions, respectively.
use std::str::FromStr;
use std::{
fmt,
hash::Hash,
hash::{Hash, Hasher},
io,
io::{Cursor, Write},
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub},
str::FromStr,
};
use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite, BufReadError};
use crate::direction::Direction;
use crate::math;
use crate::resource_location::ResourceLocation;
use crate::{direction::Direction, math, resource_location::ResourceLocation};
macro_rules! vec3_impl {
($name:ident, $type:ty) => {
@ -377,11 +376,8 @@ impl BlockPos {
/// ```
/// # use azalea_core::position::BlockPos;
/// assert_eq!(
/// BlockPos::min(
/// &BlockPos::new(1, 20, 300),
/// &BlockPos::new(50, 40, 30),
/// ),
/// BlockPos::new(1, 20, 30),
/// BlockPos::min(&BlockPos::new(1, 20, 300), &BlockPos::new(50, 40, 30),),
/// BlockPos::new(1, 20, 30),
/// );
/// ```
pub fn min(&self, other: &Self) -> Self {
@ -397,11 +393,8 @@ impl BlockPos {
/// ```
/// # use azalea_core::position::BlockPos;
/// assert_eq!(
/// BlockPos::max(
/// &BlockPos::new(1, 20, 300),
/// &BlockPos::new(50, 40, 30),
/// ),
/// BlockPos::new(50, 40, 300),
/// BlockPos::max(&BlockPos::new(1, 20, 300), &BlockPos::new(50, 40, 30),),
/// BlockPos::new(50, 40, 300),
/// );
/// ```
pub fn max(&self, other: &Self) -> Self {
@ -477,7 +470,7 @@ impl AzaleaRead for ChunkPos {
}
}
impl AzaleaWrite for ChunkPos {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
u64::from(*self).azalea_write(buf)?;
Ok(())
}
@ -485,7 +478,7 @@ impl AzaleaWrite for ChunkPos {
impl Hash for ChunkPos {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
fn hash<H: Hasher>(&self, state: &mut H) {
// optimized hash that only calls hash once
u64::from(*self).hash(state);
}
@ -526,7 +519,7 @@ impl ChunkBlockPos {
impl Hash for ChunkBlockPos {
// optimized hash that only calls hash once
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
fn hash<H: Hasher>(&self, state: &mut H) {
u64::from(*self).hash(state);
}
}
@ -546,8 +539,8 @@ impl From<ChunkBlockPos> for u64 {
}
impl nohash_hasher::IsEnabled for ChunkBlockPos {}
/// The coordinates of a block inside a chunk section. Each coordinate must be
/// in the range [0, 15].
/// The coordinates of a block inside a chunk section. Each coordinate should be
/// in the range 0..=15.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ChunkSectionBlockPos {
pub x: u8,
@ -556,6 +549,50 @@ pub struct ChunkSectionBlockPos {
}
vec3_impl!(ChunkSectionBlockPos, u8);
/// The coordinates of a chunk inside a chunk section. Each coordinate should be
/// in the range 0..=3.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ChunkSectionBiomePos {
pub x: u8,
pub y: u8,
pub z: u8,
}
impl From<&ChunkBiomePos> for ChunkSectionBiomePos {
#[inline]
fn from(pos: &ChunkBiomePos) -> Self {
ChunkSectionBiomePos {
x: pos.x,
y: (pos.y & 0b11) as u8,
z: pos.z,
}
}
}
vec3_impl!(ChunkSectionBiomePos, u8);
/// The coordinates of a biome inside a chunk. Biomes are 4x4 blocks.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ChunkBiomePos {
pub x: u8,
pub y: i32,
pub z: u8,
}
impl From<&BlockPos> for ChunkBiomePos {
#[inline]
fn from(pos: &BlockPos) -> Self {
ChunkBiomePos::from(&ChunkBlockPos::from(pos))
}
}
impl From<&ChunkBlockPos> for ChunkBiomePos {
#[inline]
fn from(pos: &ChunkBlockPos) -> Self {
ChunkBiomePos {
x: pos.x >> 2,
y: pos.y >> 2,
z: pos.z >> 2,
}
}
}
impl Add<ChunkSectionBlockPos> for ChunkSectionPos {
type Output = BlockPos;
@ -570,7 +607,7 @@ impl Add<ChunkSectionBlockPos> for ChunkSectionPos {
impl Hash for ChunkSectionBlockPos {
// optimized hash that only calls hash once
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
fn hash<H: Hasher>(&self, state: &mut H) {
u16::from(*self).hash(state);
}
}
@ -782,7 +819,7 @@ impl AzaleaRead for ChunkSectionPos {
}
impl AzaleaWrite for BlockPos {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
let mut val: u64 = 0;
val |= ((self.x as u64) & PACKED_X_MASK) << X_OFFSET;
val |= (self.y as u64) & PACKED_Y_MASK;
@ -792,7 +829,7 @@ impl AzaleaWrite for BlockPos {
}
impl AzaleaWrite for GlobalPos {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
ResourceLocation::azalea_write(&self.world, buf)?;
BlockPos::azalea_write(&self.pos, buf)?;
@ -801,7 +838,7 @@ impl AzaleaWrite for GlobalPos {
}
impl AzaleaWrite for ChunkSectionPos {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
let long = (((self.x & 0x3FFFFF) as i64) << 42)
| (self.y & 0xFFFFF) as i64
| (((self.z & 0x3FFFFF) as i64) << 20);

View file

@ -2,7 +2,7 @@
use std::{
fmt,
io::{Cursor, Write},
io::{self, Cursor, Write},
str::FromStr,
};
@ -67,7 +67,7 @@ impl AzaleaRead for ResourceLocation {
}
}
impl AzaleaWrite for ResourceLocation {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
self.to_string().azalea_write(buf)
}
}

View file

@ -2,10 +2,9 @@
mod signing;
use aes::cipher::inout::InOutBuf;
use aes::{
Aes128,
cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit},
cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit, inout::InOutBuf},
};
use rand::{RngCore, rngs::OsRng};
use sha1::{Digest, Sha1};

View file

@ -1,4 +1,4 @@
//! See <https://minecraft.fandom.com/wiki/Attribute>.
//! See <https://minecraft.wiki/w/Attribute>.
use std::collections::{HashMap, hash_map};

View file

@ -1,6 +1,6 @@
//! Define some types needed for entity metadata.
use std::io::{Cursor, Write};
use std::io::{self, Cursor, Write};
use azalea_buf::{AzBuf, AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
use azalea_chat::FormattedText;
@ -43,7 +43,7 @@ impl AzaleaRead for EntityMetadataItems {
}
impl AzaleaWrite for EntityMetadataItems {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
for item in &self.0 {
item.index.azalea_write(buf)?;
item.value.azalea_write(buf)?;
@ -128,7 +128,7 @@ impl AzaleaRead for OptionalUnsignedInt {
}
}
impl AzaleaWrite for OptionalUnsignedInt {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
match self.0 {
Some(val) => (val + 1).azalea_write_var(buf),
None => 0u32.azalea_write_var(buf),

View file

@ -12,7 +12,7 @@ mod plugin;
pub mod vec_delta_codec;
use std::{
fmt::Debug,
fmt::{self, Debug},
hash::{Hash, Hasher},
};
@ -133,7 +133,7 @@ impl EntityUuid {
}
}
impl Debug for EntityUuid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(self.0).fmt(f)
}
}

View file

@ -1,6 +1,6 @@
#![allow(clippy::single_match)]
// This file is generated from codegen/lib/code/entity.py.
// This file is @generated from codegen/lib/code/entity.py.
// Don't change it manually!
use azalea_chat::FormattedText;

View file

@ -3,6 +3,7 @@ use azalea_buf::AzBuf;
use azalea_core::{color::RgbColor, position::BlockPos};
use azalea_inventory::ItemStack;
use azalea_registry::ParticleKind;
use azalea_world::MinecraftEntityId;
use bevy_ecs::component::Component;
// the order of this enum must be kept in sync with ParticleKind, otherwise
@ -290,15 +291,27 @@ pub struct ItemParticle {
#[derive(Debug, Clone, AzBuf, Default)]
pub struct VibrationParticle {
pub origin: BlockPos,
pub position_type: String,
pub block_position: BlockPos,
#[var]
pub entity_id: u32,
pub position: PositionSource,
#[var]
pub ticks: u32,
}
#[derive(Debug, Clone, AzBuf)]
pub enum PositionSource {
Block(BlockPos),
Entity {
#[var]
id: MinecraftEntityId,
y_offset: f32,
},
}
impl Default for PositionSource {
fn default() -> Self {
// bad default but hopefully it never gets used anyways
Self::Block(BlockPos::default())
}
}
#[derive(Debug, Clone, AzBuf, Default)]
pub struct SculkChargeParticle {
pub roll: f32,

View file

@ -2,7 +2,7 @@
use std::{
collections::{HashMap, HashSet},
fmt::Debug,
fmt::{self, Debug},
};
use azalea_core::position::ChunkPos;
@ -115,7 +115,7 @@ impl EntityIdIndex {
}
impl Debug for EntityUuidIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EntityUuidIndex").finish()
}
}

View file

@ -1,5 +1,9 @@
use core::f64;
use std::{any::Any, collections::HashMap, io::Cursor};
use std::{
any::Any,
collections::HashMap,
io::{self, Cursor},
};
use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite, BufReadError};
use azalea_chat::FormattedText;
@ -23,11 +27,11 @@ pub trait DataComponent: Send + Sync + Any {
}
pub trait EncodableDataComponent: Send + Sync + Any {
fn encode(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error>;
fn encode(&self, buf: &mut Vec<u8>) -> io::Result<()>;
// using the Clone trait makes it not be object-safe, so we have our own clone
// function instead
fn clone(&self) -> Box<dyn EncodableDataComponent>;
// same deal here
// same thing here
fn eq(&self, other: Box<dyn EncodableDataComponent>) -> bool;
}
@ -35,7 +39,7 @@ impl<T> EncodableDataComponent for T
where
T: DataComponent + Clone + AzaleaWrite + AzaleaRead + PartialEq,
{
fn encode(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
fn encode(&self, buf: &mut Vec<u8>) -> io::Result<()> {
self.azalea_write(buf)
}
fn clone(&self) -> Box<dyn EncodableDataComponent> {
@ -897,7 +901,9 @@ pub struct DamageResistant {
// in the vanilla code this is
// ```
// StreamCodec.composite(
// TagKey.streamCodec(Registries.DAMAGE_TYPE), DamageResistant::types, DamageResistant::new
// TagKey.streamCodec(Registries.DAMAGE_TYPE),
// DamageResistant::types,
// DamageResistant::new,
// );
// ```
// i'm not entirely sure if this is meant to be a vec or something, i just made it a

View file

@ -1,7 +1,7 @@
use std::{
any::Any,
fmt,
io::{Cursor, Write},
io::{self, Cursor, Write},
};
use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
@ -73,10 +73,10 @@ impl ItemStack {
/// Update whether this slot is empty, based on the count.
pub fn update_empty(&mut self) {
if let ItemStack::Present(i) = self {
if i.is_empty() {
*self = ItemStack::Empty;
}
if let ItemStack::Present(i) = self
&& i.is_empty()
{
*self = ItemStack::Empty;
}
}
@ -122,14 +122,14 @@ impl ItemStackData {
/// # use azalea_inventory::ItemStackData;
/// # use azalea_registry::Item;
/// let mut a = ItemStackData {
/// kind: Item::Stone,
/// count: 1,
/// components: Default::default(),
/// kind: Item::Stone,
/// count: 1,
/// components: Default::default(),
/// };
/// let mut b = ItemStackData {
/// kind: Item::Stone,
/// count: 2,
/// components: Default::default(),
/// kind: Item::Stone,
/// count: 2,
/// components: Default::default(),
/// };
/// assert!(a.is_same_item_and_components(&b));
///
@ -159,7 +159,7 @@ impl AzaleaRead for ItemStack {
}
impl AzaleaWrite for ItemStack {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
match self {
ItemStack::Empty => 0_i32.azalea_write_var(buf)?,
ItemStack::Present(i) => {
@ -256,7 +256,7 @@ impl AzaleaRead for DataComponentPatch {
}
impl AzaleaWrite for DataComponentPatch {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
let mut components_with_data_count: u32 = 0;
let mut components_without_data_count: u32 = 0;
for component in self.components.values() {

View file

@ -151,12 +151,11 @@ fn clip_with_interaction_override(
// compostors, hoppers, and scaffolding.
let interaction_shape = &*EMPTY_SHAPE;
let interaction_hit_result = interaction_shape.clip(from, to, block_pos);
if let Some(interaction_hit_result) = interaction_hit_result {
if interaction_hit_result.location.distance_squared_to(from)
if let Some(interaction_hit_result) = interaction_hit_result
&& interaction_hit_result.location.distance_squared_to(from)
< block_hit_result.location.distance_squared_to(from)
{
return Some(block_hit_result.with_direction(interaction_hit_result.direction));
}
{
return Some(block_hit_result.with_direction(interaction_hit_result.direction));
}
Some(block_hit_result)

View file

@ -1,6 +1,6 @@
//! Autogenerated block collisions for every block
// This file is generated from codegen/lib/code/shapes.py. If you want to
// This file is @generated from codegen/lib/code/shapes.py. If you want to
// modify it, change that file.
#![allow(clippy::explicit_auto_deref)]

View file

@ -1,3 +1,5 @@
use std::cmp;
use azalea_core::{
bitset::BitSet,
direction::{Axis, AxisCycle},
@ -146,12 +148,12 @@ impl BitSetDiscreteVoxelShape {
fn fill_update_bounds(&mut self, x: u32, y: u32, z: u32, update: bool) {
self.storage.set(self.get_index(x, y, z));
if update {
self.x_min = std::cmp::min(self.x_min, x as i32);
self.y_min = std::cmp::min(self.y_min, y as i32);
self.z_min = std::cmp::min(self.z_min, z as i32);
self.x_max = std::cmp::max(self.x_max, (x + 1) as i32);
self.y_max = std::cmp::max(self.y_max, (y + 1) as i32);
self.z_max = std::cmp::max(self.z_max, (z + 1) as i32);
self.x_min = cmp::min(self.x_min, x as i32);
self.y_min = cmp::min(self.y_min, y as i32);
self.z_min = cmp::min(self.z_min, z as i32);
self.x_max = cmp::max(self.x_max, (x + 1) as i32);
self.y_max = cmp::max(self.y_max, (y + 1) as i32);
self.z_max = cmp::max(self.z_max, (z + 1) as i32);
}
}
@ -201,24 +203,24 @@ impl BitSetDiscreteVoxelShape {
var12.try_into().unwrap(),
var14.try_into().unwrap(),
));
var7[2] = std::cmp::min(var7[2], var14);
var7[5] = std::cmp::max(var7[5], var14);
var7[2] = cmp::min(var7[2], var14);
var7[5] = cmp::max(var7[5], var14);
var13[0] = true;
}
true
});
if var13[0] {
var7[1] = std::cmp::min(var7[1], var12);
var7[4] = std::cmp::max(var7[4], var12);
var7[1] = cmp::min(var7[1], var12);
var7[4] = cmp::max(var7[4], var12);
var10[0] = true;
}
true
});
if var10[0] {
var7[0] = std::cmp::min(var7[0], var9);
var7[3] = std::cmp::max(var7[3], var9);
var7[0] = cmp::min(var7[0], var9);
var7[3] = cmp::max(var7[3], var9);
}
true

View file

@ -86,7 +86,7 @@ impl<'a> BlockCollisionsState<'a> {
let block_state: BlockState = if item_chunk_pos == initial_chunk_pos {
match &initial_chunk {
Some(initial_chunk) => initial_chunk
.get(&ChunkBlockPos::from(item.pos), self.world.chunks.min_y)
.get_block_state(&ChunkBlockPos::from(item.pos), self.world.chunks.min_y)
.unwrap_or(BlockState::AIR),
_ => BlockState::AIR,
}
@ -186,7 +186,7 @@ impl<'a> BlockCollisionsState<'a> {
for (cached_section_pos, cached_section) in &self.cached_sections {
if section_pos == *cached_section_pos {
return cached_section.get(section_block_pos);
return cached_section.get_block_state(section_block_pos);
}
}
@ -211,7 +211,7 @@ impl<'a> BlockCollisionsState<'a> {
// println!("chunk section data: {:?}", section.states.storage.data);
// println!("biome length: {}", section.biomes.storage.data.len());
section.get(section_block_pos)
section.get_block_state(section_block_pos)
}
fn get_block_shape(&mut self, block_state: BlockState) -> &'static VoxelShape {

View file

@ -23,7 +23,7 @@ fn as_packet_derive(input: TokenStream, state: proc_macro2::TokenStream) -> Toke
let contents = quote! {
impl #ident {
pub fn write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
pub fn write(&self, buf: &mut impl std::io::Write) -> std::io::Result<()> {
azalea_buf::AzaleaWrite::azalea_write(self, buf)
}
@ -361,7 +361,7 @@ pub fn declare_state_packets(input: TokenStream) -> TokenStream {
}
}
fn write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
fn write(&self, buf: &mut impl std::io::Write) -> std::io::Result<()> {
match self {
#serverbound_write_match_contents
}
@ -405,7 +405,7 @@ pub fn declare_state_packets(input: TokenStream) -> TokenStream {
}
}
fn write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
fn write(&self, buf: &mut impl std::io::Write) -> std::io::Result<()> {
match self {
#clientbound_write_match_contents
}

View file

@ -25,8 +25,7 @@ use tokio::{
io::{self, AsyncWriteExt},
net::{TcpListener, TcpStream},
};
use tracing::Level;
use tracing::{error, info, warn};
use tracing::{Level, error, info, warn};
const LISTEN_ADDR: &str = "127.0.0.1:25566";
const PROXY_ADDR: &str = "127.0.0.1:25565";

View file

@ -1,3 +1,5 @@
use std::io::{self, Cursor};
use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite};
use azalea_core::bitset::FixedBitSet;
use bevy_ecs::component::Component;
@ -96,8 +98,8 @@ impl Default for ModelCustomization {
}
impl AzaleaRead for ModelCustomization {
fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> {
let set = FixedBitSet::<{ 7_usize.div_ceil(8) }>::azalea_read(buf)?;
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> {
let set = FixedBitSet::<7>::azalea_read(buf)?;
Ok(Self {
cape: set.index(0),
jacket: set.index(1),
@ -111,8 +113,8 @@ impl AzaleaRead for ModelCustomization {
}
impl AzaleaWrite for ModelCustomization {
fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
let mut set = FixedBitSet::<{ 7_usize.div_ceil(8) }>::new();
fn azalea_write(&self, buf: &mut impl io::Write) -> io::Result<()> {
let mut set = FixedBitSet::<7>::new();
if self.cape {
set.set(0);
}

View file

@ -32,7 +32,7 @@ pub struct RelativeMovements {
impl AzaleaRead for RelativeMovements {
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
// yes minecraft seriously wastes that many bits, smh
let set = FixedBitSet::<{ 32_usize.div_ceil(8) }>::azalea_read(buf)?;
let set = FixedBitSet::<32>::azalea_read(buf)?;
Ok(RelativeMovements {
x: set.index(0),
y: set.index(1),
@ -48,8 +48,8 @@ impl AzaleaRead for RelativeMovements {
}
impl AzaleaWrite for RelativeMovements {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
let mut set = FixedBitSet::<{ 32_usize.div_ceil(8) }>::new();
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
let mut set = FixedBitSet::<32>::new();
let mut set_bit = |index: usize, value: bool| {
if value {
set.set(index);

View file

@ -1,29 +1,40 @@
//! Connect to remote servers/clients.
use std::fmt::{self, Debug, Display};
use std::io::{self, Cursor};
use std::marker::PhantomData;
use std::net::SocketAddr;
use std::{
fmt::{self, Debug, Display},
io::{self, Cursor},
marker::PhantomData,
net::SocketAddr,
};
use azalea_auth::game_profile::GameProfile;
use azalea_auth::sessionserver::{ClientSessionServerError, ServerSessionServerError};
use azalea_auth::{
game_profile::GameProfile,
sessionserver::{ClientSessionServerError, ServerSessionServerError},
};
use azalea_crypto::{Aes128CfbDec, Aes128CfbEnc};
use thiserror::Error;
use tokio::io::{AsyncWriteExt, BufStream};
use tokio::net::TcpStream;
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf, ReuniteError};
use tokio::{
io::{AsyncWriteExt, BufStream},
net::{
TcpStream,
tcp::{OwnedReadHalf, OwnedWriteHalf, ReuniteError},
},
};
use tracing::{error, info};
use uuid::Uuid;
use crate::packets::ProtocolPacket;
use crate::packets::config::{ClientboundConfigPacket, ServerboundConfigPacket};
use crate::packets::game::{ClientboundGamePacket, ServerboundGamePacket};
use crate::packets::handshake::{ClientboundHandshakePacket, ServerboundHandshakePacket};
use crate::packets::login::c_hello::ClientboundHello;
use crate::packets::login::{ClientboundLoginPacket, ServerboundLoginPacket};
use crate::packets::status::{ClientboundStatusPacket, ServerboundStatusPacket};
use crate::read::{ReadPacketError, deserialize_packet, read_raw_packet, try_read_raw_packet};
use crate::write::{serialize_packet, write_raw_packet};
use crate::{
packets::{
ProtocolPacket,
config::{ClientboundConfigPacket, ServerboundConfigPacket},
game::{ClientboundGamePacket, ServerboundGamePacket},
handshake::{ClientboundHandshakePacket, ServerboundHandshakePacket},
login::{ClientboundLoginPacket, ServerboundLoginPacket, c_hello::ClientboundHello},
status::{ClientboundStatusPacket, ServerboundStatusPacket},
},
read::{ReadPacketError, deserialize_packet, read_raw_packet, try_read_raw_packet},
write::{serialize_packet, write_raw_packet},
};
pub struct RawReadConnection {
pub read_stream: OwnedReadHalf,
@ -57,18 +68,13 @@ pub struct WriteConnection<W: ProtocolPacket> {
/// Join an offline-mode server and go through the handshake.
/// ```rust,no_run
/// use azalea_protocol::{
/// resolver,
/// connect::Connection,
/// packets::{
/// self,
/// ClientIntention, PROTOCOL_VERSION,
/// login::{
/// ClientboundLoginPacket,
/// ServerboundHello,
/// ServerboundKey
/// },
/// handshake::ServerboundIntention
/// }
/// self, ClientIntention, PROTOCOL_VERSION,
/// handshake::ServerboundIntention,
/// login::{ClientboundLoginPacket, ServerboundHello, ServerboundKey},
/// },
/// resolver,
/// };
///
/// #[tokio::main]
@ -82,7 +88,8 @@ pub struct WriteConnection<W: ProtocolPacket> {
/// hostname: resolved_address.ip().to_string(),
/// port: resolved_address.port(),
/// intention: ClientIntention::Login,
/// }).await?;
/// })
/// .await?;
///
/// let mut conn = conn.login();
///
@ -90,7 +97,8 @@ pub struct WriteConnection<W: ProtocolPacket> {
/// conn.write(ServerboundHello {
/// name: "bot".to_string(),
/// profile_id: uuid::Uuid::nil(),
/// }).await?;
/// })
/// .await?;
///
/// let (conn, game_profile) = loop {
/// let packet = conn.read().await?;
@ -101,7 +109,8 @@ pub struct WriteConnection<W: ProtocolPacket> {
/// conn.write(ServerboundKey {
/// key_bytes: e.encrypted_public_key,
/// encrypted_challenge: e.encrypted_challenge,
/// }).await?;
/// })
/// .await?;
/// conn.set_encryption_key(e.secret_key);
/// }
/// ClientboundLoginPacket::LoginCompression(p) => {
@ -391,19 +400,20 @@ impl Connection<ClientboundLoginPacket, ServerboundLoginPacket> {
///
/// ```rust,no_run
/// use azalea_auth::AuthResult;
/// use azalea_protocol::connect::Connection;
/// use azalea_protocol::packets::login::{
/// ClientboundLoginPacket,
/// ServerboundKey
/// use azalea_protocol::{
/// connect::Connection,
/// packets::login::{ClientboundLoginPacket, ServerboundKey},
/// };
/// use uuid::Uuid;
/// # use azalea_protocol::ServerAddress;
///
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let AuthResult { access_token, profile } = azalea_auth::auth(
/// "example@example.com",
/// azalea_auth::AuthOpts::default()
/// ).await.expect("Couldn't authenticate");
/// let AuthResult {
/// access_token,
/// profile,
/// } = azalea_auth::auth("example@example.com", azalea_auth::AuthOpts::default())
/// .await
/// .expect("Couldn't authenticate");
/// #
/// # let address = ServerAddress::try_from("example@example.com").unwrap();
/// # let resolved_address = azalea_protocol::resolver::resolve_address(&address).await?;
@ -417,16 +427,13 @@ impl Connection<ClientboundLoginPacket, ServerboundLoginPacket> {
/// ClientboundLoginPacket::Hello(p) => {
/// // tell Mojang we're joining the server & enable encryption
/// let e = azalea_crypto::encrypt(&p.public_key, &p.challenge).unwrap();
/// conn.authenticate(
/// &access_token,
/// &profile.id,
/// e.secret_key,
/// &p
/// ).await?;
/// conn.authenticate(&access_token, &profile.id, e.secret_key, &p)
/// .await?;
/// conn.write(ServerboundKey {
/// key_bytes: e.encrypted_public_key,
/// encrypted_challenge: e.encrypted_challenge,
/// }).await?;
/// })
/// .await?;
/// conn.set_encryption_key(e.secret_key);
/// }
/// _ => {}

Some files were not shown because too many files have changed in this diff Show more