diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f61d882..29b060cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 03a78262..a275fc05 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/azalea-auth/README.md b/azalea-auth/README.md index 4b46bfce..8f7cd46b 100644 --- a/azalea-auth/README.md +++ b/azalea-auth/README.md @@ -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). diff --git a/azalea-auth/src/auth.rs b/azalea-auth/src/auth.rs index 99dfc115..51e44a70 100644 --- a/azalea-auth/src/auth.rs +++ b/azalea-auth/src/auth.rs @@ -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) -> Result) -> Result) -> 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)?; diff --git a/azalea-block/azalea-block-macros/src/lib.rs b/azalea-block/azalea-block-macros/src/lib.rs index f1b5c10f..3d09b018 100644 --- a/azalea-block/azalea-block-macros/src/lib.rs +++ b/azalea-block/azalea-block-macros/src/lib.rs @@ -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! {}; diff --git a/azalea-block/src/block_state.rs b/azalea-block/src/block_state.rs index 9dd2e741..dfa2a9b2 100644 --- a/azalea-block/src/block_state.rs +++ b/azalea-block/src/block_state.rs @@ -94,6 +94,12 @@ impl TryFrom for BlockState { } } } +impl From 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 { @@ -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) } } diff --git a/azalea-brigadier/src/arguments/argument_type.rs b/azalea-brigadier/src/arguments/argument_type.rs index d7bfa7d6..45859538 100644 --- a/azalea-brigadier/src/arguments/argument_type.rs +++ b/azalea-brigadier/src/arguments/argument_type.rs @@ -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, CommandSyntaxException>; + fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxError>; fn list_suggestions(&self, _builder: SuggestionsBuilder) -> Suggestions { Suggestions::default() diff --git a/azalea-brigadier/src/arguments/bool_argument_type.rs b/azalea-brigadier/src/arguments/bool_argument_type.rs index efb86509..225e985e 100644 --- a/azalea-brigadier/src/arguments/bool_argument_type.rs +++ b/azalea-brigadier/src/arguments/bool_argument_type.rs @@ -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, CommandSyntaxException> { + fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxError> { Ok(Arc::new(reader.read_boolean()?)) } diff --git a/azalea-brigadier/src/arguments/double_argument_type.rs b/azalea-brigadier/src/arguments/double_argument_type.rs index 559d1cf5..5b5d8cce 100644 --- a/azalea-brigadier/src/arguments/double_argument_type.rs +++ b/azalea-brigadier/src/arguments/double_argument_type.rs @@ -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, CommandSyntaxException> { + fn parse(&self, reader: &mut StringReader) -> Result, 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)) } diff --git a/azalea-brigadier/src/arguments/float_argument_type.rs b/azalea-brigadier/src/arguments/float_argument_type.rs index 83e298ff..8ea3a417 100644 --- a/azalea-brigadier/src/arguments/float_argument_type.rs +++ b/azalea-brigadier/src/arguments/float_argument_type.rs @@ -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, CommandSyntaxException> { + fn parse(&self, reader: &mut StringReader) -> Result, 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)) } diff --git a/azalea-brigadier/src/arguments/integer_argument_type.rs b/azalea-brigadier/src/arguments/integer_argument_type.rs index 47d25e27..9cdb9c34 100644 --- a/azalea-brigadier/src/arguments/integer_argument_type.rs +++ b/azalea-brigadier/src/arguments/integer_argument_type.rs @@ -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, CommandSyntaxException> { + fn parse(&self, reader: &mut StringReader) -> Result, 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)) } diff --git a/azalea-brigadier/src/arguments/long_argument_type.rs b/azalea-brigadier/src/arguments/long_argument_type.rs index ba65479c..1d4b3c9b 100644 --- a/azalea-brigadier/src/arguments/long_argument_type.rs +++ b/azalea-brigadier/src/arguments/long_argument_type.rs @@ -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, CommandSyntaxException> { + fn parse(&self, reader: &mut StringReader) -> Result, 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)) } diff --git a/azalea-brigadier/src/arguments/string_argument_type.rs b/azalea-brigadier/src/arguments/string_argument_type.rs index 96b9c998..bcb040d6 100644 --- a/azalea-brigadier/src/arguments/string_argument_type.rs +++ b/azalea-brigadier/src/arguments/string_argument_type.rs @@ -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, CommandSyntaxException> { + fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxError> { let result = match self { StringArgument::SingleWord => reader.read_unquoted_string().to_string(), StringArgument::QuotablePhrase => reader.read_string()?, diff --git a/azalea-brigadier/src/builder/argument_builder.rs b/azalea-brigadier/src/builder/argument_builder.rs index 9ebe6400..5fbdaca5 100644 --- a/azalea-brigadier/src/builder/argument_builder.rs +++ b/azalea-brigadier/src/builder/argument_builder.rs @@ -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 { Literal(Literal), - Argument(Argument), + Argument(Argument), +} +impl Clone for ArgumentBuilderType { + 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 { /// A node that isn't yet built. impl ArgumentBuilder { - pub fn new(value: ArgumentBuilderType) -> Self { + pub fn new(value: ArgumentBuilderType) -> Self { Self { arguments: CommandNode { value, @@ -49,9 +63,7 @@ impl ArgumentBuilder { /// ``` /// # 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) -> Self { @@ -79,6 +91,16 @@ impl ArgumentBuilder { pub fn executes(mut self, f: F) -> Self where F: Fn(&CommandContext) -> i32 + Send + Sync + 'static, + { + self.command = Some(Arc::new(move |ctx: &CommandContext| Ok(f(ctx)))); + self + } + + /// Same as [`Self::executes`] but returns a `Result`. + pub fn executes_result(mut self, f: F) -> Self + where + F: Fn(&CommandContext) -> Result + Send + Sync + 'static, { self.command = Some(Arc::new(f)); self @@ -163,7 +185,7 @@ impl ArgumentBuilder { } impl Debug for ArgumentBuilder { - 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) diff --git a/azalea-brigadier/src/builder/literal_argument_builder.rs b/azalea-brigadier/src/builder/literal_argument_builder.rs index 6627ffdc..ea5db2d3 100644 --- a/azalea-brigadier/src/builder/literal_argument_builder.rs +++ b/azalea-brigadier/src/builder/literal_argument_builder.rs @@ -12,7 +12,7 @@ impl Literal { } } -impl From for ArgumentBuilderType { +impl From for ArgumentBuilderType { fn from(literal: Literal) -> Self { Self::Literal(literal) } diff --git a/azalea-brigadier/src/builder/required_argument_builder.rs b/azalea-brigadier/src/builder/required_argument_builder.rs index 51f0acec..3fc33143 100644 --- a/azalea-brigadier/src/builder/required_argument_builder.rs +++ b/azalea-brigadier/src/builder/required_argument_builder.rs @@ -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 { pub name: String, parser: Arc, + custom_suggestions: Option + Send + Sync>>, } -impl Argument { - pub fn new(name: &str, parser: Arc) -> Self { +impl Argument { + pub fn new( + name: &str, + parser: Arc, + custom_suggestions: Option + Send + Sync>>, + ) -> Self { Self { name: name.to_string(), parser, + custom_suggestions, } } - pub fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException> { + pub fn parse(&self, reader: &mut StringReader) -> Result, 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, + 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 { @@ -39,14 +54,14 @@ impl Argument { } } -impl From for ArgumentBuilderType { - fn from(argument: Argument) -> Self { +impl From> for ArgumentBuilderType { + fn from(argument: Argument) -> Self { Self::Argument(argument) } } -impl Debug for Argument { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl Debug for Argument { + 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( name: &str, parser: impl ArgumentType + Send + Sync + 'static, ) -> ArgumentBuilder { - ArgumentBuilder::new(Argument::new(name, Arc::new(parser)).into()) + ArgumentBuilder::new(Argument::new(name, Arc::new(parser), None).into()) +} + +impl Clone for Argument { + fn clone(&self) -> Self { + Self { + name: self.name.clone(), + parser: self.parser.clone(), + custom_suggestions: self.custom_suggestions.clone(), + } + } } diff --git a/azalea-brigadier/src/command_dispatcher.rs b/azalea-brigadier/src/command_dispatcher.rs index 4a3b51d7..b27df6d1 100644 --- a/azalea-brigadier/src/command_dispatcher.rs +++ b/azalea-brigadier/src/command_dispatcher.rs @@ -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>>, + consumer: Box + Send + Sync>, } impl CommandDispatcher { pub fn new() -> Self { Self { root: Arc::new(RwLock::new(CommandNode::default())), + consumer: Box::new(DefaultResultConsumer), } } @@ -64,10 +67,10 @@ impl CommandDispatcher { node: &Arc>>, original_reader: &StringReader, context_so_far: CommandContextBuilder<'a, S>, - ) -> Result, CommandSyntaxException> { + ) -> Result, 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::>, CommandSyntaxException>::new(); + let mut errors = HashMap::>, CommandSyntaxError>::new(); let mut potentials: Vec> = vec![]; let cursor = original_reader.cursor(); @@ -83,7 +86,7 @@ impl CommandDispatcher { 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 CommandDispatcher { 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 CommandDispatcher { &self, input: impl Into, source: S, - ) -> Result { + ) -> Result { 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 CommandDispatcher { } /// Executes a given pre-parsed command. - pub fn execute_parsed(parse: ParseResults) -> Result { + pub fn execute_parsed(&self, parse: ParseResults) -> Result { 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> = 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( diff --git a/azalea-brigadier/src/context/command_context.rs b/azalea-brigadier/src/context/command_context.rs index 202d6f21..a9959895 100644 --- a/azalea-brigadier/src/context/command_context.rs +++ b/azalea-brigadier/src/context/command_context.rs @@ -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 { pub source: Arc, - pub input: String, - pub arguments: HashMap, - pub command: Command, - pub root_node: Arc>>, - pub nodes: Vec>, - pub range: StringRange, - pub child: Option>>, - pub modifier: Option>>, - pub forks: bool, + pub(super) input: String, + pub(super) arguments: HashMap, + pub(super) command: Command, + pub(super) root_node: Arc>>, + pub(super) nodes: Vec>, + pub(super) range: StringRange, + pub(super) child: Option>>, + pub(super) modifier: Option>>, + pub(super) forks: bool, } impl Clone for CommandContext { @@ -40,7 +46,7 @@ impl Clone for CommandContext { } impl Debug for CommandContext { - 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 Debug for CommandContext { impl CommandContext { pub fn copy_for(&self, source: Arc) -> Self { if Arc::ptr_eq(&source, &self.source) { + // fast path return self.clone(); } + CommandContext { source, input: self.input.clone(), @@ -75,12 +83,52 @@ impl CommandContext { } } + pub fn child(&self) -> Option<&CommandContext> { + self.child.as_ref().map(|c| c.as_ref()) + } + + pub fn last_child(&self) -> &CommandContext { + let mut result = self; + while let Some(child) = result.child() { + result = child; + } + result + } + + pub fn command(&self) -> &Command { + &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> { + 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>> { + &self.root_node + } + + pub fn nodes(&self) -> &[ParsedCommandNode] { + &self.nodes + } + pub fn has_nodes(&self) -> bool { !self.nodes.is_empty() } - pub fn argument(&self, name: &str) -> Option> { - let argument = self.arguments.get(name); - argument.map(|a| a.result.clone()) + pub fn is_forked(&self) -> bool { + self.forks } } diff --git a/azalea-brigadier/src/context/command_context_builder.rs b/azalea-brigadier/src/context/command_context_builder.rs index 87427bc1..f8d4334d 100644 --- a/azalea-brigadier/src/context/command_context_builder.rs +++ b/azalea-brigadier/src/context/command_context_builder.rs @@ -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 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) diff --git a/azalea-brigadier/src/context/context_chain.rs b/azalea-brigadier/src/context/context_chain.rs new file mode 100644 index 00000000..afafe957 --- /dev/null +++ b/azalea-brigadier/src/context/context_chain.rs @@ -0,0 +1,167 @@ +use std::{rc::Rc, sync::Arc}; + +use super::CommandContext; +use crate::{errors::CommandSyntaxError, result_consumer::ResultConsumer}; + +pub struct ContextChain { + modifiers: Vec>>, + executable: Rc>, + next_stage_cache: Option>>, +} + +impl ContextChain { + pub fn new(modifiers: Vec>>, executable: Rc>) -> 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>) -> Option { + 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>, + source: Arc, + result_consumer: &dyn ResultConsumer, + forked_mode: bool, + ) -> Result>, 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>, + source: Arc, + result_consumer: &dyn ResultConsumer, + forked_mode: bool, + ) -> Result { + 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, + result_consumer: &(dyn ResultConsumer), + ) -> Result { + 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> { + self.modifiers + .first() + .cloned() + .unwrap_or_else(|| self.executable.clone()) + } + + pub fn next_stage(&mut self) -> Option>> { + 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, +} diff --git a/azalea-brigadier/src/context/mod.rs b/azalea-brigadier/src/context/mod.rs index 28e1a12e..815892bf 100644 --- a/azalea-brigadier/src/context/mod.rs +++ b/azalea-brigadier/src/context/mod.rs @@ -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; diff --git a/azalea-brigadier/src/exceptions/builtin_exceptions.rs b/azalea-brigadier/src/errors/builtin_errors.rs similarity index 63% rename from azalea-brigadier/src/exceptions/builtin_exceptions.rs rename to azalea-brigadier/src/errors/builtin_errors.rs index bf2072c1..36803397 100644 --- a/azalea-brigadier/src/exceptions/builtin_exceptions.rs +++ b/azalea-brigadier/src/errors/builtin_errors.rs @@ -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()) } } diff --git a/azalea-brigadier/src/errors/command_syntax_error.rs b/azalea-brigadier/src/errors/command_syntax_error.rs new file mode 100644 index 00000000..a476fec4 --- /dev/null +++ b/azalea-brigadier/src/errors/command_syntax_error.rs @@ -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, + cursor: Option, +} + +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 { + 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 { + &self.input + } + + pub fn cursor(&self) -> Option { + self.cursor + } +} + +impl Debug for CommandSyntaxError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.message()) + } +} diff --git a/azalea-brigadier/src/errors/mod.rs b/azalea-brigadier/src/errors/mod.rs new file mode 100644 index 00000000..facebe5e --- /dev/null +++ b/azalea-brigadier/src/errors/mod.rs @@ -0,0 +1,5 @@ +mod builtin_errors; +mod command_syntax_error; + +pub use builtin_errors::BuiltInError; +pub use command_syntax_error::CommandSyntaxError; diff --git a/azalea-brigadier/src/exceptions/command_syntax_exception.rs b/azalea-brigadier/src/exceptions/command_syntax_exception.rs deleted file mode 100644 index 657649b0..00000000 --- a/azalea-brigadier/src/exceptions/command_syntax_exception.rs +++ /dev/null @@ -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, - cursor: Option, -} - -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 { - 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 { - &self.input - } - - pub fn cursor(&self) -> Option { - self.cursor - } -} - -impl fmt::Debug for CommandSyntaxException { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.message()) - } -} diff --git a/azalea-brigadier/src/exceptions/mod.rs b/azalea-brigadier/src/exceptions/mod.rs deleted file mode 100644 index 6b9c8d62..00000000 --- a/azalea-brigadier/src/exceptions/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod builtin_exceptions; -mod command_syntax_exception; - -pub use builtin_exceptions::BuiltInExceptions; -pub use command_syntax_exception::CommandSyntaxException; diff --git a/azalea-brigadier/src/lib.rs b/azalea-brigadier/src/lib.rs index 4f704d46..28f529f2 100644 --- a/azalea-brigadier/src/lib.rs +++ b/azalea-brigadier/src/lib.rs @@ -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; diff --git a/azalea-brigadier/src/modifier.rs b/azalea-brigadier/src/modifier.rs index bebdd0cb..aba5e95f 100644 --- a/azalea-brigadier/src/modifier.rs +++ b/azalea-brigadier/src/modifier.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::{context::CommandContext, exceptions::CommandSyntaxException}; +use crate::{context::CommandContext, errors::CommandSyntaxError}; pub type RedirectModifier = - dyn Fn(&CommandContext) -> Result>, CommandSyntaxException> + Send + Sync; + dyn Fn(&CommandContext) -> Result>, CommandSyntaxError> + Send + Sync; diff --git a/azalea-brigadier/src/parse_results.rs b/azalea-brigadier/src/parse_results.rs index a2cefcf7..7d3cf98a 100644 --- a/azalea-brigadier/src/parse_results.rs +++ b/azalea-brigadier/src/parse_results.rs @@ -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>, CommandSyntaxException>, + pub exceptions: HashMap>, CommandSyntaxError>, } impl 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) diff --git a/azalea-brigadier/src/result_consumer.rs b/azalea-brigadier/src/result_consumer.rs new file mode 100644 index 00000000..fb9dd135 --- /dev/null +++ b/azalea-brigadier/src/result_consumer.rs @@ -0,0 +1,12 @@ +use std::rc::Rc; + +use crate::context::CommandContext; + +pub trait ResultConsumer { + fn on_command_complete(&self, context: Rc>, success: bool, result: i32); +} + +pub struct DefaultResultConsumer; +impl ResultConsumer for DefaultResultConsumer { + fn on_command_complete(&self, _context: Rc>, _success: bool, _result: i32) {} +} diff --git a/azalea-brigadier/src/string_reader.rs b/azalea-brigadier/src/string_reader.rs index 963a2244..8dd41ed3 100644 --- a/azalea-brigadier/src/string_reader.rs +++ b/azalea-brigadier/src/string_reader.rs @@ -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 { + pub fn read_int(&mut self) -> Result { 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 { + pub fn read_long(&mut self) -> Result { 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 { + pub fn read_double(&mut self) -> Result { 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 { + pub fn read_float(&mut self) -> Result { 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 { + pub fn read_quoted_string(&mut self) -> Result { 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 { + pub fn read_string_until(&mut self, terminator: char) -> Result { 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 { + pub fn read_string(&mut self) -> Result { 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 { + pub fn read_boolean(&mut self) -> Result { 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(()) diff --git a/azalea-brigadier/src/suggestion/mod.rs b/azalea-brigadier/src/suggestion/mod.rs index a31b6837..af29264e 100644 --- a/azalea-brigadier/src/suggestion/mod.rs +++ b/azalea-brigadier/src/suggestion/mod.rs @@ -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 { + fn partial_cmp(&self, other: &Self) -> Option { 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() diff --git a/azalea-brigadier/src/suggestion/suggestion_provider.rs b/azalea-brigadier/src/suggestion/suggestion_provider.rs new file mode 100644 index 00000000..4c3a9f41 --- /dev/null +++ b/azalea-brigadier/src/suggestion/suggestion_provider.rs @@ -0,0 +1,10 @@ +use super::{Suggestions, SuggestionsBuilder}; +use crate::context::CommandContext; + +pub trait SuggestionProvider { + fn get_suggestions( + &self, + context: CommandContext, + builder: SuggestionsBuilder, + ) -> Suggestions; +} diff --git a/azalea-brigadier/src/suggestion/suggestions.rs b/azalea-brigadier/src/suggestion/suggestions.rs index 60eaa111..7f04d9d7 100644 --- a/azalea-brigadier/src/suggestion/suggestions.rs +++ b/azalea-brigadier/src/suggestion/suggestions.rs @@ -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)?; diff --git a/azalea-brigadier/src/tree/mod.rs b/azalea-brigadier/src/tree/mod.rs index 690e5df3..993b0698 100644 --- a/azalea-brigadier/src/tree/mod.rs +++ b/azalea-brigadier/src/tree/mod.rs @@ -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 = Option) -> i32 + Send + Sync>>; +pub type Command = + Option) -> Result + Send + Sync>>; /// An ArgumentBuilder that has been built. #[non_exhaustive] pub struct CommandNode { - pub value: ArgumentBuilderType, + pub value: ArgumentBuilderType, // this is a BTreeMap because children need to be ordered when getting command suggestions pub children: BTreeMap>>>, @@ -66,7 +67,7 @@ impl CommandNode { } /// 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 { match self.value { ArgumentBuilderType::Argument(ref argument) => argument, _ => panic!("CommandNode::argument() called on non-argument node"), @@ -149,7 +150,7 @@ impl CommandNode { &self, reader: &mut StringReader, context_builder: &mut CommandContextBuilder, - ) -> Result<(), CommandSyntaxException> { + ) -> Result<(), CommandSyntaxError> { match self.value { ArgumentBuilderType::Argument(ref argument) => { let start = reader.cursor(); @@ -176,7 +177,7 @@ impl CommandNode { return Ok(()); } - Err(BuiltInExceptions::LiteralIncorrect { + Err(BuiltInError::LiteralIncorrect { expected: literal.value.clone(), } .create_with_context(reader)) @@ -214,9 +215,7 @@ impl CommandNode { 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, + context: CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { match &self.value { @@ -231,15 +230,15 @@ impl CommandNode { Suggestions::default() } } - ArgumentBuilderType::Argument(argument) => argument.list_suggestions(builder), + ArgumentBuilderType::Argument(argument) => argument.list_suggestions(context, builder), } } } impl Debug for CommandNode { - 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 Default for CommandNode { } impl Hash for CommandNode { - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut H) { // hash the children for (k, v) in &self.children { k.hash(state); diff --git a/azalea-brigadier/tests/bevy_app_usage.rs b/azalea-brigadier/tests/bevy_app_usage.rs index 24a2e369..4520595a 100644 --- a/azalea-brigadier/tests/bevy_app_usage.rs +++ b/azalea-brigadier/tests/bevy_app_usage.rs @@ -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) -> i32 { - let num = context.argument("entities").unwrap(); - let num = *num.downcast_ref::().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>; fn deref(&self) -> &Self::Target { &self.world diff --git a/azalea-brigadier/tests/command_dispatcher_test.rs b/azalea-brigadier/tests/command_dispatcher_test.rs index eecbf668..f04ddfdf 100644 --- a/azalea-brigadier/tests/command_dispatcher_test.rs +++ b/azalea-brigadier/tests/command_dispatcher_test.rs @@ -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| -> Result>, CommandSyntaxException> { + let modifier = move |_: &CommandContext| -> Result>, 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)); } diff --git a/azalea-brigadier/tests/string_reader_test.rs b/azalea-brigadier/tests/string_reader_test.rs index de605e99..3b70043b 100644 --- a/azalea-brigadier/tests/string_reader_test.rs +++ b/azalea-brigadier/tests/string_reader_test.rs @@ -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)); } } diff --git a/azalea-brigadier/tests/suggestion/suggestions_test.rs b/azalea-brigadier/tests/suggestion/suggestions_test.rs index 987dfb71..0da9febc 100644 --- a/azalea-brigadier/tests/suggestion/suggestions_test.rs +++ b/azalea-brigadier/tests/suggestion/suggestions_test.rs @@ -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); } diff --git a/azalea-buf/src/read.rs b/azalea-buf/src/read.rs index b1b95f4d..143190b5 100644 --- a/azalea-buf/src/read.rs +++ b/azalea-buf/src/read.rs @@ -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 { diff --git a/azalea-buf/src/serializable_uuid.rs b/azalea-buf/src/serializable_uuid.rs index 10e57eeb..76737034 100644 --- a/azalea-buf/src/serializable_uuid.rs +++ b/azalea-buf/src/serializable_uuid.rs @@ -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)?; diff --git a/azalea-buf/src/write.rs b/azalea-buf/src/write.rs index 0f35dba8..a925647d 100644 --- a/azalea-buf/src/write.rs +++ b/azalea-buf/src/write.rs @@ -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::(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 AzaleaWrite for Vec { - 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 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 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 AzaleaWrite for [T] { } impl AzaleaWrite for HashMap { - 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 AzaleaWrite for HashMap { } impl AzaleaWriteVar for HashMap { - 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 AzaleaWriteVar for HashMap { } impl AzaleaWrite for Vec { - 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 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 AzaleaWriteVar for [T] { } } impl AzaleaWriteVar for Vec { - 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 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::(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::(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::(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::(buf, *self) } } impl AzaleaWrite for Option { - 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 AzaleaWrite for Option { } impl AzaleaWriteVar for Option { - 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 AzaleaWriteVar for Option { // [T; N] impl 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 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 AzaleaWrite for Box 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 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 AzaleaWrite for Arc { - 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) } } diff --git a/azalea-chat/src/component.rs b/azalea-chat/src/component.rs index 8eaaf178..c04aac4e 100644 --- a/azalea-chat/src/component.rs +++ b/azalea-chat/src/component.rs @@ -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 { + fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { 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), diff --git a/azalea-chat/src/numbers.rs b/azalea-chat/src/numbers.rs index 161f1177..d9499e26 100644 --- a/azalea-chat/src/numbers.rs +++ b/azalea-chat/src/numbers.rs @@ -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 } => { diff --git a/azalea-chat/src/style.rs b/azalea-chat/src/style.rs index 8b0c503c..18176a3d 100644 --- a/azalea-chat/src/style.rs +++ b/azalea-chat/src/style.rs @@ -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 diff --git a/azalea-chat/src/text_component.rs b/azalea-chat/src/text_component.rs index 62547d0d..bd598e16 100644 --- a/azalea-chat/src/text_component.rs +++ b/azalea-chat/src/text_component.rs @@ -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]
{END_SPAN}{BOLD_AQUA}HAPPY HOLIDAYS{END_SPAN}", END_SPAN = "", - GREEN = "", - RED = "", - BOLD_AQUA = "", + GREEN = "", + RED = "", + BOLD_AQUA = "", ) ); } @@ -207,8 +207,8 @@ mod tests { format!( "{GREEN}<b>&
{END_SPAN}{AQUA}</b>{END_SPAN}", END_SPAN = "
", - GREEN = "", - AQUA = "", + GREEN = "", + AQUA = "", ) ); } diff --git a/azalea-chat/src/translatable_component.rs b/azalea-chat/src/translatable_component.rs index 452de738..431c08b2 100644 --- a/azalea-chat/src/translatable_component.rs +++ b/azalea-chat/src/translatable_component.rs @@ -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}"), diff --git a/azalea-client/src/account.rs b/azalea-client/src/account.rs index 9b2c2350..7e3f917d 100644 --- a/azalea-client/src/account.rs +++ b/azalea-client/src/account.rs @@ -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; diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index dc9a3d3e..7b63ff12 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -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), - #[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> { @@ -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 { // send a StartJoinServerEvent let (start_join_callback_tx, mut start_join_callback_rx) = - mpsc::unbounded_channel::>(); + mpsc::unbounded_channel::(); 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::(|h| h.food); /// # } diff --git a/azalea-client/src/entity_query.rs b/azalea-client/src/entity_query.rs index 77be68a8..ad8a87a6 100644 --- a/azalea-client/src/entity_query.rs +++ b/azalea-client/src/entity_query.rs @@ -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::() + any::type_name::() ) }) } - /// 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::, (&GameProfileComponent,)>( @@ -59,11 +59,25 @@ impl Client { /// ``` /// /// [`Entity`]: bevy_ecs::entity::Entity + /// [`Instance`]: azalea_world::Instance pub fn entity_by( &self, predicate: impl EntityPredicate, ) -> Option { - predicate.find(self.ecs.clone()) + let instance_name = self.get_component::()?; + predicate.find(self.ecs.clone(), &instance_name) + } + + /// Same as [`Self::entity_by`] but returns a `Vec` of all entities + /// in our instance that match the predicate. + pub fn entities_by( + &self, + predicate: impl EntityPredicate, + ) -> Vec { + let Some(instance_name) = self.get_component::() 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::() + any::type_name::() ) }); components.clone() @@ -95,35 +109,29 @@ impl Client { } pub trait EntityPredicate { - fn find(&self, ecs_lock: Arc>) -> Option; + fn find(&self, ecs_lock: Arc>, instance_name: &InstanceName) -> Option; + fn find_all(&self, ecs_lock: Arc>, instance_name: &InstanceName) -> Vec; } -impl EntityPredicate for F +impl EntityPredicate for F where F: Fn(&ROQueryItem) -> bool, - Q: QueryData, - Filter: QueryFilter, { - fn find(&self, ecs_lock: Arc>) -> Option { + fn find(&self, ecs_lock: Arc>, instance_name: &InstanceName) -> Option { 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>, instance_name: &InstanceName) -> Vec { + 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::>() } } - -// impl<'a, F, Q1, Q2> EntityPredicate<'a, (Q1, Q2)> for F -// where -// F: Fn(&::Item<'_>, &::Item<'_>) -> -// bool, Q1: QueryFilter, -// Q2: QueryFilter, -// { -// fn find(&self, ecs: &mut Ecs) -> Option { -// // (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 -// } -// } diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index 31ad6e71..6bff353e 100644 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -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::*; diff --git a/azalea-client/src/local_player.rs b/azalea-client/src/local_player.rs index 26e08e9b..4a937ec7 100644 --- a/azalea-client/src/local_player.rs +++ b/azalea-client/src/local_player.rs @@ -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>, } -/// 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, } +impl From 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::(); /// println!("Online players:"); @@ -169,13 +172,13 @@ pub enum HandlePacketError { #[error(transparent)] Io(#[from] io::Error), #[error(transparent)] - Other(#[from] Box), + Other(#[from] Box), #[error("{0}")] Send(#[from] mpsc::error::SendError), } -impl From> for HandlePacketError { - fn from(e: std::sync::PoisonError) -> Self { +impl From> for HandlePacketError { + fn from(e: PoisonError) -> Self { HandlePacketError::Poison(e.to_string()) } } diff --git a/azalea-client/src/player.rs b/azalea-client/src/player.rs index d774e877..d696d133 100644 --- a/azalea-client/src/player.rs +++ b/azalea-client/src/player.rs @@ -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>, } +/// 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. diff --git a/azalea-client/src/plugins/auto_reconnect.rs b/azalea-client/src/plugins/auto_reconnect.rs index 52c6e94b..1391545f 100644 --- a/azalea-client/src/plugins/auto_reconnect.rs +++ b/azalea-client/src/plugins/auto_reconnect.rs @@ -70,11 +70,17 @@ fn get_delay( auto_reconnect_delay_query: Query<&AutoReconnectDelay>, entity: Entity, ) -> Option { - 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( diff --git a/azalea-client/src/plugins/chat/mod.rs b/azalea-client/src/plugins/chat/mod.rs index 7d42eb20..603dea60 100644 --- a/azalea-client/src/plugins/chat/mod.rs +++ b/azalea-client/src/plugins/chat/mod.rs @@ -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, diff --git a/azalea-client/src/plugins/chat_signing.rs b/azalea-client/src/plugins/chat_signing.rs index 9863cb3f..8961430e 100644 --- a/azalea-client/src/plugins/chat_signing.rs +++ b/azalea-client/src/plugins/chat_signing.rs @@ -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::(); - } + 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::(); } } } diff --git a/azalea-client/src/plugins/chunks.rs b/azalea-client/src/plugins/chunks.rs index 5e062887..7a99c16c 100644 --- a/azalea-client/src/plugins/chunks.rs +++ b/azalea-client/src/plugins/chunks.rs @@ -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; diff --git a/azalea-client/src/plugins/connection.rs b/azalea-client/src/plugins/connection.rs index 36aa2ee7..a929a4c7 100644 --- a/azalea-client/src/plugins/connection.rs +++ b/azalea-client/src/plugins/connection.rs @@ -169,9 +169,9 @@ pub struct RawConnection { /// /// To check if we haven't disconnected from the server, use /// [`Self::is_alive`]. - network: Option, + pub(crate) network: Option, 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 diff --git a/azalea-client/src/plugins/disconnect.rs b/azalea-client/src/plugins/disconnect.rs index c1c962d5..c6c01e35 100644 --- a/azalea-client/src/plugins/disconnect.rs +++ b/azalea-client/src/plugins/disconnect.rs @@ -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, diff --git a/azalea-client/src/plugins/events.rs b/azalea-client/src/plugins/events.rs index 090b9d0d..d9cbf912 100644 --- a/azalea-client/src/plugins/events.rs +++ b/azalea-client/src/plugins/events.rs @@ -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): diff --git a/azalea-client/src/plugins/interact.rs b/azalea-client/src/plugins/interact.rs index c623663e..712e3242 100644 --- a/azalea-client/src/plugins/interact.rs +++ b/azalea-client/src/plugins/interact.rs @@ -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, } pub fn handle_start_use_item_event( diff --git a/azalea-client/src/plugins/inventory.rs b/azalea-client/src/plugins/inventory.rs index 0a9c5cad..829b37f8 100644 --- a/azalea-client/src/plugins/inventory.rs +++ b/azalea-client/src/plugins/inventory.rs @@ -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(); } } } diff --git a/azalea-client/src/plugins/join.rs b/azalea-client/src/plugins/join.rs index e31c64c4..09eeff59 100644 --- a/azalea-client/src/plugins/join.rs +++ b/azalea-client/src/plugins/join.rs @@ -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>, - pub start_join_callback_tx: Option, + // this is mpsc instead of oneshot so it can be cloned (since it's sent in an event) + pub start_join_callback_tx: Option>, } /// 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>); - pub fn handle_start_join_server_event( mut commands: Commands, mut events: EventReader, @@ -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>); 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, ) { - 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::(); @@ -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, - 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())); - } -} diff --git a/azalea-client/src/plugins/login.rs b/azalea-client/src/plugins/login.rs index ebba5905..5cb2bce7 100644 --- a/azalea-client/src/plugins/login.rs +++ b/azalea-client/src/plugins/login.rs @@ -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>); +pub struct AuthTask(Task>); + +#[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, diff --git a/azalea-client/src/plugins/mining.rs b/azalea-client/src/plugins/mining.rs index 797c4361..204b482c 100644 --- a/azalea-client/src/plugins/mining.rs +++ b/azalea-client/src/plugins/mining.rs @@ -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, mut attack_block_events: EventWriter, mut mine_block_progress_events: EventWriter, 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::(); 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); #[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, +pub fn handle_finish_mining_block_observer( + trigger: Trigger, mut query: Query<( &InstanceName, &LocalGameMode, @@ -428,53 +445,48 @@ pub fn handle_finish_mining_block_event( )>, instances: Res, ) { - 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::::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::::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, - mut finish_mining_events: EventWriter, instances: Res, ) { 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::(); @@ -604,12 +619,16 @@ pub fn continue_mining_block( **mine_ticks += 1.; if **mine_progress >= 1. { - commands.entity(entity).remove::(); - 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, }); } } diff --git a/azalea-client/src/plugins/movement.rs b/azalea-client/src/plugins/movement.rs index 2d844064..b4649f20 100644 --- a/azalea-client/src/plugins/movement.rs +++ b/azalea-client/src/plugins/movement.rs @@ -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 for MovePlayerError { diff --git a/azalea-client/src/plugins/packet/config/mod.rs b/azalea-client/src/plugins/packet/config/mod.rs index 0bc700ec..3116e245 100644 --- a/azalea-client/src/plugins/packet/config/mod.rs +++ b/azalea-client/src/plugins/packet/config/mod.rs @@ -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, diff --git a/azalea-client/src/plugins/packet/game/events.rs b/azalea-client/src/plugins/packet/game/events.rs index 707d2cd9..7134a2f2 100644 --- a/azalea-client/src/plugins/packet/game/events.rs +++ b/azalea-client/src/plugins/packet/game/events.rs @@ -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) { -/// for ReceiveGamePacketEvent { -/// entity, -/// packet, -/// } in events.read() { +/// for ReceiveGamePacketEvent { entity, packet } in events.read() { /// match packet.as_ref() { /// ClientboundGamePacket::LevelParticles(p) => { /// // ... diff --git a/azalea-client/src/plugins/packet/game/mod.rs b/azalea-client/src/plugins/packet/game/mod.rs index a7c949c8..afb906f2 100644 --- a/azalea-client/src/plugins/packet/game/mod.rs +++ b/azalea-client/src/plugins/packet/game/mod.rs @@ -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::>(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(); diff --git a/azalea-client/src/plugins/packet/login/mod.rs b/azalea-client/src/plugins/packet/login/mod.rs index 3dad17ab..99ebae9b 100644 --- a/azalea-client/src/plugins/packet/login/mod.rs +++ b/azalea-client/src/plugins/packet/login/mod.rs @@ -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) { diff --git a/azalea-client/src/test_simulation.rs b/azalea-client/src/test_simulation.rs index e154a9d9..c53a624a 100644 --- a/azalea-client/src/test_simulation.rs +++ b/azalea-client/src/test_simulation.rs @@ -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::::new(), + biomes: PalettedContainer::::new(), }); } sections.azalea_write(&mut chunk_bytes).unwrap(); diff --git a/azalea-client/tests/login_to_dimension_with_same_name.rs b/azalea-client/tests/login_to_dimension_with_same_name.rs index be362bb7..24637693 100644 --- a/azalea-client/tests/login_to_dimension_with_same_name.rs +++ b/azalea-client/tests/login_to_dimension_with_same_name.rs @@ -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::{ diff --git a/azalea-client/tests/reply_to_ping_with_pong.rs b/azalea-client/tests/reply_to_ping_with_pong.rs index d921c905..bc8bccd4 100644 --- a/azalea-client/tests/reply_to_ping_with_pong.rs +++ b/azalea-client/tests/reply_to_ping_with_pong.rs @@ -28,11 +28,11 @@ fn reply_to_ping_with_pong() { simulation .app .add_observer(move |trigger: Trigger| { - 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| { - 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; } }); diff --git a/azalea-core/src/bitset.rs b/azalea-core/src/bitset.rs index 5af05e19..1cac4a9c 100644 --- a/azalea-core/src/bitset.rs +++ b/azalea-core/src/bitset.rs @@ -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> 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: -/// -/// -/// 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 { - data: [u8; N], +pub struct FixedBitSet +where + [u8; bits_to_bytes(N)]: Sized, +{ + data: [u8; bits_to_bytes(N)], } -impl FixedBitSet { - pub fn new() -> Self { - FixedBitSet { data: [0; N] } +impl FixedBitSet +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 FixedBitSet { } } -impl AzaleaRead for FixedBitSet { +impl AzaleaRead for FixedBitSet +where + [u8; bits_to_bytes(N)]: Sized, +{ fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { - 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 AzaleaWrite for FixedBitSet { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - for i in 0..N { +impl AzaleaWrite for FixedBitSet +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 Default for FixedBitSet { +impl Default for FixedBitSet +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::*; diff --git a/azalea-core/src/difficulty.rs b/azalea-core/src/difficulty.rs index b907bbb3..f82f0e9d 100644 --- a/azalea-core/src/difficulty.rs +++ b/azalea-core/src/difficulty.rs @@ -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) } } diff --git a/azalea-core/src/filterable.rs b/azalea-core/src/filterable.rs index 70b0d6f7..1c432b28 100644 --- a/azalea-core/src/filterable.rs +++ b/azalea-core/src/filterable.rs @@ -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 { } impl azalea_buf::AzaleaWrite for Filterable { - 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(()) diff --git a/azalea-core/src/game_type.rs b/azalea-core/src/game_type.rs index 5d9ee8f5..7328902a 100644 --- a/azalea-core/src/game_type.rs +++ b/azalea-core/src/game_type.rs @@ -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) } } diff --git a/azalea-core/src/lib.rs b/azalea-core/src/lib.rs index 6c5e57c1..9f6e9386 100644 --- a/azalea-core/src/lib.rs +++ b/azalea-core/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(incomplete_features)] +#![feature(generic_const_exprs)] #![doc = include_str!("../README.md")] pub mod aabb; diff --git a/azalea-core/src/objectives.rs b/azalea-core/src/objectives.rs index a2f0d517..be5ea252 100644 --- a/azalea-core/src/objectives.rs +++ b/azalea-core/src/objectives.rs @@ -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"), diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs index e82e5e4a..5932cb5b 100644 --- a/azalea-core/src/position.rs +++ b/azalea-core/src/position.rs @@ -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(&self, state: &mut H) { + fn hash(&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(&self, state: &mut H) { + fn hash(&self, state: &mut H) { u64::from(*self).hash(state); } } @@ -546,8 +539,8 @@ impl From 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 for ChunkSectionPos { type Output = BlockPos; @@ -570,7 +607,7 @@ impl Add for ChunkSectionPos { impl Hash for ChunkSectionBlockPos { // optimized hash that only calls hash once #[inline] - fn hash(&self, state: &mut H) { + fn hash(&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); diff --git a/azalea-core/src/resource_location.rs b/azalea-core/src/resource_location.rs index 0b21a43b..a4309035 100644 --- a/azalea-core/src/resource_location.rs +++ b/azalea-core/src/resource_location.rs @@ -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) } } diff --git a/azalea-crypto/src/lib.rs b/azalea-crypto/src/lib.rs index b087c426..4f780431 100644 --- a/azalea-crypto/src/lib.rs +++ b/azalea-crypto/src/lib.rs @@ -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}; diff --git a/azalea-entity/src/attributes.rs b/azalea-entity/src/attributes.rs index d3881147..12c9b908 100644 --- a/azalea-entity/src/attributes.rs +++ b/azalea-entity/src/attributes.rs @@ -1,4 +1,4 @@ -//! See . +//! See . use std::collections::{HashMap, hash_map}; diff --git a/azalea-entity/src/data.rs b/azalea-entity/src/data.rs index c92d1c95..24e31713 100644 --- a/azalea-entity/src/data.rs +++ b/azalea-entity/src/data.rs @@ -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), diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs index 0f49039e..44a4a9ee 100644 --- a/azalea-entity/src/lib.rs +++ b/azalea-entity/src/lib.rs @@ -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) } } diff --git a/azalea-entity/src/metadata.rs b/azalea-entity/src/metadata.rs index f32d831a..c20233cc 100644 --- a/azalea-entity/src/metadata.rs +++ b/azalea-entity/src/metadata.rs @@ -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; diff --git a/azalea-entity/src/particle.rs b/azalea-entity/src/particle.rs index 6bc1ef6f..212d9668 100644 --- a/azalea-entity/src/particle.rs +++ b/azalea-entity/src/particle.rs @@ -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, diff --git a/azalea-entity/src/plugin/indexing.rs b/azalea-entity/src/plugin/indexing.rs index 0f1e036e..2fc89e84 100644 --- a/azalea-entity/src/plugin/indexing.rs +++ b/azalea-entity/src/plugin/indexing.rs @@ -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() } } diff --git a/azalea-inventory/src/components.rs b/azalea-inventory/src/components.rs index 9e0a37b0..0440afe3 100644 --- a/azalea-inventory/src/components.rs +++ b/azalea-inventory/src/components.rs @@ -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) -> Result<(), std::io::Error>; + fn encode(&self, buf: &mut Vec) -> io::Result<()>; // using the Clone trait makes it not be object-safe, so we have our own clone // function instead fn clone(&self) -> Box; - // same deal here + // same thing here fn eq(&self, other: Box) -> bool; } @@ -35,7 +39,7 @@ impl EncodableDataComponent for T where T: DataComponent + Clone + AzaleaWrite + AzaleaRead + PartialEq, { - fn encode(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + fn encode(&self, buf: &mut Vec) -> io::Result<()> { self.azalea_write(buf) } fn clone(&self) -> Box { @@ -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 diff --git a/azalea-inventory/src/slot.rs b/azalea-inventory/src/slot.rs index e2e84a68..66f8bf50 100644 --- a/azalea-inventory/src/slot.rs +++ b/azalea-inventory/src/slot.rs @@ -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() { diff --git a/azalea-physics/src/clip.rs b/azalea-physics/src/clip.rs index e557b028..e7d203d8 100644 --- a/azalea-physics/src/clip.rs +++ b/azalea-physics/src/clip.rs @@ -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) diff --git a/azalea-physics/src/collision/blocks.rs b/azalea-physics/src/collision/blocks.rs index 2953f3e7..486b93a0 100644 --- a/azalea-physics/src/collision/blocks.rs +++ b/azalea-physics/src/collision/blocks.rs @@ -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)] diff --git a/azalea-physics/src/collision/discrete_voxel_shape.rs b/azalea-physics/src/collision/discrete_voxel_shape.rs index cacbc987..a56c8085 100644 --- a/azalea-physics/src/collision/discrete_voxel_shape.rs +++ b/azalea-physics/src/collision/discrete_voxel_shape.rs @@ -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 diff --git a/azalea-physics/src/collision/world_collisions.rs b/azalea-physics/src/collision/world_collisions.rs index ded31275..11e8946f 100644 --- a/azalea-physics/src/collision/world_collisions.rs +++ b/azalea-physics/src/collision/world_collisions.rs @@ -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 { diff --git a/azalea-protocol/azalea-protocol-macros/src/lib.rs b/azalea-protocol/azalea-protocol-macros/src/lib.rs index a1255519..c11dd6d5 100644 --- a/azalea-protocol/azalea-protocol-macros/src/lib.rs +++ b/azalea-protocol/azalea-protocol-macros/src/lib.rs @@ -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 } diff --git a/azalea-protocol/examples/handshake_proxy.rs b/azalea-protocol/examples/handshake_proxy.rs index 0161258a..cfe4af52 100644 --- a/azalea-protocol/examples/handshake_proxy.rs +++ b/azalea-protocol/examples/handshake_proxy.rs @@ -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"; diff --git a/azalea-protocol/src/common/client_information.rs b/azalea-protocol/src/common/client_information.rs index f2290fe7..690d052a 100644 --- a/azalea-protocol/src/common/client_information.rs +++ b/azalea-protocol/src/common/client_information.rs @@ -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 { - let set = FixedBitSet::<{ 7_usize.div_ceil(8) }>::azalea_read(buf)?; + fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { + 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); } diff --git a/azalea-protocol/src/common/movements.rs b/azalea-protocol/src/common/movements.rs index a3898805..7ab7fc2f 100644 --- a/azalea-protocol/src/common/movements.rs +++ b/azalea-protocol/src/common/movements.rs @@ -32,7 +32,7 @@ pub struct RelativeMovements { impl AzaleaRead for RelativeMovements { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { // 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); diff --git a/azalea-protocol/src/connect.rs b/azalea-protocol/src/connect.rs index bd8cf115..78ad6d81 100644 --- a/azalea-protocol/src/connect.rs +++ b/azalea-protocol/src/connect.rs @@ -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 { /// 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 { /// 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 { /// 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 { /// 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 { /// /// ```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> { - /// 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::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); /// } /// _ => {} diff --git a/azalea-protocol/src/lib.rs b/azalea-protocol/src/lib.rs index 7ac8756e..fd092293 100644 --- a/azalea-protocol/src/lib.rs +++ b/azalea-protocol/src/lib.rs @@ -12,7 +12,11 @@ // this is necessary for thiserror backtraces #![feature(error_generic_member_access)] -use std::{fmt::Display, net::SocketAddr, str::FromStr}; +use std::{ + fmt::{self, Display}, + net::SocketAddr, + str::FromStr, +}; pub mod common; #[cfg(feature = "connecting")] @@ -79,7 +83,7 @@ impl From for ServerAddress { } impl Display for ServerAddress { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}:{}", self.host, self.port) } } diff --git a/azalea-protocol/src/packets/config/c_update_tags.rs b/azalea-protocol/src/packets/config/c_update_tags.rs index 678a5ccf..750e3499 100644 --- a/azalea-protocol/src/packets/config/c_update_tags.rs +++ b/azalea-protocol/src/packets/config/c_update_tags.rs @@ -1,4 +1,4 @@ -use std::io::Cursor; +use std::io::{self, Cursor}; use std::ops::Deref; use std::{collections::HashMap, io::Write}; @@ -40,7 +40,7 @@ impl AzaleaRead for TagMap { } impl AzaleaWrite for TagMap { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { (self.len() as u32).azalea_write_var(buf)?; for (k, v) in &self.0 { k.azalea_write(buf)?; @@ -58,7 +58,7 @@ impl AzaleaRead for Tags { } impl AzaleaWrite for Tags { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { self.name.azalea_write(buf)?; self.elements.azalea_write_var(buf)?; Ok(()) diff --git a/azalea-protocol/src/packets/config/mod.rs b/azalea-protocol/src/packets/config/mod.rs index 46624431..58e00ad5 100644 --- a/azalea-protocol/src/packets/config/mod.rs +++ b/azalea-protocol/src/packets/config/mod.rs @@ -1,4 +1,4 @@ -// NOTE: This file is generated automatically by codegen/packet.py. +// NOTE: This file is @generated automatically by codegen/packet.py. // Don't edit it directly! use azalea_protocol_macros::declare_state_packets; diff --git a/azalea-protocol/src/packets/game/c_add_entity.rs b/azalea-protocol/src/packets/game/c_add_entity.rs index 008d6d28..b2370f3f 100644 --- a/azalea-protocol/src/packets/game/c_add_entity.rs +++ b/azalea-protocol/src/packets/game/c_add_entity.rs @@ -22,7 +22,7 @@ pub struct ClientboundAddEntity { /// encouraged to use [`MinecraftEntityId::from`] for. Other entities may /// treat it as a block state or enum variant. /// - /// See [the wiki](https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Object_Data) + /// See [the wiki](https://minecraft.wiki/w/Java_Edition_protocol/Object_data) /// for more information about this field. #[var] pub data: u32, diff --git a/azalea-protocol/src/packets/game/c_boss_event.rs b/azalea-protocol/src/packets/game/c_boss_event.rs index 58d9f4bb..18984985 100644 --- a/azalea-protocol/src/packets/game/c_boss_event.rs +++ b/azalea-protocol/src/packets/game/c_boss_event.rs @@ -1,3 +1,4 @@ +use std::io; use std::io::Cursor; use std::io::Write; @@ -36,14 +37,14 @@ impl AzaleaRead for Operation { _ => { return Err(BufReadError::UnexpectedEnumVariant { id: operation_id as i32, - }) + }); } }) } } impl AzaleaWrite for Operation { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { match self { Operation::Add(add) => { 0u32.azalea_write_var(buf)?; @@ -116,7 +117,7 @@ pub struct Properties { impl AzaleaRead for Properties { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { - let set = FixedBitSet::<{ 3_usize.div_ceil(8) }>::azalea_read(buf)?; + let set = FixedBitSet::<3>::azalea_read(buf)?; Ok(Self { darken_screen: set.index(0), play_music: set.index(1), @@ -126,8 +127,8 @@ impl AzaleaRead for Properties { } impl AzaleaWrite for Properties { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - let mut set = FixedBitSet::<{ 3_usize.div_ceil(8) }>::new(); + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { + let mut set = FixedBitSet::<3>::new(); if self.darken_screen { set.set(0); } diff --git a/azalea-protocol/src/packets/game/c_commands.rs b/azalea-protocol/src/packets/game/c_commands.rs index 9bb5b955..ae9ee803 100644 --- a/azalea-protocol/src/packets/game/c_commands.rs +++ b/azalea-protocol/src/packets/game/c_commands.rs @@ -1,4 +1,4 @@ -use std::io::{Cursor, Write}; +use std::io::{self, Cursor, Write}; use azalea_buf::{AzBuf, AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError}; use azalea_core::{bitset::FixedBitSet, resource_location::ResourceLocation}; @@ -46,7 +46,7 @@ impl PartialEq for BrigadierNumber { impl AzaleaRead for BrigadierNumber { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { - let flags = FixedBitSet::<{ 2_usize.div_ceil(8) }>::azalea_read(buf)?; + let flags = FixedBitSet::<2>::azalea_read(buf)?; let min = if flags.index(0) { Some(T::azalea_read(buf)?) } else { @@ -61,8 +61,8 @@ impl AzaleaRead for BrigadierNumber { } } impl AzaleaWrite for BrigadierNumber { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - let mut flags = FixedBitSet::<{ 2_usize.div_ceil(8) }>::new(); + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { + let mut flags = FixedBitSet::<2>::new(); if self.min.is_some() { flags.set(0); } @@ -159,7 +159,7 @@ pub struct EntityParser { } impl AzaleaRead for EntityParser { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { - let flags = FixedBitSet::<{ 2_usize.div_ceil(8) }>::azalea_read(buf)?; + let flags = FixedBitSet::<2>::azalea_read(buf)?; Ok(EntityParser { single: flags.index(0), players_only: flags.index(1), @@ -167,8 +167,8 @@ impl AzaleaRead for EntityParser { } } impl AzaleaWrite for EntityParser { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - let mut flags = FixedBitSet::<{ 2_usize.div_ceil(8) }>::new(); + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { + let mut flags = FixedBitSet::<2>::new(); if self.single { flags.set(0); } @@ -183,7 +183,7 @@ impl AzaleaWrite for EntityParser { // TODO: BrigadierNodeStub should have more stuff impl AzaleaRead for BrigadierNodeStub { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { - let flags = FixedBitSet::<{ 8_usize.div_ceil(8) }>::azalea_read(buf)?; + let flags = FixedBitSet::<8>::azalea_read(buf)?; if flags.index(5) || flags.index(6) || flags.index(7) { warn!( "The flags from a Brigadier node are over 31. This is a bug, BrigadierParser probably needs updating.", @@ -243,8 +243,8 @@ impl AzaleaRead for BrigadierNodeStub { } impl AzaleaWrite for BrigadierNodeStub { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - let mut flags = FixedBitSet::<{ 4_usize.div_ceil(8) }>::new(); + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { + let mut flags = FixedBitSet::<4>::new(); if self.is_executable { flags.set(2); } diff --git a/azalea-protocol/src/packets/game/c_damage_event.rs b/azalea-protocol/src/packets/game/c_damage_event.rs index d4393685..b7fd15c1 100644 --- a/azalea-protocol/src/packets/game/c_damage_event.rs +++ b/azalea-protocol/src/packets/game/c_damage_event.rs @@ -1,4 +1,4 @@ -use std::io::{Cursor, Write}; +use std::io::{self, Cursor, Write}; use azalea_buf::{AzBuf, AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar}; use azalea_core::position::Vec3; @@ -27,7 +27,7 @@ impl AzaleaRead for OptionalEntityId { } } impl AzaleaWrite for OptionalEntityId { - 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(id) => (id + 1).azalea_write_var(buf), None => 0u32.azalea_write_var(buf), diff --git a/azalea-protocol/src/packets/game/c_level_particles.rs b/azalea-protocol/src/packets/game/c_level_particles.rs index d54315ab..3f11bef2 100644 --- a/azalea-protocol/src/packets/game/c_level_particles.rs +++ b/azalea-protocol/src/packets/game/c_level_particles.rs @@ -27,9 +27,7 @@ mod tests { #[test] fn test_c_level_particles_packet() { #[rustfmt::skip] - let slice = [ - 0, 0, 64, 156, 51, 153, 153, 153, 153, 154, 192, 64, 140, 204, 204, 204, 204, 205, 63, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 13, 255, 0, 255, 255, 63, 128, 0, 0 - ]; + let slice = [0, 0, 192, 159, 104, 133, 28, 126, 5, 107, 192, 59, 0, 0, 0, 0, 0, 0, 64, 140, 27, 255, 120, 249, 188, 204, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 47, 1, 251, 245, 15, 64, 29, 194, 144, 12]; let mut bytes = Cursor::new(slice.as_slice()); let packet = ClientboundLevelParticles::azalea_read(&mut bytes).unwrap(); diff --git a/azalea-protocol/src/packets/game/c_map_item_data.rs b/azalea-protocol/src/packets/game/c_map_item_data.rs index 9a41ed85..f8c036e3 100644 --- a/azalea-protocol/src/packets/game/c_map_item_data.rs +++ b/azalea-protocol/src/packets/game/c_map_item_data.rs @@ -1,3 +1,5 @@ +use std::io::{self, Cursor, Write}; + use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite}; use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundGamePacket; @@ -27,7 +29,7 @@ pub struct MapDecoration { pub struct OptionalMapPatch(pub Option); impl AzaleaRead for OptionalMapPatch { - fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> Result { + fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { let pos = buf.position(); Ok(Self(if u8::azalea_read(buf)? == 0 { None @@ -39,7 +41,7 @@ impl AzaleaRead for OptionalMapPatch { } impl AzaleaWrite for OptionalMapPatch { - fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { match &self.0 { None => 0u8.azalea_write(buf), Some(m) => m.azalea_write(buf), diff --git a/azalea-protocol/src/packets/game/c_player_abilities.rs b/azalea-protocol/src/packets/game/c_player_abilities.rs index fffd320c..3f4e1024 100644 --- a/azalea-protocol/src/packets/game/c_player_abilities.rs +++ b/azalea-protocol/src/packets/game/c_player_abilities.rs @@ -1,4 +1,4 @@ -use std::io::{Cursor, Write}; +use std::io::{self, Cursor, Write}; use azalea_buf::{AzBuf, BufReadError}; use azalea_buf::{AzaleaRead, AzaleaWrite}; @@ -23,7 +23,7 @@ pub struct PlayerAbilitiesFlags { impl AzaleaRead for PlayerAbilitiesFlags { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { - let set = FixedBitSet::<{ 4_usize.div_ceil(8) }>::azalea_read(buf)?; + let set = FixedBitSet::<4>::azalea_read(buf)?; Ok(PlayerAbilitiesFlags { invulnerable: set.index(0), flying: set.index(1), @@ -34,8 +34,8 @@ impl AzaleaRead for PlayerAbilitiesFlags { } impl AzaleaWrite for PlayerAbilitiesFlags { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - let mut set = FixedBitSet::<{ 4_usize.div_ceil(8) }>::new(); + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { + let mut set = FixedBitSet::<4>::new(); if self.invulnerable { set.set(0); } diff --git a/azalea-protocol/src/packets/game/c_player_chat.rs b/azalea-protocol/src/packets/game/c_player_chat.rs index 77dce487..d44e5f59 100644 --- a/azalea-protocol/src/packets/game/c_player_chat.rs +++ b/azalea-protocol/src/packets/game/c_player_chat.rs @@ -1,4 +1,4 @@ -use std::io::{Cursor, Write}; +use std::io::{self, Cursor, Write}; use azalea_buf::{AzBuf, AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError}; use azalea_chat::{ @@ -142,7 +142,7 @@ impl AzaleaRead for PackedMessageSignature { } } impl AzaleaWrite for PackedMessageSignature { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { match self { PackedMessageSignature::Signature(full_signature) => { 0u32.azalea_write_var(buf)?; diff --git a/azalea-protocol/src/packets/game/c_player_info_update.rs b/azalea-protocol/src/packets/game/c_player_info_update.rs index db3d38a5..0041b483 100644 --- a/azalea-protocol/src/packets/game/c_player_info_update.rs +++ b/azalea-protocol/src/packets/game/c_player_info_update.rs @@ -1,6 +1,6 @@ use std::{ collections::HashMap, - io::{Cursor, Write}, + io::{self, Cursor, Write}, }; use azalea_auth::game_profile::{GameProfile, ProfilePropertyValue}; @@ -119,7 +119,7 @@ impl AzaleaRead for ClientboundPlayerInfoUpdate { } impl AzaleaWrite for ClientboundPlayerInfoUpdate { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { self.actions.azalea_write(buf)?; (self.entries.len() as u32).azalea_write_var(buf)?; @@ -183,7 +183,7 @@ pub struct ActionEnumSet { impl AzaleaRead for ActionEnumSet { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { - let set = FixedBitSet::<{ 7_usize.div_ceil(8) }>::azalea_read(buf)?; + let set = FixedBitSet::<7>::azalea_read(buf)?; Ok(ActionEnumSet { add_player: set.index(0), initialize_chat: set.index(1), @@ -198,8 +198,8 @@ impl AzaleaRead for ActionEnumSet { } impl AzaleaWrite for ActionEnumSet { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - let mut set = FixedBitSet::<{ 7_usize.div_ceil(8) }>::new(); + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { + let mut set = FixedBitSet::<7>::new(); if self.add_player { set.set(0); } diff --git a/azalea-protocol/src/packets/game/c_section_blocks_update.rs b/azalea-protocol/src/packets/game/c_section_blocks_update.rs index 2a7a867e..b9d07db3 100644 --- a/azalea-protocol/src/packets/game/c_section_blocks_update.rs +++ b/azalea-protocol/src/packets/game/c_section_blocks_update.rs @@ -1,4 +1,4 @@ -use std::io::{Cursor, Write}; +use std::io::{self, Cursor, Write}; use azalea_block::BlockState; use azalea_buf::{AzBuf, AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError}; @@ -34,7 +34,7 @@ impl AzaleaRead for BlockStateWithPosition { } impl AzaleaWrite for BlockStateWithPosition { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { let data = ((self.state.id() as u64) << 12) | ((u64::from(self.pos.x) << 8) | (u64::from(self.pos.z) << 4) | u64::from(self.pos.y)); u64::azalea_write_var(&data, buf)?; diff --git a/azalea-protocol/src/packets/game/c_set_equipment.rs b/azalea-protocol/src/packets/game/c_set_equipment.rs index ad691d66..c80e0072 100644 --- a/azalea-protocol/src/packets/game/c_set_equipment.rs +++ b/azalea-protocol/src/packets/game/c_set_equipment.rs @@ -1,4 +1,4 @@ -use std::io::Cursor; +use std::io::{self, Cursor, Write}; use azalea_buf::{AzBuf, BufReadError}; use azalea_buf::{AzaleaRead, AzaleaWrite}; @@ -41,7 +41,7 @@ impl AzaleaRead for EquipmentSlots { } } impl AzaleaWrite for EquipmentSlots { - fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { for i in 0..self.slots.len() { let (equipment_slot, item) = &self.slots[i]; let mut equipment_byte = *equipment_slot as u8; diff --git a/azalea-protocol/src/packets/game/c_set_objective.rs b/azalea-protocol/src/packets/game/c_set_objective.rs index 7ddb3f71..0f5ffdc1 100644 --- a/azalea-protocol/src/packets/game/c_set_objective.rs +++ b/azalea-protocol/src/packets/game/c_set_objective.rs @@ -1,7 +1,7 @@ -use std::io::{Cursor, Write}; +use std::io::{self, Cursor, Write}; use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite}; -use azalea_chat::{numbers::NumberFormat, FormattedText}; +use azalea_chat::{FormattedText, numbers::NumberFormat}; use azalea_core::objectives::ObjectiveCriteria; use azalea_protocol_macros::ClientboundGamePacket; @@ -53,7 +53,7 @@ impl AzaleaRead for Method { } impl AzaleaWrite for Method { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { match self { Method::Add { display_name, diff --git a/azalea-protocol/src/packets/game/c_stop_sound.rs b/azalea-protocol/src/packets/game/c_stop_sound.rs index aa92c23e..da689882 100644 --- a/azalea-protocol/src/packets/game/c_stop_sound.rs +++ b/azalea-protocol/src/packets/game/c_stop_sound.rs @@ -1,4 +1,4 @@ -use std::io::{Cursor, Write}; +use std::io::{self, Cursor, Write}; use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError}; use azalea_core::{bitset::FixedBitSet, resource_location::ResourceLocation}; @@ -14,7 +14,7 @@ pub struct ClientboundStopSound { impl AzaleaRead for ClientboundStopSound { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { - let set = FixedBitSet::<{ 2_usize.div_ceil(8) }>::azalea_read(buf)?; + let set = FixedBitSet::<2>::azalea_read(buf)?; let source = if set.index(0) { Some(SoundSource::azalea_read(buf)?) } else { @@ -31,8 +31,8 @@ impl AzaleaRead for ClientboundStopSound { } impl AzaleaWrite for ClientboundStopSound { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - let mut set = FixedBitSet::<{ 2_usize.div_ceil(8) }>::new(); + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { + let mut set = FixedBitSet::<2>::new(); if self.source.is_some() { set.set(0); } diff --git a/azalea-protocol/src/packets/game/c_update_advancements.rs b/azalea-protocol/src/packets/game/c_update_advancements.rs index 1c645965..43542e56 100644 --- a/azalea-protocol/src/packets/game/c_update_advancements.rs +++ b/azalea-protocol/src/packets/game/c_update_advancements.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::io::Cursor; +use std::io::{self, Cursor, Write}; use azalea_buf::AzBuf; use azalea_chat::FormattedText; @@ -38,7 +38,7 @@ pub struct DisplayInfo { } impl azalea_buf::AzaleaWrite for DisplayInfo { - 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.title.azalea_write(buf)?; self.description.azalea_write(buf)?; self.icon.azalea_write(buf)?; diff --git a/azalea-protocol/src/packets/game/c_update_tags.rs b/azalea-protocol/src/packets/game/c_update_tags.rs index 4b63ec8f..108f8772 100644 --- a/azalea-protocol/src/packets/game/c_update_tags.rs +++ b/azalea-protocol/src/packets/game/c_update_tags.rs @@ -1,4 +1,4 @@ -use std::io::Cursor; +use std::io::{self, Cursor}; use std::ops::Deref; use std::{collections::HashMap, io::Write}; @@ -40,7 +40,7 @@ impl AzaleaRead for TagMap { } impl AzaleaWrite for TagMap { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { (self.len() as u32).azalea_write_var(buf)?; for (k, v) in &self.0 { k.azalea_write(buf)?; @@ -58,7 +58,7 @@ impl AzaleaRead for Tags { } impl AzaleaWrite for Tags { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { self.name.azalea_write(buf)?; self.elements.azalea_write_var(buf)?; Ok(()) diff --git a/azalea-protocol/src/packets/game/mod.rs b/azalea-protocol/src/packets/game/mod.rs index 3616a231..264892b0 100644 --- a/azalea-protocol/src/packets/game/mod.rs +++ b/azalea-protocol/src/packets/game/mod.rs @@ -1,4 +1,4 @@ -// NOTE: This file is generated automatically by codegen/packet.py. +// NOTE: This file is @generated automatically by codegen/packet.py. // Don't edit it directly! use azalea_protocol_macros::declare_state_packets; diff --git a/azalea-protocol/src/packets/game/s_chat.rs b/azalea-protocol/src/packets/game/s_chat.rs index 07702ddf..f4049243 100644 --- a/azalea-protocol/src/packets/game/s_chat.rs +++ b/azalea-protocol/src/packets/game/s_chat.rs @@ -17,6 +17,6 @@ pub struct ServerboundChat { pub struct LastSeenMessagesUpdate { #[var] pub offset: u32, - pub acknowledged: FixedBitSet<{ 20_usize.div_ceil(8) }>, + pub acknowledged: FixedBitSet<20>, pub checksum: u8, } diff --git a/azalea-protocol/src/packets/game/s_interact.rs b/azalea-protocol/src/packets/game/s_interact.rs index a3007749..762afe4e 100644 --- a/azalea-protocol/src/packets/game/s_interact.rs +++ b/azalea-protocol/src/packets/game/s_interact.rs @@ -1,4 +1,4 @@ -use std::io::{Cursor, Write}; +use std::io::{self, Cursor, Write}; use azalea_buf::{AzBuf, AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar}; use azalea_core::position::Vec3; @@ -29,7 +29,7 @@ pub enum ActionType { } impl AzaleaWrite for ActionType { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { match self { ActionType::Interact { hand } => { 0u32.azalea_write_var(buf)?; diff --git a/azalea-protocol/src/packets/game/s_player_abilities.rs b/azalea-protocol/src/packets/game/s_player_abilities.rs index 5e63ef2e..da194228 100644 --- a/azalea-protocol/src/packets/game/s_player_abilities.rs +++ b/azalea-protocol/src/packets/game/s_player_abilities.rs @@ -1,4 +1,4 @@ -use std::io::Cursor; +use std::io::{self, Cursor, Write}; use azalea_buf::{AzaleaRead, AzaleaWrite}; use azalea_core::bitset::FixedBitSet; @@ -13,7 +13,7 @@ pub struct ServerboundPlayerAbilities { impl AzaleaRead for ServerboundPlayerAbilities { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { - let set = FixedBitSet::<{ 2_usize.div_ceil(8) }>::azalea_read(buf)?; + let set = FixedBitSet::<2>::azalea_read(buf)?; Ok(Self { is_flying: set.index(1), }) @@ -21,8 +21,8 @@ impl AzaleaRead for ServerboundPlayerAbilities { } impl AzaleaWrite for ServerboundPlayerAbilities { - fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { - let mut set = FixedBitSet::<{ 2_usize.div_ceil(8) }>::new(); + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { + let mut set = FixedBitSet::<2>::new(); if self.is_flying { set.set(1); } diff --git a/azalea-protocol/src/packets/game/s_player_input.rs b/azalea-protocol/src/packets/game/s_player_input.rs index a16a62d0..a12262b4 100644 --- a/azalea-protocol/src/packets/game/s_player_input.rs +++ b/azalea-protocol/src/packets/game/s_player_input.rs @@ -1,4 +1,4 @@ -use std::io::Cursor; +use std::io::{self, Cursor, Write}; use azalea_buf::BufReadError; use azalea_buf::{AzaleaRead, AzaleaWrite}; @@ -18,7 +18,7 @@ pub struct ServerboundPlayerInput { impl AzaleaRead for ServerboundPlayerInput { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { - let set = FixedBitSet::<{ 7_usize.div_ceil(8) }>::azalea_read(buf)?; + let set = FixedBitSet::<7>::azalea_read(buf)?; Ok(Self { forward: set.index(0), backward: set.index(1), @@ -32,8 +32,8 @@ impl AzaleaRead for ServerboundPlayerInput { } impl AzaleaWrite for ServerboundPlayerInput { - 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 Write) -> io::Result<()> { + let mut set = FixedBitSet::<7>::new(); if self.forward { set.set(0); } diff --git a/azalea-protocol/src/packets/game/s_seen_advancements.rs b/azalea-protocol/src/packets/game/s_seen_advancements.rs index f46411f0..d6fef907 100644 --- a/azalea-protocol/src/packets/game/s_seen_advancements.rs +++ b/azalea-protocol/src/packets/game/s_seen_advancements.rs @@ -1,4 +1,4 @@ -use std::io::Cursor; +use std::io::{self, Cursor, Write}; use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite}; use azalea_core::resource_location::ResourceLocation; @@ -31,7 +31,7 @@ impl AzaleaRead for ServerboundSeenAdvancements { } impl AzaleaWrite for ServerboundSeenAdvancements { - 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.action.azalea_write(buf)?; if let Some(tab) = &self.tab { tab.azalea_write(buf)?; diff --git a/azalea-protocol/src/packets/game/s_set_command_block.rs b/azalea-protocol/src/packets/game/s_set_command_block.rs index ea1af6bb..89cece2c 100644 --- a/azalea-protocol/src/packets/game/s_set_command_block.rs +++ b/azalea-protocol/src/packets/game/s_set_command_block.rs @@ -1,4 +1,4 @@ -use std::io::Cursor; +use std::io::{self, Cursor, Write}; use azalea_buf::{AzBuf, AzaleaRead, BufReadError}; use azalea_core::{bitset::FixedBitSet, position::BlockPos}; @@ -30,7 +30,7 @@ impl AzaleaRead for ServerboundSetCommandBlock { let command = String::azalea_read(buf)?; let mode = Mode::azalea_read(buf)?; - let set = FixedBitSet::<{ 3_usize.div_ceil(8) }>::azalea_read(buf)?; + let set = FixedBitSet::<3>::azalea_read(buf)?; Ok(Self { pos, command, @@ -43,12 +43,12 @@ impl AzaleaRead for ServerboundSetCommandBlock { } impl AzaleaWrite for ServerboundSetCommandBlock { - 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.pos.azalea_write(buf)?; self.command.azalea_write(buf)?; self.mode.azalea_write(buf)?; - let mut set = FixedBitSet::<{ 3_usize.div_ceil(8) }>::new(); + let mut set = FixedBitSet::<3>::new(); if self.track_output { set.set(0); } diff --git a/azalea-protocol/src/packets/game/s_set_jigsaw_block.rs b/azalea-protocol/src/packets/game/s_set_jigsaw_block.rs index 1d97d4c7..8c218ffd 100644 --- a/azalea-protocol/src/packets/game/s_set_jigsaw_block.rs +++ b/azalea-protocol/src/packets/game/s_set_jigsaw_block.rs @@ -1,3 +1,4 @@ +use std::io; use std::io::Cursor; use std::io::Write; @@ -41,7 +42,7 @@ impl AzaleaRead for JointType { } impl AzaleaWrite for JointType { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { match self { JointType::Rollable => "rollable".to_string().azalea_write(buf)?, JointType::Aligned => "aligned".to_string().azalea_write(buf)?, diff --git a/azalea-protocol/src/packets/game/s_set_structure_block.rs b/azalea-protocol/src/packets/game/s_set_structure_block.rs index b2f2aeb1..8d518292 100644 --- a/azalea-protocol/src/packets/game/s_set_structure_block.rs +++ b/azalea-protocol/src/packets/game/s_set_structure_block.rs @@ -1,4 +1,4 @@ -use std::io::{Cursor, Write}; +use std::io::{self, Cursor, Write}; use azalea_buf::AzBuf; use azalea_buf::{AzaleaRead, AzaleaWrite}; @@ -73,7 +73,7 @@ pub struct Flags { impl AzaleaRead for Flags { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { - let set = FixedBitSet::<{ 3_usize.div_ceil(8) }>::azalea_read(buf)?; + let set = FixedBitSet::<3>::azalea_read(buf)?; Ok(Self { ignore_entities: set.index(0), show_air: set.index(1), @@ -83,8 +83,8 @@ impl AzaleaRead for Flags { } impl AzaleaWrite for Flags { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - let mut set = FixedBitSet::<{ 3_usize.div_ceil(8) }>::new(); + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { + let mut set = FixedBitSet::<3>::new(); if self.ignore_entities { set.set(0); } diff --git a/azalea-protocol/src/packets/game/s_use_item_on.rs b/azalea-protocol/src/packets/game/s_use_item_on.rs index cfe4a5d3..11e32b2c 100644 --- a/azalea-protocol/src/packets/game/s_use_item_on.rs +++ b/azalea-protocol/src/packets/game/s_use_item_on.rs @@ -1,4 +1,4 @@ -use std::io::{Cursor, Write}; +use std::io::{self, Cursor, Write}; use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite, BufReadError}; use azalea_core::{ @@ -35,7 +35,7 @@ pub struct BlockHit { } impl AzaleaWrite for BlockHit { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { self.block_pos.azalea_write(buf)?; self.direction.azalea_write(buf)?; f32::azalea_write( diff --git a/azalea-protocol/src/packets/handshake/mod.rs b/azalea-protocol/src/packets/handshake/mod.rs index 90a312e7..8d23a13c 100644 --- a/azalea-protocol/src/packets/handshake/mod.rs +++ b/azalea-protocol/src/packets/handshake/mod.rs @@ -1,4 +1,4 @@ -// NOTE: This file is generated automatically by codegen/packet.py. +// NOTE: This file is @generated automatically by codegen/packet.py. // Don't edit it directly! use azalea_protocol_macros::declare_state_packets; diff --git a/azalea-protocol/src/packets/login/c_login_disconnect.rs b/azalea-protocol/src/packets/login/c_login_disconnect.rs index 580bfb2c..699801be 100644 --- a/azalea-protocol/src/packets/login/c_login_disconnect.rs +++ b/azalea-protocol/src/packets/login/c_login_disconnect.rs @@ -1,4 +1,4 @@ -use std::io::{Cursor, Write}; +use std::io::{self, Cursor, Write}; use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError}; use azalea_chat::FormattedText; @@ -21,7 +21,7 @@ impl AzaleaRead for ClientboundLoginDisconnect { Err(err) => { return Err(BufReadError::Custom(format!( "Failed to deserialize disconnect JSON {disconnect_string:?}: {err}" - ))) + ))); } }; @@ -32,7 +32,7 @@ impl AzaleaRead for ClientboundLoginDisconnect { } impl AzaleaWrite for ClientboundLoginDisconnect { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { let status_string = FormattedText::serialize(&self.reason, serde_json::value::Serializer) .unwrap() .to_string(); diff --git a/azalea-protocol/src/packets/login/mod.rs b/azalea-protocol/src/packets/login/mod.rs index 9edf50cf..2038cdcd 100644 --- a/azalea-protocol/src/packets/login/mod.rs +++ b/azalea-protocol/src/packets/login/mod.rs @@ -1,4 +1,4 @@ -// NOTE: This file is generated automatically by codegen/packet.py. +// NOTE: This file is @generated automatically by codegen/packet.py. // Don't edit it directly! use azalea_protocol_macros::declare_state_packets; diff --git a/azalea-protocol/src/packets/mod.rs b/azalea-protocol/src/packets/mod.rs index 3d1b06ab..31097ba5 100644 --- a/azalea-protocol/src/packets/mod.rs +++ b/azalea-protocol/src/packets/mod.rs @@ -5,7 +5,7 @@ pub mod handshake; pub mod login; pub mod status; -use std::io::{Cursor, Write}; +use std::io::{self, Cursor, Write}; use azalea_buf::{AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError}; @@ -53,7 +53,7 @@ where /// Read a packet by its id, `ConnectionProtocol`, and flow fn read(id: u32, buf: &mut Cursor<&[u8]>) -> Result>; - fn write(&self, buf: &mut impl Write) -> Result<(), std::io::Error>; + fn write(&self, buf: &mut impl Write) -> io::Result<()>; } pub trait Packet { @@ -98,7 +98,7 @@ impl azalea_buf::AzaleaRead for ClientIntention { } impl AzaleaWrite for ClientIntention { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { (*self as i32).azalea_write_var(buf) } -} +} \ No newline at end of file diff --git a/azalea-protocol/src/packets/status/c_status_response.rs b/azalea-protocol/src/packets/status/c_status_response.rs index b30c75be..5121f317 100644 --- a/azalea-protocol/src/packets/status/c_status_response.rs +++ b/azalea-protocol/src/packets/status/c_status_response.rs @@ -1,4 +1,4 @@ -use std::io::{Cursor, Write}; +use std::io::{self, Cursor, Write}; use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError}; use azalea_chat::FormattedText; @@ -51,7 +51,7 @@ impl AzaleaRead for ClientboundStatusResponse { } impl AzaleaWrite for ClientboundStatusResponse { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { let status_string = ClientboundStatusResponse::serialize(self, Serializer) .unwrap() .to_string(); diff --git a/azalea-protocol/src/packets/status/mod.rs b/azalea-protocol/src/packets/status/mod.rs index 335600e3..de7cc1ae 100644 --- a/azalea-protocol/src/packets/status/mod.rs +++ b/azalea-protocol/src/packets/status/mod.rs @@ -1,4 +1,4 @@ -// NOTE: This file is generated automatically by codegen/packet.py. +// NOTE: This file is @generated automatically by codegen/packet.py. // Don't edit it directly! use azalea_protocol_macros::declare_state_packets; diff --git a/azalea-protocol/src/read.rs b/azalea-protocol/src/read.rs index 038af319..433a2718 100644 --- a/azalea-protocol/src/read.rs +++ b/azalea-protocol/src/read.rs @@ -1,23 +1,25 @@ //! Read packets from a stream. -use std::backtrace::Backtrace; -use std::env; -use std::sync::LazyLock; use std::{ + backtrace::Backtrace, + env, fmt::Debug, + io, io::{Cursor, Read}, + sync::LazyLock, }; -use azalea_buf::AzaleaReadVar; -use azalea_buf::BufReadError; +use azalea_buf::{AzaleaReadVar, BufReadError}; use azalea_crypto::Aes128CfbDec; use flate2::read::ZlibDecoder; use futures::StreamExt; use futures_lite::future; use thiserror::Error; use tokio::io::AsyncRead; -use tokio_util::bytes::Buf; -use tokio_util::codec::{BytesCodec, FramedRead}; +use tokio_util::{ + bytes::Buf, + codec::{BytesCodec, FramedRead}, +}; use tracing::trace; use crate::packets::ProtocolPacket; @@ -53,7 +55,7 @@ pub enum ReadPacketError { IoError { #[from] #[backtrace] - source: std::io::Error, + source: io::Error, }, #[error("Connection closed")] ConnectionClosed, @@ -70,7 +72,7 @@ pub enum FrameSplitterError { Io { #[from] #[backtrace] - source: std::io::Error, + source: io::Error, }, #[error("Packet is longer than {max} bytes (is {size})")] BadLength { max: usize, size: usize }, @@ -171,7 +173,7 @@ pub enum DecompressionError { Io { #[from] #[backtrace] - source: std::io::Error, + source: io::Error, }, #[error("Badly compressed packet - size of {size} is below server threshold of {threshold}")] BelowCompressionThreshold { size: u32, threshold: u32 }, diff --git a/azalea-registry/src/data.rs b/azalea-registry/src/data.rs index 3e9486a1..cfbb2416 100644 --- a/azalea-registry/src/data.rs +++ b/azalea-registry/src/data.rs @@ -56,3 +56,21 @@ data_registry! {CatVariant, "cat_variant"} data_registry! {PigVariant, "pig_variant"} data_registry! {PaintingVariant, "painting_variant"} data_registry! {WolfVariant, "wolf_variant"} + +data_registry! {Biome, "biome"} +// these extra traits are required for Biome to be allowed to be palletable +impl Default for Biome { + fn default() -> Self { + Self::new_raw(0) + } +} +impl From for Biome { + fn from(id: u32) -> Self { + Self::new_raw(id) + } +} +impl From for u32 { + fn from(biome: Biome) -> Self { + biome.protocol_id() + } +} diff --git a/azalea-registry/src/lib.rs b/azalea-registry/src/lib.rs index a31743b1..b630784b 100644 --- a/azalea-registry/src/lib.rs +++ b/azalea-registry/src/lib.rs @@ -9,8 +9,10 @@ mod data; mod extra; pub mod tags; -use std::fmt::{self, Debug}; -use std::io::{self, Cursor, Write}; +use std::{ + fmt::{self, Debug}, + io::{self, Cursor, Write}, +}; use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError}; use azalea_registry_macros::registry; @@ -42,7 +44,7 @@ impl AzaleaRead for OptionalRegistry { } } impl AzaleaWrite for OptionalRegistry { - 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 { None => 0u32.azalea_write_var(buf), Some(value) => (value.to_u32() + 1).azalea_write_var(buf), @@ -67,7 +69,7 @@ impl AzaleaRead for CustomRegistry AzaleaWrite for CustomRegistry { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { match self { CustomRegistry::Direct(direct_registry) => { // write the id + 1 @@ -115,7 +117,7 @@ impl AzaleaRead impl AzaleaWrite for HolderSet { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { match self { Self::Direct { contents } => { (contents.len() as i32 + 1).azalea_write_var(buf)?; @@ -167,7 +169,7 @@ impl AzaleaRead for Holder AzaleaWrite for Holder { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { match self { Self::Reference(value) => (value.to_u32() + 1).azalea_write_var(buf), Self::Direct(value) => { diff --git a/azalea-registry/src/tags/blocks.rs b/azalea-registry/src/tags/blocks.rs index 69471472..efc138a2 100644 --- a/azalea-registry/src/tags/blocks.rs +++ b/azalea-registry/src/tags/blocks.rs @@ -1,4 +1,4 @@ -// This file was generated by codegen/lib/code/tags.py, don't edit it manually! +// This file was @generated by codegen/lib/code/tags.py, don't edit it manually! use std::{collections::HashSet, sync::LazyLock}; diff --git a/azalea-registry/src/tags/fluids.rs b/azalea-registry/src/tags/fluids.rs index 0713a655..ec96fa8f 100644 --- a/azalea-registry/src/tags/fluids.rs +++ b/azalea-registry/src/tags/fluids.rs @@ -1,4 +1,4 @@ -// This file was generated by codegen/lib/code/tags.py, don't edit it manually! +// This file was @generated by codegen/lib/code/tags.py, don't edit it manually! use std::{collections::HashSet, sync::LazyLock}; diff --git a/azalea-registry/src/tags/items.rs b/azalea-registry/src/tags/items.rs index ea04d482..5abb7981 100644 --- a/azalea-registry/src/tags/items.rs +++ b/azalea-registry/src/tags/items.rs @@ -1,4 +1,4 @@ -// This file was generated by codegen/lib/code/tags.py, don't edit it manually! +// This file was @generated by codegen/lib/code/tags.py, don't edit it manually! use std::{collections::HashSet, sync::LazyLock}; diff --git a/azalea-world/benches/chunks.rs b/azalea-world/benches/chunks.rs index 0e7f4554..f84fdc56 100644 --- a/azalea-world/benches/chunks.rs +++ b/azalea-world/benches/chunks.rs @@ -11,7 +11,7 @@ fn bench_chunks(c: &mut Criterion) { for x in 0..16 { for z in 0..16 { - chunk.set( + chunk.set_block_state( &ChunkBlockPos::new(x, 1, z), azalea_registry::Block::Bedrock.into(), 0, diff --git a/azalea-world/src/chunk_storage.rs b/azalea-world/src/chunk_storage.rs index 9e97a7a7..23d1bb89 100644 --- a/azalea-world/src/chunk_storage.rs +++ b/azalea-world/src/chunk_storage.rs @@ -1,23 +1,29 @@ -use std::collections::hash_map::Entry; use std::{ - collections::HashMap, + collections::{HashMap, hash_map::Entry}, + fmt, fmt::Debug, + io, io::{Cursor, Write}, sync::{Arc, Weak}, }; -use azalea_block::block_state::{BlockState, BlockStateIntegerRepr}; -use azalea_block::fluid_state::FluidState; +use azalea_block::{ + block_state::{BlockState, BlockStateIntegerRepr}, + fluid_state::FluidState, +}; use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError}; -use azalea_core::position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos}; +use azalea_core::position::{ + BlockPos, ChunkBiomePos, ChunkBlockPos, ChunkPos, ChunkSectionBiomePos, ChunkSectionBlockPos, +}; +use azalea_registry::Biome; use nohash_hasher::IntMap; use parking_lot::RwLock; use tracing::{debug, trace, warn}; -use crate::heightmap::Heightmap; -use crate::heightmap::HeightmapKind; -use crate::palette::PalettedContainer; -use crate::palette::PalettedContainerKind; +use crate::{ + heightmap::{Heightmap, HeightmapKind}, + palette::PalettedContainer, +}; const SECTION_HEIGHT: u32 = 16; @@ -56,11 +62,11 @@ pub struct Chunk { } /// A section of a chunk, i.e. a 16*16*16 block area. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct Section { pub block_count: u16, - pub states: PalettedContainer, - pub biomes: PalettedContainer, + pub states: PalettedContainer, + pub biomes: PalettedContainer, } /// Get the actual stored view distance for the selected view distance. For some @@ -69,16 +75,6 @@ pub fn calculate_chunk_storage_range(view_distance: u32) -> u32 { u32::max(view_distance, 2) + 3 } -impl Default for Section { - fn default() -> Self { - Section { - block_count: 0, - states: PalettedContainer::new(PalettedContainerKind::BlockStates), - biomes: PalettedContainer::new(PalettedContainerKind::Biomes), - } - } -} - impl Default for Chunk { fn default() -> Self { Chunk { @@ -168,7 +164,7 @@ impl PartialChunkStorage { let chunk_pos = ChunkPos::from(pos); let chunk_lock = chunk_storage.get(&chunk_pos)?; let mut chunk = chunk_lock.write(); - Some(chunk.get_and_set(&ChunkBlockPos::from(pos), state, chunk_storage.min_y)) + Some(chunk.get_and_set_block_state(&ChunkBlockPos::from(pos), state, chunk_storage.min_y)) } pub fn replace_with_packet_data( @@ -298,7 +294,7 @@ impl ChunkStorage { let chunk_pos = ChunkPos::from(pos); let chunk = self.get(&chunk_pos)?; let chunk = chunk.read(); - chunk.get(&ChunkBlockPos::from(pos), self.min_y) + chunk.get_block_state(&ChunkBlockPos::from(pos), self.min_y) } pub fn get_fluid_state(&self, pos: &BlockPos) -> Option { @@ -306,6 +302,13 @@ impl ChunkStorage { Some(FluidState::from(block_state)) } + pub fn get_biome(&self, pos: &BlockPos) -> Option { + let chunk_pos = ChunkPos::from(pos); + let chunk = self.get(&chunk_pos)?; + let chunk = chunk.read(); + chunk.get_biome(&ChunkBiomePos::from(pos), self.min_y) + } + pub fn set_block_state(&self, pos: &BlockPos, state: BlockState) -> Option { if pos.y < self.min_y || pos.y >= (self.min_y + self.height as i32) { return None; @@ -313,7 +316,7 @@ impl ChunkStorage { let chunk_pos = ChunkPos::from(pos); let chunk = self.get(&chunk_pos)?; let mut chunk = chunk.write(); - Some(chunk.get_and_set(&ChunkBlockPos::from(pos), state, self.min_y)) + Some(chunk.get_and_set_block_state(&ChunkBlockPos::from(pos), state, self.min_y)) } } @@ -343,7 +346,7 @@ impl Chunk { let mut heightmaps = HashMap::new(); for (kind, data) in heightmaps_data { - let data: Box<[u64]> = data.iter().copied().collect(); + let data: Box<[u64]> = data.clone(); let heightmap = Heightmap::new(*kind, dimension_height, min_y, data); heightmaps.insert(*kind, heightmap); } @@ -354,22 +357,26 @@ impl Chunk { }) } - pub fn get(&self, pos: &ChunkBlockPos, min_y: i32) -> Option { + pub fn get_block_state(&self, pos: &ChunkBlockPos, min_y: i32) -> Option { get_block_state_from_sections(&self.sections, pos, min_y) } - #[must_use = "Use Chunk::set instead if you don't need the previous state"] - pub fn get_and_set( + #[must_use = "Use Chunk::set_block_state instead if you don't need the previous state"] + pub fn get_and_set_block_state( &mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32, ) -> BlockState { let section_index = section_index(pos.y, min_y); - // TODO: make sure the section exists - let section = &mut self.sections[section_index as usize]; + let Some(section) = self.sections.get_mut(section_index as usize) else { + warn!( + "Tried to get and set block state {state:?} at out-of-bounds relative chunk position {pos:?}", + ); + return BlockState::AIR; + }; let chunk_section_pos = ChunkSectionBlockPos::from(pos); - let previous_state = section.get_and_set(chunk_section_pos, state); + let previous_state = section.get_and_set_block_state(chunk_section_pos, state); for heightmap in self.heightmaps.values_mut() { heightmap.update(pos, state, &self.sections); @@ -378,17 +385,35 @@ impl Chunk { previous_state } - pub fn set(&mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32) { + pub fn set_block_state(&mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32) { let section_index = section_index(pos.y, min_y); - // TODO: make sure the section exists - let section = &mut self.sections[section_index as usize]; + let Some(section) = self.sections.get_mut(section_index as usize) else { + warn!( + "Tried to set block state {state:?} at out-of-bounds relative chunk position {pos:?}", + ); + return; + }; let chunk_section_pos = ChunkSectionBlockPos::from(pos); - section.set(chunk_section_pos, state); + section.set_block_state(chunk_section_pos, state); for heightmap in self.heightmaps.values_mut() { heightmap.update(pos, state, &self.sections); } } + + pub fn get_biome(&self, pos: &ChunkBiomePos, min_y: i32) -> Option { + if pos.y < min_y { + // y position is out of bounds + return None; + } + let section_index = section_index(pos.y, min_y); + let Some(section) = self.sections.get(section_index as usize) else { + warn!("Tried to get biome at out-of-bounds relative chunk position {pos:?}",); + return None; + }; + let chunk_section_pos = ChunkSectionBiomePos::from(pos); + Some(section.get_biome(chunk_section_pos)) + } } /// Get the block state at the given position from a list of sections. Returns @@ -410,11 +435,11 @@ pub fn get_block_state_from_sections( }; let section = §ions[section_index]; let chunk_section_pos = ChunkSectionBlockPos::from(pos); - Some(section.get(chunk_section_pos)) + Some(section.get_block_state(chunk_section_pos)) } impl AzaleaWrite for Chunk { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { for section in &self.sections { section.azalea_write(buf)?; } @@ -423,7 +448,7 @@ impl AzaleaWrite for Chunk { } impl Debug for PartialChunkStorage { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PartialChunkStorage") .field("view_center", &self.view_center) .field("chunk_radius", &self.chunk_radius) @@ -445,7 +470,7 @@ impl AzaleaRead for Section { // "A section has more blocks than what should be possible. This is a bug!" // ); - let states = PalettedContainer::read_with_type(buf, &PalettedContainerKind::BlockStates)?; + let states = PalettedContainer::::read(buf)?; for i in 0..states.storage.size() { if !BlockState::is_valid_state(states.storage.get(i) as BlockStateIntegerRepr) { @@ -456,7 +481,7 @@ impl AzaleaRead for Section { } } - let biomes = PalettedContainer::read_with_type(buf, &PalettedContainerKind::Biomes)?; + let biomes = PalettedContainer::::read(buf)?; Ok(Section { block_count, states, @@ -466,7 +491,7 @@ impl AzaleaRead for Section { } impl AzaleaWrite for Section { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { self.block_count.azalea_write(buf)?; self.states.azalea_write(buf)?; self.biomes.azalea_write(buf)?; @@ -475,19 +500,28 @@ impl AzaleaWrite for Section { } impl Section { - pub fn get(&self, pos: ChunkSectionBlockPos) -> BlockState { - self.states - .get(pos.x as usize, pos.y as usize, pos.z as usize) + pub fn get_block_state(&self, pos: ChunkSectionBlockPos) -> BlockState { + self.states.get(pos) + } + pub fn get_and_set_block_state( + &mut self, + pos: ChunkSectionBlockPos, + state: BlockState, + ) -> BlockState { + self.states.get_and_set(pos, state) + } + pub fn set_block_state(&mut self, pos: ChunkSectionBlockPos, state: BlockState) { + self.states.set(pos, state); } - pub fn get_and_set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) -> BlockState { - self.states - .get_and_set(pos.x as usize, pos.y as usize, pos.z as usize, state) + pub fn get_biome(&self, pos: ChunkSectionBiomePos) -> Biome { + self.biomes.get(pos) } - - pub fn set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) { - self.states - .set(pos.x as usize, pos.y as usize, pos.z as usize, state); + pub fn set_biome(&mut self, pos: ChunkSectionBiomePos, biome: Biome) { + self.biomes.set(pos, biome); + } + pub fn get_and_set_biome(&mut self, pos: ChunkSectionBiomePos, biome: Biome) -> Biome { + self.biomes.get_and_set(pos, biome) } } diff --git a/azalea-world/src/container.rs b/azalea-world/src/container.rs index 2bccddac..bb8ade70 100644 --- a/azalea-world/src/container.rs +++ b/azalea-world/src/container.rs @@ -58,7 +58,7 @@ impl InstanceContainer { let existing = existing_lock.read(); if existing.chunks.height != height { error!( - "Shared dimension height mismatch: {} != {height}", + "Shared world height mismatch: {} != {height}", existing.chunks.height ); } @@ -86,7 +86,8 @@ impl InstanceContainer { } /// The name of the [`Instance`](crate::Instance) (world) the entity is -/// in. If two entities share the same world name, we assume they're in the same -/// instance. +/// in. If two entities share the same instance name, we assume they're in the +/// same instance. #[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)] +#[doc(alias("worldname", "world name"))] pub struct InstanceName(pub ResourceLocation); diff --git a/azalea-world/src/find_blocks.rs b/azalea-world/src/find_blocks.rs index 967f20ec..b266d799 100644 --- a/azalea-world/src/find_blocks.rs +++ b/azalea-world/src/find_blocks.rs @@ -1,9 +1,9 @@ -use azalea_block::BlockStates; +use azalea_block::{BlockState, BlockStates}; use azalea_core::position::{BlockPos, ChunkPos}; use crate::{ChunkStorage, Instance, iterators::ChunkIterator, palette::Palette}; -fn palette_maybe_has_block(palette: &Palette, block_states: &BlockStates) -> bool { +fn palette_maybe_has_block(palette: &Palette, block_states: &BlockStates) -> bool { match &palette { Palette::SingleValue(id) => block_states.contains(id), Palette::Linear(ids) => ids.iter().any(|id| block_states.contains(id)), @@ -20,7 +20,10 @@ impl Instance { /// /// ``` /// # fn example(client: &azalea_client::Client) { - /// client.world().read().find_block(client.position(), &azalea_registry::Block::Chest.into()); + /// client + /// .world() + /// .read() + /// .find_block(client.position(), &azalea_registry::Block::Chest.into()); /// # } /// ``` pub fn find_block( @@ -60,11 +63,11 @@ impl Instance { let block_state = section.states.get_at_index(i); if block_states.contains(&block_state) { - let (section_x, section_y, section_z) = section.states.coords_from_index(i); + let section_pos = section.states.coords_from_index(i); let (x, y, z) = ( - chunk_pos.x * 16 + (section_x as i32), - self.chunks.min_y + (section_index * 16) as i32 + section_y as i32, - chunk_pos.z * 16 + (section_z as i32), + chunk_pos.x * 16 + (section_pos.x as i32), + self.chunks.min_y + (section_index * 16) as i32 + section_pos.y as i32, + chunk_pos.z * 16 + (section_pos.z as i32), ); let this_block_pos = BlockPos { x, y, z }; let this_block_distance = (nearest_to - this_block_pos).length_manhattan(); @@ -187,11 +190,11 @@ impl Iterator for FindBlocks<'_> { let block_state = section.states.get_at_index(i); if self.block_states.contains(&block_state) { - let (section_x, section_y, section_z) = section.states.coords_from_index(i); + let section_pos = section.states.coords_from_index(i); let (x, y, z) = ( - chunk_pos.x * 16 + (section_x as i32), - self.chunks.min_y + (section_index * 16) as i32 + section_y as i32, - chunk_pos.z * 16 + (section_z as i32), + chunk_pos.x * 16 + (section_pos.x as i32), + self.chunks.min_y + (section_index * 16) as i32 + section_pos.y as i32, + chunk_pos.z * 16 + (section_pos.z as i32), ); let this_block_pos = BlockPos { x, y, z }; let this_block_distance = diff --git a/azalea-world/src/heightmap.rs b/azalea-world/src/heightmap.rs index 00ebad41..462d5b09 100644 --- a/azalea-world/src/heightmap.rs +++ b/azalea-world/src/heightmap.rs @@ -1,4 +1,7 @@ -use std::{fmt::Display, str::FromStr}; +use std::{ + fmt::{self, Display}, + str::FromStr, +}; use azalea_block::BlockState; use azalea_buf::AzBuf; @@ -161,7 +164,7 @@ impl FromStr for HeightmapKind { } impl Display for HeightmapKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { HeightmapKind::WorldSurfaceWg => write!(f, "WORLD_SURFACE_WG"), HeightmapKind::WorldSurface => write!(f, "WORLD_SURFACE"), diff --git a/azalea-world/src/iterators.rs b/azalea-world/src/iterators.rs index acb76cf6..3362487b 100644 --- a/azalea-world/src/iterators.rs +++ b/azalea-world/src/iterators.rs @@ -11,7 +11,7 @@ use azalea_core::position::{BlockPos, ChunkPos}; /// /// let mut iter = BlockIterator::new(BlockPos::default(), 4); /// for block_pos in iter { -/// println!("{:?}", block_pos); +/// println!("{:?}", block_pos); /// } /// ``` pub struct BlockIterator { @@ -86,7 +86,7 @@ impl Iterator for BlockIterator { /// /// let mut iter = SquareChunkIterator::new(ChunkPos::default(), 4); /// for chunk_pos in iter { -/// println!("{:?}", chunk_pos); +/// println!("{:?}", chunk_pos); /// } /// ``` pub struct SquareChunkIterator { @@ -123,11 +123,11 @@ impl SquareChunkIterator { /// /// let mut iter = SquareChunkIterator::new(ChunkPos::default(), 2); /// while let Some(chunk_pos) = iter.next() { - /// println!("{:?}", chunk_pos); + /// println!("{:?}", chunk_pos); /// } /// iter.set_max_distance(4); /// while let Some(chunk_pos) = iter.next() { - /// println!("{:?}", chunk_pos); + /// println!("{:?}", chunk_pos); /// } /// ``` pub fn set_max_distance(&mut self, max_distance: u32) { @@ -174,7 +174,7 @@ impl Iterator for SquareChunkIterator { /// /// let mut iter = ChunkIterator::new(ChunkPos::default(), 4); /// for chunk_pos in iter { -/// println!("{:?}", chunk_pos); +/// println!("{:?}", chunk_pos); /// } /// ``` pub struct ChunkIterator { diff --git a/azalea-world/src/palette.rs b/azalea-world/src/palette.rs deleted file mode 100644 index 37d43fac..00000000 --- a/azalea-world/src/palette.rs +++ /dev/null @@ -1,432 +0,0 @@ -use std::io::{Cursor, Write}; - -use azalea_block::BlockState; -use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError}; -use tracing::warn; - -use crate::BitStorage; - -#[derive(Clone, Debug, Copy)] -pub enum PalettedContainerKind { - Biomes, - BlockStates, -} - -#[derive(Clone, Debug)] -pub struct PalettedContainer { - pub bits_per_entry: u8, - /// This is usually a list of unique values that appear in the container so - /// they can be indexed by the bit storage. - /// - /// Sometimes it doesn't contain anything if there's too many unique items - /// in the bit storage, though. - pub palette: Palette, - /// Compacted list of indices pointing to entry IDs in the Palette. - pub storage: BitStorage, - pub container_type: PalettedContainerKind, -} - -impl PalettedContainer { - pub fn new(container_type: PalettedContainerKind) -> Self { - let palette = Palette::SingleValue(BlockState::AIR); - let size = container_type.size(); - let storage = BitStorage::new(0, size, Some(Box::new([]))).unwrap(); - - PalettedContainer { - bits_per_entry: 0, - palette, - storage, - container_type, - } - } - - pub fn read_with_type( - buf: &mut Cursor<&[u8]>, - container_type: &'static PalettedContainerKind, - ) -> Result { - let bits_per_entry = u8::azalea_read(buf)?; - let palette_type = PaletteKind::from_bits_and_type(bits_per_entry, container_type); - let palette = palette_type.read(buf)?; - let size = container_type.size(); - - let mut storage = match BitStorage::new( - bits_per_entry as usize, - size, - if bits_per_entry == 0 { - Some(Box::new([])) - } else { - // we're going to update the data after creating the bitstorage - None - }, - ) { - Ok(storage) => storage, - Err(e) => { - warn!("Failed to create bit storage: {:?}", e); - return Err(BufReadError::Custom( - "Failed to create bit storage".to_string(), - )); - } - }; - - // now read the data - for i in 0..storage.data.len() { - storage.data[i] = u64::azalea_read(buf)?; - } - - Ok(PalettedContainer { - bits_per_entry, - palette, - storage, - container_type: *container_type, - }) - } - - /// Calculates the index of the given coordinates. - pub fn index_from_coords(&self, x: usize, y: usize, z: usize) -> usize { - let size_bits = self.container_type.size_bits(); - - (((y << size_bits) | z) << size_bits) | x - } - - pub fn coords_from_index(&self, index: usize) -> (usize, usize, usize) { - let size_bits = self.container_type.size_bits(); - let mask = (1 << size_bits) - 1; - ( - index & mask, - (index >> size_bits >> size_bits) & mask, - (index >> size_bits) & mask, - ) - } - - /// Returns the value at the given index. - /// - /// # Panics - /// - /// This function panics if the index is greater than or equal to the number - /// of things in the storage. (So for block states, it must be less than - /// 4096). - pub fn get_at_index(&self, index: usize) -> BlockState { - // first get the palette id - let paletted_value = self.storage.get(index); - // and then get the value from that id - self.palette.value_for(paletted_value as usize) - } - - /// Returns the value at the given coordinates. - pub fn get(&self, x: usize, y: usize, z: usize) -> BlockState { - // let paletted_value = self.storage.get(self.get_index(x, y, z)); - // self.palette.value_for(paletted_value as usize) - self.get_at_index(self.index_from_coords(x, y, z)) - } - - /// Sets the id at the given coordinates and return the previous id - pub fn get_and_set(&mut self, x: usize, y: usize, z: usize, value: BlockState) -> BlockState { - let paletted_value = self.id_for(value); - let block_state_id = self - .storage - .get_and_set(self.index_from_coords(x, y, z), paletted_value as u64); - // error in debug mode - #[cfg(debug_assertions)] - if block_state_id > BlockState::MAX_STATE.into() { - warn!( - "Old block state from get_and_set {block_state_id} was greater than max state {}", - BlockState::MAX_STATE - ); - } - - BlockState::try_from(block_state_id as u32).unwrap_or_default() - } - - /// Sets the id at the given index and return the previous id. You probably - /// want `.set` instead. - pub fn set_at_index(&mut self, index: usize, value: BlockState) { - let paletted_value = self.id_for(value); - self.storage.set(index, paletted_value as u64); - } - - /// Sets the id at the given coordinates and return the previous id - pub fn set(&mut self, x: usize, y: usize, z: usize, value: BlockState) { - self.set_at_index(self.index_from_coords(x, y, z), value); - } - - fn create_or_reuse_data(&self, bits_per_entry: u8) -> PalettedContainer { - let new_palette_type = - PaletteKind::from_bits_and_type(bits_per_entry, &self.container_type); - - let old_palette_type = (&self.palette).into(); - if bits_per_entry == self.bits_per_entry && new_palette_type == old_palette_type { - return self.clone(); - } - let storage = - BitStorage::new(bits_per_entry as usize, self.container_type.size(), None).unwrap(); - - // sanity check - debug_assert_eq!(storage.size(), self.container_type.size()); - - // let palette = new_palette_type.as_empty_palette(1usize << (bits_per_entry as - // usize)); - let palette = new_palette_type.as_empty_palette(); - PalettedContainer { - bits_per_entry, - palette, - storage, - container_type: self.container_type, - } - } - - fn on_resize(&mut self, bits_per_entry: u8, value: BlockState) -> usize { - // in vanilla this is always true, but it's sometimes false in purpur servers - // assert!(bits_per_entry <= 5, "bits_per_entry must be <= 5"); - let mut new_data = self.create_or_reuse_data(bits_per_entry); - new_data.copy_from(&self.palette, &self.storage); - *self = new_data; - self.id_for(value) - } - - fn copy_from(&mut self, palette: &Palette, storage: &BitStorage) { - for i in 0..storage.size() { - let value = palette.value_for(storage.get(i) as usize); - let id = self.id_for(value) as u64; - self.storage.set(i, id); - } - } - - pub fn id_for(&mut self, value: BlockState) -> usize { - match &mut self.palette { - Palette::SingleValue(v) => { - if *v != value { - self.on_resize(1, value) - } else { - 0 - } - } - Palette::Linear(palette) => { - if let Some(index) = palette.iter().position(|&v| v == value) { - return index; - } - let capacity = 2usize.pow(self.bits_per_entry.into()); - if capacity > palette.len() { - palette.push(value); - palette.len() - 1 - } else { - self.on_resize(self.bits_per_entry + 1, value) - } - } - Palette::Hashmap(palette) => { - // TODO? vanilla keeps this in memory as a hashmap, but it should be benchmarked - // before changing it - if let Some(index) = palette.iter().position(|v| *v == value) { - return index; - } - let capacity = 2usize.pow(self.bits_per_entry.into()); - if capacity > palette.len() { - palette.push(value); - palette.len() - 1 - } else { - self.on_resize(self.bits_per_entry + 1, value) - } - } - Palette::Global => value.id() as usize, - } - } -} - -impl AzaleaWrite for PalettedContainer { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - self.bits_per_entry.azalea_write(buf)?; - self.palette.azalea_write(buf)?; - self.storage.data.azalea_write(buf)?; - Ok(()) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum PaletteKind { - SingleValue, - Linear, - Hashmap, - Global, -} - -/// A representation of the different types of chunk palettes Minecraft uses. -#[derive(Clone, Debug)] -pub enum Palette { - /// ID of the corresponding entry in its global palette - SingleValue(BlockState), - // in vanilla this keeps a `size` field that might be less than the length, but i'm not sure - // it's actually needed? - Linear(Vec), - Hashmap(Vec), - Global, -} - -impl Palette { - pub fn value_for(&self, id: usize) -> BlockState { - match self { - Palette::SingleValue(v) => *v, - Palette::Linear(v) => v.get(id).copied().unwrap_or_default(), - Palette::Hashmap(v) => v.get(id).copied().unwrap_or_default(), - Palette::Global => BlockState::try_from(id as u32).unwrap_or_default(), - } - } -} - -impl AzaleaWrite for Palette { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - match self { - Palette::SingleValue(value) => { - value.azalea_write(buf)?; - } - Palette::Linear(values) => { - values.azalea_write(buf)?; - } - Palette::Hashmap(values) => { - values.azalea_write(buf)?; - } - Palette::Global => {} - } - Ok(()) - } -} - -impl PaletteKind { - pub fn from_bits_and_type(bits_per_entry: u8, container_type: &PalettedContainerKind) -> Self { - match container_type { - PalettedContainerKind::BlockStates => match bits_per_entry { - 0 => PaletteKind::SingleValue, - 1..=4 => PaletteKind::Linear, - 5..=8 => PaletteKind::Hashmap, - _ => PaletteKind::Global, - }, - PalettedContainerKind::Biomes => match bits_per_entry { - 0 => PaletteKind::SingleValue, - 1..=3 => PaletteKind::Linear, - _ => PaletteKind::Global, - }, - } - } - - pub fn read(&self, buf: &mut Cursor<&[u8]>) -> Result { - Ok(match self { - // since they're read as varints it's actually fine to just use BlockStateIntegerRepr - // instead of the correct type (u32) - PaletteKind::SingleValue => Palette::SingleValue(BlockState::azalea_read(buf)?), - PaletteKind::Linear => Palette::Linear(Vec::::azalea_read(buf)?), - PaletteKind::Hashmap => Palette::Hashmap(Vec::::azalea_read(buf)?), - PaletteKind::Global => Palette::Global, - }) - } - - pub fn as_empty_palette(&self) -> Palette { - match self { - PaletteKind::SingleValue => Palette::SingleValue(BlockState::AIR), - PaletteKind::Linear => Palette::Linear(Vec::new()), - PaletteKind::Hashmap => Palette::Hashmap(Vec::new()), - PaletteKind::Global => Palette::Global, - } - } -} - -impl From<&Palette> for PaletteKind { - fn from(palette: &Palette) -> Self { - match palette { - Palette::SingleValue(_) => PaletteKind::SingleValue, - Palette::Linear(_) => PaletteKind::Linear, - Palette::Hashmap(_) => PaletteKind::Hashmap, - Palette::Global => PaletteKind::Global, - } - } -} - -impl PalettedContainerKind { - fn size_bits(&self) -> usize { - match self { - PalettedContainerKind::BlockStates => 4, - PalettedContainerKind::Biomes => 2, - } - } - - fn size(&self) -> usize { - 1 << (self.size_bits() * 3) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_resize_0_bits_to_1() { - let mut palette_container = PalettedContainer::new(PalettedContainerKind::BlockStates); - - assert_eq!(palette_container.bits_per_entry, 0); - assert_eq!(palette_container.get_at_index(0), BlockState::AIR); - assert_eq!( - PaletteKind::from(&palette_container.palette), - PaletteKind::SingleValue - ); - let block_state_1 = BlockState::try_from(1_u32).unwrap(); - palette_container.set_at_index(0, block_state_1); - assert_eq!(palette_container.get_at_index(0), block_state_1); - assert_eq!( - PaletteKind::from(&palette_container.palette), - PaletteKind::Linear - ); - } - - #[test] - fn test_resize_0_bits_to_5() { - let mut palette_container = PalettedContainer::new(PalettedContainerKind::BlockStates); - - let set = |pc: &mut PalettedContainer, i, v: u32| { - pc.set_at_index(i, BlockState::try_from(v).unwrap()); - }; - - set(&mut palette_container, 0, 0); // 0 bits - assert_eq!(palette_container.bits_per_entry, 0); - - set(&mut palette_container, 1, 1); // 1 bit - assert_eq!(palette_container.bits_per_entry, 1); - - set(&mut palette_container, 2, 2); // 2 bits - assert_eq!(palette_container.bits_per_entry, 2); - set(&mut palette_container, 3, 3); - - set(&mut palette_container, 4, 4); // 3 bits - assert_eq!(palette_container.bits_per_entry, 3); - set(&mut palette_container, 5, 5); - set(&mut palette_container, 6, 6); - set(&mut palette_container, 7, 7); - - set(&mut palette_container, 8, 8); // 4 bits - assert_eq!(palette_container.bits_per_entry, 4); - set(&mut palette_container, 9, 9); - set(&mut palette_container, 10, 10); - set(&mut palette_container, 11, 11); - set(&mut palette_container, 12, 12); - set(&mut palette_container, 13, 13); - set(&mut palette_container, 14, 14); - set(&mut palette_container, 15, 15); - assert_eq!(palette_container.bits_per_entry, 4); - - set(&mut palette_container, 16, 16); // 5 bits - assert_eq!(palette_container.bits_per_entry, 5); - } - - #[test] - fn test_coords_from_index() { - let palette_container = PalettedContainer::new(PalettedContainerKind::BlockStates); - - for x in 0..15 { - for y in 0..15 { - for z in 0..15 { - assert_eq!( - palette_container - .coords_from_index(palette_container.index_from_coords(x, y, z)), - (x, y, z) - ); - } - } - } - } -} diff --git a/azalea-world/src/palette/container.rs b/azalea-world/src/palette/container.rs new file mode 100644 index 00000000..2f3d9238 --- /dev/null +++ b/azalea-world/src/palette/container.rs @@ -0,0 +1,314 @@ +use std::{ + fmt::Debug, + io::{self, Cursor, Write}, +}; + +use azalea_block::BlockState; +use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError}; +use azalea_core::position::{ChunkSectionBiomePos, ChunkSectionBlockPos}; +use azalea_registry::Biome; +use tracing::warn; + +use super::{Palette, PaletteKind}; +use crate::BitStorage; + +#[derive(Clone, Debug)] +pub struct PalettedContainer { + pub bits_per_entry: u8, + /// This is usually a list of unique values that appear in the container so + /// they can be indexed by the bit storage. + /// + /// Sometimes it doesn't contain anything if there's too many unique items + /// in the bit storage, though. + pub palette: Palette, + /// Compacted list of indices pointing to entry IDs in the Palette. + pub storage: BitStorage, +} + +pub trait PalletedContainerKind: Copy + Clone + Debug + Default + TryFrom + Into { + type SectionPos: SectionPos; + + fn size_bits() -> usize; + + fn size() -> usize { + 1 << (Self::size_bits() * 3) + } + + fn bits_per_entry_to_palette_kind(bits_per_entry: u8) -> PaletteKind; +} +impl PalletedContainerKind for BlockState { + type SectionPos = ChunkSectionBlockPos; + + fn size_bits() -> usize { + 4 + } + + fn bits_per_entry_to_palette_kind(bits_per_entry: u8) -> PaletteKind { + match bits_per_entry { + 0 => PaletteKind::SingleValue, + 1..=4 => PaletteKind::Linear, + 5..=8 => PaletteKind::Hashmap, + _ => PaletteKind::Global, + } + } +} +impl PalletedContainerKind for Biome { + type SectionPos = ChunkSectionBiomePos; + + fn size_bits() -> usize { + 2 + } + + fn bits_per_entry_to_palette_kind(bits_per_entry: u8) -> PaletteKind { + match bits_per_entry { + 0 => PaletteKind::SingleValue, + 1..=3 => PaletteKind::Linear, + _ => PaletteKind::Global, + } + } +} + +/// A trait for position types that are sometimes valid ways to index into a +/// chunk section. +pub trait SectionPos { + fn coords(&self) -> (usize, usize, usize); + fn new(x: usize, y: usize, z: usize) -> Self; +} +impl SectionPos for ChunkSectionBlockPos { + fn coords(&self) -> (usize, usize, usize) { + (self.x as usize, self.y as usize, self.z as usize) + } + + fn new(x: usize, y: usize, z: usize) -> Self { + ChunkSectionBlockPos { + x: x as u8, + y: y as u8, + z: z as u8, + } + } +} +impl SectionPos for ChunkSectionBiomePos { + fn coords(&self) -> (usize, usize, usize) { + (self.x as usize, self.y as usize, self.z as usize) + } + + fn new(x: usize, y: usize, z: usize) -> Self { + ChunkSectionBiomePos { + x: x as u8, + y: y as u8, + z: z as u8, + } + } +} + +impl PalettedContainer { + pub fn new() -> Self { + let palette = Palette::SingleValue(S::default()); + let size = S::size(); + let storage = BitStorage::new(0, size, Some(Box::new([]))).unwrap(); + + PalettedContainer { + bits_per_entry: 0, + palette, + storage, + } + } + + pub fn read(buf: &mut Cursor<&[u8]>) -> Result { + let bits_per_entry = u8::azalea_read(buf)?; + let palette_type = S::bits_per_entry_to_palette_kind(bits_per_entry); + let palette = palette_type.read(buf)?; + let size = S::size(); + + let mut storage = match BitStorage::new( + bits_per_entry as usize, + size, + if bits_per_entry == 0 { + Some(Box::new([])) + } else { + // we're going to update the data after creating the bitstorage + None + }, + ) { + Ok(storage) => storage, + Err(e) => { + warn!("Failed to create bit storage: {:?}", e); + return Err(BufReadError::Custom( + "Failed to create bit storage".to_string(), + )); + } + }; + + // now read the data + for i in 0..storage.data.len() { + storage.data[i] = u64::azalea_read(buf)?; + } + + Ok(PalettedContainer { + bits_per_entry, + palette, + storage, + }) + } + + /// Calculates the index of the given coordinates. + pub fn index_from_coords(&self, pos: S::SectionPos) -> usize { + let size_bits = S::size_bits(); + let (x, y, z) = pos.coords(); + (((y << size_bits) | z) << size_bits) | x + } + + pub fn coords_from_index(&self, index: usize) -> S::SectionPos { + let size_bits = S::size_bits(); + let mask = (1 << size_bits) - 1; + S::SectionPos::new( + index & mask, + (index >> size_bits >> size_bits) & mask, + (index >> size_bits) & mask, + ) + } + + /// Returns the value at the given index. + /// + /// # Panics + /// + /// This function panics if the index is greater than or equal to the number + /// of things in the storage. (So for block states, it must be less than + /// 4096). + pub fn get_at_index(&self, index: usize) -> S { + // first get the palette id + let paletted_value = self.storage.get(index); + // and then get the value from that id + self.palette.value_for(paletted_value as usize) + } + + /// Returns the value at the given coordinates. + pub fn get(&self, pos: S::SectionPos) -> S { + // let paletted_value = self.storage.get(self.get_index(x, y, z)); + // self.palette.value_for(paletted_value as usize) + self.get_at_index(self.index_from_coords(pos)) + } + + /// Sets the id at the given coordinates and return the previous id + pub fn get_and_set(&mut self, pos: S::SectionPos, value: S) -> S { + let paletted_value = self.id_for(value); + let block_state_id = self + .storage + .get_and_set(self.index_from_coords(pos), paletted_value as u64); + // error in debug mode + #[cfg(debug_assertions)] + if block_state_id > BlockState::MAX_STATE.into() { + warn!( + "Old block state from get_and_set {block_state_id} was greater than max state {}", + BlockState::MAX_STATE + ); + } + + S::try_from(block_state_id as u32).unwrap_or_default() + } + + /// Sets the id at the given index and return the previous id. You probably + /// want `.set` instead. + pub fn set_at_index(&mut self, index: usize, value: S) { + let paletted_value = self.id_for(value); + self.storage.set(index, paletted_value as u64); + } + + /// Sets the id at the given coordinates and return the previous id + pub fn set(&mut self, pos: S::SectionPos, value: S) { + self.set_at_index(self.index_from_coords(pos), value); + } + + fn create_or_reuse_data(&self, bits_per_entry: u8) -> PalettedContainer { + let new_palette_type = S::bits_per_entry_to_palette_kind(bits_per_entry); + + let old_palette_type = (&self.palette).into(); + if bits_per_entry == self.bits_per_entry && new_palette_type == old_palette_type { + return self.clone(); + } + let storage = BitStorage::new(bits_per_entry as usize, S::size(), None).unwrap(); + + // sanity check + debug_assert_eq!(storage.size(), S::size()); + + // let palette = new_palette_type.as_empty_palette(1usize << (bits_per_entry as + // usize)); + let palette = new_palette_type.as_empty_palette(); + PalettedContainer { + bits_per_entry, + palette, + storage, + } + } + + fn on_resize(&mut self, bits_per_entry: u8, value: S) -> usize { + // in vanilla this is always true, but it's sometimes false in purpur servers + // assert!(bits_per_entry <= 5, "bits_per_entry must be <= 5"); + let mut new_data = self.create_or_reuse_data(bits_per_entry); + new_data.copy_from(&self.palette, &self.storage); + *self = new_data; + self.id_for(value) + } + + fn copy_from(&mut self, palette: &Palette, storage: &BitStorage) { + for i in 0..storage.size() { + let value = palette.value_for(storage.get(i) as usize); + let id = self.id_for(value) as u64; + self.storage.set(i, id); + } + } + + pub fn id_for(&mut self, value: S) -> usize { + match &mut self.palette { + Palette::SingleValue(v) => { + if (*v).into() != value.into() { + self.on_resize(1, value) + } else { + 0 + } + } + Palette::Linear(palette) => { + if let Some(index) = palette.iter().position(|&v| v.into() == value.into()) { + return index; + } + let capacity = 2usize.pow(self.bits_per_entry.into()); + if capacity > palette.len() { + palette.push(value); + palette.len() - 1 + } else { + self.on_resize(self.bits_per_entry + 1, value) + } + } + Palette::Hashmap(palette) => { + // TODO? vanilla keeps this in memory as a hashmap, but it should be benchmarked + // before changing it + if let Some(index) = palette.iter().position(|v| (*v).into() == value.into()) { + return index; + } + let capacity = 2usize.pow(self.bits_per_entry.into()); + if capacity > palette.len() { + palette.push(value); + palette.len() - 1 + } else { + self.on_resize(self.bits_per_entry + 1, value) + } + } + Palette::Global => value.into() as usize, + } + } +} + +impl AzaleaWrite for PalettedContainer { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { + self.bits_per_entry.azalea_write(buf)?; + self.palette.azalea_write(buf)?; + self.storage.data.azalea_write(buf)?; + Ok(()) + } +} + +impl Default for PalettedContainer { + fn default() -> Self { + Self::new() + } +} diff --git a/azalea-world/src/palette/mod.rs b/azalea-world/src/palette/mod.rs new file mode 100644 index 00000000..65a04f6a --- /dev/null +++ b/azalea-world/src/palette/mod.rs @@ -0,0 +1,115 @@ +mod container; + +#[cfg(test)] +mod tests; + +use std::{ + fmt::Debug, + io::{self, Cursor, Write}, +}; + +use azalea_buf::{AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError}; +pub use container::*; + +/// A representation of the different types of chunk palettes Minecraft uses. +#[derive(Clone, Debug)] +pub enum Palette { + /// ID of the corresponding entry in its global palette + SingleValue(S), + // in vanilla this keeps a `size` field that might be less than the length, but i'm not sure + // it's actually needed? + Linear(Vec), + Hashmap(Vec), + Global, +} + +impl Palette { + pub fn value_for(&self, id: usize) -> S { + match self { + Palette::SingleValue(v) => *v, + Palette::Linear(v) => v.get(id).copied().unwrap_or_default(), + Palette::Hashmap(v) => v.get(id).copied().unwrap_or_default(), + Palette::Global => S::try_from(id as u32).unwrap_or_default(), + } + } +} + +impl AzaleaWrite for Palette { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { + match self { + Palette::SingleValue(value) => { + (*value).into().azalea_write_var(buf)?; + } + Palette::Linear(values) => { + (values.len() as u32).azalea_write_var(buf)?; + for value in values { + (*value).into().azalea_write_var(buf)?; + } + } + Palette::Hashmap(values) => { + (values.len() as u32).azalea_write_var(buf)?; + for value in values { + (*value).into().azalea_write_var(buf)?; + } + } + Palette::Global => {} + } + Ok(()) + } +} + +impl PaletteKind { + pub fn read( + &self, + buf: &mut Cursor<&[u8]>, + ) -> Result, BufReadError> { + Ok(match self { + // since they're read as varints it's actually fine to just use BlockStateIntegerRepr + // instead of the correct type (u32) + PaletteKind::SingleValue => { + Palette::SingleValue(S::try_from(u32::azalea_read_var(buf)?).unwrap_or_default()) + } + PaletteKind::Linear => Palette::Linear( + Vec::::azalea_read_var(buf)? + .into_iter() + .map(|v| S::try_from(v).unwrap_or_default()) + .collect(), + ), + PaletteKind::Hashmap => Palette::Hashmap( + Vec::::azalea_read_var(buf)? + .into_iter() + .map(|v| S::try_from(v).unwrap_or_default()) + .collect(), + ), + PaletteKind::Global => Palette::Global, + }) + } + + pub fn as_empty_palette(&self) -> Palette { + match self { + PaletteKind::SingleValue => Palette::SingleValue(S::default()), + PaletteKind::Linear => Palette::Linear(Vec::new()), + PaletteKind::Hashmap => Palette::Hashmap(Vec::new()), + PaletteKind::Global => Palette::Global, + } + } +} + +impl From<&Palette> for PaletteKind { + fn from(palette: &Palette) -> Self { + match palette { + Palette::SingleValue(_) => PaletteKind::SingleValue, + Palette::Linear(_) => PaletteKind::Linear, + Palette::Hashmap(_) => PaletteKind::Hashmap, + Palette::Global => PaletteKind::Global, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum PaletteKind { + SingleValue, + Linear, + Hashmap, + Global, +} diff --git a/azalea-world/src/palette/tests.rs b/azalea-world/src/palette/tests.rs new file mode 100644 index 00000000..d1423306 --- /dev/null +++ b/azalea-world/src/palette/tests.rs @@ -0,0 +1,80 @@ +use azalea_block::BlockState; +use azalea_core::position::ChunkSectionBlockPos; + +use super::*; + +#[test] +fn test_resize_0_bits_to_1() { + let mut palette_container = PalettedContainer::::new(); + + assert_eq!(palette_container.bits_per_entry, 0); + assert_eq!(palette_container.get_at_index(0), BlockState::AIR); + assert_eq!( + PaletteKind::from(&palette_container.palette), + PaletteKind::SingleValue + ); + let block_state_1 = BlockState::try_from(1_u32).unwrap(); + palette_container.set_at_index(0, block_state_1); + assert_eq!(palette_container.get_at_index(0), block_state_1); + assert_eq!( + PaletteKind::from(&palette_container.palette), + PaletteKind::Linear + ); +} + +#[test] +fn test_resize_0_bits_to_5() { + let mut palette_container = PalettedContainer::::new(); + + let set = |pc: &mut PalettedContainer, i, v: u32| { + pc.set_at_index(i, BlockState::try_from(v).unwrap()); + }; + + set(&mut palette_container, 0, 0); // 0 bits + assert_eq!(palette_container.bits_per_entry, 0); + + set(&mut palette_container, 1, 1); // 1 bit + assert_eq!(palette_container.bits_per_entry, 1); + + set(&mut palette_container, 2, 2); // 2 bits + assert_eq!(palette_container.bits_per_entry, 2); + set(&mut palette_container, 3, 3); + + set(&mut palette_container, 4, 4); // 3 bits + assert_eq!(palette_container.bits_per_entry, 3); + set(&mut palette_container, 5, 5); + set(&mut palette_container, 6, 6); + set(&mut palette_container, 7, 7); + + set(&mut palette_container, 8, 8); // 4 bits + assert_eq!(palette_container.bits_per_entry, 4); + set(&mut palette_container, 9, 9); + set(&mut palette_container, 10, 10); + set(&mut palette_container, 11, 11); + set(&mut palette_container, 12, 12); + set(&mut palette_container, 13, 13); + set(&mut palette_container, 14, 14); + set(&mut palette_container, 15, 15); + assert_eq!(palette_container.bits_per_entry, 4); + + set(&mut palette_container, 16, 16); // 5 bits + assert_eq!(palette_container.bits_per_entry, 5); +} + +#[test] +fn test_coords_from_index() { + let palette_container = PalettedContainer::::new(); + + for x in 0..15 { + for y in 0..15 { + for z in 0..15 { + assert_eq!( + palette_container.coords_from_index( + palette_container.index_from_coords(ChunkSectionBlockPos::new(x, y, z)) + ), + ChunkSectionBlockPos::new(x, y, z) + ); + } + } + } +} diff --git a/azalea-world/src/world.rs b/azalea-world/src/world.rs index 4b2b2a19..47804dcc 100644 --- a/azalea-world/src/world.rs +++ b/azalea-world/src/world.rs @@ -1,16 +1,17 @@ -use std::fmt::{self, Display, Formatter}; -use std::hash::{Hash, Hasher}; -use std::io::{self, Cursor}; use std::{ collections::{HashMap, HashSet}, - fmt::Debug, + fmt::{self, Debug, Display}, + hash::{Hash, Hasher}, + io::{self, Cursor}, }; -use azalea_block::BlockState; -use azalea_block::fluid_state::FluidState; +use azalea_block::{BlockState, fluid_state::FluidState}; use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError}; -use azalea_core::position::{BlockPos, ChunkPos}; -use azalea_core::registry_holder::RegistryHolder; +use azalea_core::{ + position::{BlockPos, ChunkPos}, + registry_holder::RegistryHolder, +}; +use azalea_registry::Biome; use bevy_ecs::{component::Component, entity::Entity}; use derive_more::{Deref, DerefMut}; use nohash_hasher::IntMap; @@ -84,7 +85,7 @@ impl AzaleaRead for MinecraftEntityId { } } impl AzaleaWrite for MinecraftEntityId { - fn azalea_write(&self, buf: &mut impl io::Write) -> Result<(), io::Error> { + fn azalea_write(&self, buf: &mut impl io::Write) -> io::Result<()> { i32::azalea_write(&self.0, buf) } } @@ -94,12 +95,12 @@ impl AzaleaReadVar for MinecraftEntityId { } } impl AzaleaWriteVar for MinecraftEntityId { - fn azalea_write_var(&self, buf: &mut impl io::Write) -> Result<(), io::Error> { + fn azalea_write_var(&self, buf: &mut impl io::Write) -> io::Result<()> { i32::azalea_write_var(&self.0, buf) } } impl Display for MinecraftEntityId { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "eid({})", self.0) } } @@ -143,6 +144,14 @@ impl PartialEntityInfos { /// A world where the chunks are stored as weak pointers. This is used for /// shared worlds. +/// +/// Also see [`PartialInstance`]. +/// +/// The reason this is called "instance" instead of "world" or "dimension" is +/// because "world" already means the entire ECS (which can contain multiple +/// instances if we're in a swarm) and "dimension" can be ambiguous (for +/// instance there can be multiple overworlds, and "dimension" is also a math +/// term) #[derive(Default, Debug)] pub struct Instance { pub chunks: ChunkStorage, @@ -170,13 +179,17 @@ impl Instance { self.chunks.get_block_state(pos).map(FluidState::from) } + pub fn get_biome(&self, pos: &BlockPos) -> Option { + self.chunks.get_biome(pos) + } + pub fn set_block_state(&self, pos: &BlockPos, state: BlockState) -> Option { self.chunks.set_block_state(pos, state) } } impl Debug for PartialInstance { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PartialInstance") .field("chunks", &self.chunks) .field("entity_infos", &self.entity_infos) diff --git a/azalea/benches/pathfinder.rs b/azalea/benches/pathfinder.rs index bb4e312a..cddaee2c 100644 --- a/azalea/benches/pathfinder.rs +++ b/azalea/benches/pathfinder.rs @@ -4,6 +4,7 @@ use azalea::{ BlockPos, pathfinder::{ astar::{self, PathfinderTimeout, WeightedNode, a_star}, + custom_state::CustomPathfinderStateRef, goals::{BlockPosGoal, Goal}, mining::MiningCache, rel_block_pos::RelBlockPos, @@ -41,13 +42,13 @@ fn generate_bedrock_world( let mut chunk = chunk.write(); for x in 0..16_u8 { for z in 0..16_u8 { - chunk.set( + chunk.set_block_state( &ChunkBlockPos::new(x, 1, z), azalea_registry::Block::Bedrock.into(), chunks.min_y, ); if rng.gen_bool(0.5) { - chunk.set( + chunk.set_block_state( &ChunkBlockPos::new(x, 2, z), azalea_registry::Block::Bedrock.into(), chunks.min_y, @@ -99,7 +100,7 @@ fn generate_mining_world( for y in chunks.min_y..(chunks.min_y + chunks.height as i32) { for x in 0..16_u8 { for z in 0..16_u8 { - chunk.set( + chunk.set_block_state( &ChunkBlockPos::new(x, y, z), azalea_registry::Block::Stone.into(), chunks.min_y, @@ -134,7 +135,13 @@ fn run_pathfinder_benchmark( let goal = BlockPosGoal(end); let successors = |pos: RelBlockPos| { - azalea::pathfinder::call_successors_fn(&cached_world, &mining_cache, successors_fn, pos) + azalea::pathfinder::call_successors_fn( + &cached_world, + &mining_cache, + &CustomPathfinderStateRef::default(), + successors_fn, + pos, + ) }; let astar::Path { diff --git a/azalea/benches/physics.rs b/azalea/benches/physics.rs index 5eb164d7..2f122014 100644 --- a/azalea/benches/physics.rs +++ b/azalea/benches/physics.rs @@ -25,7 +25,7 @@ fn generate_world(partial_chunks: &mut PartialChunkStorage, size: u32) -> ChunkS let mut chunk = chunk.write(); for x in 0..16_u8 { for z in 0..16_u8 { - chunk.set( + chunk.set_block_state( &ChunkBlockPos::new(x, 1, z), azalea_registry::Block::OakFence.into(), chunks.min_y, diff --git a/azalea/build.rs b/azalea/build.rs index cd0a16b2..faed1365 100644 --- a/azalea/build.rs +++ b/azalea/build.rs @@ -1,5 +1,4 @@ -use std::env; -use std::process::Command; +use std::{env, process::Command}; fn main() { // If using `rustup`, check the toolchain via `RUSTUP_TOOLCHAIN` diff --git a/azalea/examples/echo.rs b/azalea/examples/echo.rs index 1e773b7d..80b0cb15 100644 --- a/azalea/examples/echo.rs +++ b/azalea/examples/echo.rs @@ -18,13 +18,14 @@ async fn main() { pub struct State {} async fn handle(bot: Client, event: Event, _state: State) -> anyhow::Result<()> { - if let Event::Chat(m) = event { - if let (Some(sender), content) = m.split_sender_and_content() { - if sender == bot.username() { - return Ok(()); // ignore our own messages - } - bot.chat(&content); - }; + if let Event::Chat(m) = event + && let (Some(sender), content) = m.split_sender_and_content() + { + if sender == bot.username() { + // ignore our own messages + return Ok(()); + } + bot.chat(&content); } Ok(()) diff --git a/azalea/examples/nearest_entity.rs b/azalea/examples/nearest_entity.rs index 1fb6bdd1..2e6973cf 100644 --- a/azalea/examples/nearest_entity.rs +++ b/azalea/examples/nearest_entity.rs @@ -1,10 +1,10 @@ -use azalea::ClientBuilder; -use azalea::nearest_entity::EntityFinder; -use azalea::{Bot, LookAtEvent}; +use azalea::{Bot, ClientBuilder, LookAtEvent, nearest_entity::EntityFinder}; use azalea_client::Account; use azalea_core::tick::GameTick; -use azalea_entity::metadata::{ItemItem, Player}; -use azalea_entity::{EyeHeight, LocalEntity, Position}; +use azalea_entity::{ + EyeHeight, LocalEntity, Position, + metadata::{ItemItem, Player}, +}; use bevy_app::Plugin; use bevy_ecs::{ prelude::{Entity, EventWriter}, diff --git a/azalea/examples/steal.rs b/azalea/examples/steal.rs index 028c1b5a..899c2568 100644 --- a/azalea/examples/steal.rs +++ b/azalea/examples/steal.rs @@ -2,10 +2,8 @@ use std::sync::Arc; -use azalea::pathfinder::goals::RadiusGoal; -use azalea::{BlockPos, prelude::*}; -use azalea_inventory::ItemStack; -use azalea_inventory::operations::QuickMoveClick; +use azalea::{BlockPos, pathfinder::goals::RadiusGoal, prelude::*}; +use azalea_inventory::{ItemStack, operations::QuickMoveClick}; use parking_lot::Mutex; #[tokio::main] diff --git a/azalea/examples/testbot/commands.rs b/azalea/examples/testbot/commands.rs index 4e261e17..79a73bd9 100644 --- a/azalea/examples/testbot/commands.rs +++ b/azalea/examples/testbot/commands.rs @@ -2,12 +2,10 @@ pub mod combat; pub mod debug; pub mod movement; -use azalea::Client; -use azalea::GameProfileComponent; -use azalea::brigadier::prelude::*; -use azalea::chat::ChatPacket; -use azalea::ecs::prelude::*; -use azalea::entity::metadata::Player; +use azalea::{ + Client, brigadier::prelude::*, chat::ChatPacket, ecs::prelude::*, entity::metadata::Player, + player::GameProfileComponent, +}; use parking_lot::Mutex; use crate::State; diff --git a/azalea/examples/testbot/commands/debug.rs b/azalea/examples/testbot/commands/debug.rs index 075348b2..b97ad71d 100644 --- a/azalea/examples/testbot/commands/debug.rs +++ b/azalea/examples/testbot/commands/debug.rs @@ -1,6 +1,6 @@ //! Commands for debugging and getting the current state of the bot. -use std::{env, fs::File, io::Write, thread, time::Duration}; +use std::{env, fs::File, io::Write, process, thread, time::Duration}; use azalea::{ BlockPos, @@ -288,7 +288,7 @@ pub fn register(commands: &mut CommandDispatcher>) { thread::spawn(move || { thread::sleep(Duration::from_secs(1)); - std::process::exit(0); + process::exit(0); }); 1 diff --git a/azalea/examples/testbot/main.rs b/azalea/examples/testbot/main.rs index 487cea1e..8a35a281 100644 --- a/azalea/examples/testbot/main.rs +++ b/azalea/examples/testbot/main.rs @@ -25,16 +25,12 @@ mod commands; pub mod killaura; -use std::time::Duration; -use std::{env, process}; -use std::{sync::Arc, thread}; +use std::{env, process, sync::Arc, thread, time::Duration}; -use azalea::ClientInformation; -use azalea::brigadier::command_dispatcher::CommandDispatcher; -use azalea::ecs::prelude::*; -use azalea::pathfinder::debug::PathfinderDebugParticles; -use azalea::prelude::*; -use azalea::swarm::prelude::*; +use azalea::{ + ClientInformation, brigadier::command_dispatcher::CommandDispatcher, ecs::prelude::*, + pathfinder::debug::PathfinderDebugParticles, prelude::*, swarm::prelude::*, +}; use commands::{CommandSource, register_commands}; use parking_lot::Mutex; @@ -187,6 +183,9 @@ async fn handle(bot: Client, event: azalea::Event, state: State) -> anyhow::Resu BotTask::None => {} } } + azalea::Event::Login => { + println!("Got login event") + } _ => {} } diff --git a/azalea/src/accept_resource_packs.rs b/azalea/src/accept_resource_packs.rs index c310d541..4518abcb 100644 --- a/azalea/src/accept_resource_packs.rs +++ b/azalea/src/accept_resource_packs.rs @@ -1,12 +1,18 @@ -use azalea_client::InConfigState; -use azalea_client::chunks::handle_chunk_batch_finished_event; -use azalea_client::inventory::InventorySet; -use azalea_client::packet::config::SendConfigPacketEvent; -use azalea_client::packet::game::SendPacketEvent; -use azalea_client::packet::{death_event_on_0_health, game::ResourcePackEvent}; -use azalea_client::respawn::perform_respawn; -use azalea_protocol::packets::config; -use azalea_protocol::packets::game::s_resource_pack::{self, ServerboundResourcePack}; +use azalea_client::{ + InConfigState, + chunks::handle_chunk_batch_finished_event, + inventory::InventorySet, + packet::{ + config::SendConfigPacketEvent, + death_event_on_0_health, + game::{ResourcePackEvent, SendPacketEvent}, + }, + respawn::perform_respawn, +}; +use azalea_protocol::packets::{ + config, + game::s_resource_pack::{self, ServerboundResourcePack}, +}; use bevy_app::Update; use bevy_ecs::prelude::*; diff --git a/azalea/src/bot.rs b/azalea/src/bot.rs index 63a3adcb..8bc9d594 100644 --- a/azalea/src/bot.rs +++ b/azalea/src/bot.rs @@ -1,9 +1,13 @@ use std::f64::consts::PI; -use azalea_client::mining::Mining; -use azalea_client::tick_broadcast::{TickBroadcast, UpdateBroadcast}; -use azalea_core::position::{BlockPos, Vec3}; -use azalea_core::tick::GameTick; +use azalea_client::{ + mining::Mining, + tick_broadcast::{TickBroadcast, UpdateBroadcast}, +}; +use azalea_core::{ + position::{BlockPos, Vec3}, + tick::GameTick, +}; use azalea_entity::{ EyeHeight, Jumping, LocalEntity, LookDirection, Position, clamp_look_direction, metadata::Player, @@ -14,18 +18,20 @@ use bevy_ecs::prelude::*; use futures_lite::Future; use tracing::trace; -use crate::accept_resource_packs::AcceptResourcePacksPlugin; -use crate::app::{App, Plugin, PluginGroup, PluginGroupBuilder}; -use crate::auto_respawn::AutoRespawnPlugin; -use crate::container::ContainerPlugin; -use crate::ecs::{ - component::Component, - entity::Entity, - event::EventReader, - query::{With, Without}, - system::{Commands, Query}, +use crate::{ + accept_resource_packs::AcceptResourcePacksPlugin, + app::{App, Plugin, PluginGroup, PluginGroupBuilder}, + auto_respawn::AutoRespawnPlugin, + container::ContainerPlugin, + ecs::{ + component::Component, + entity::Entity, + event::EventReader, + query::{With, Without}, + system::{Commands, Query}, + }, + pathfinder::PathfinderPlugin, }; -use crate::pathfinder::PathfinderPlugin; #[derive(Clone, Default)] pub struct BotPlugin; diff --git a/azalea/src/container.rs b/azalea/src/container.rs index 0d1cfb16..6715cd63 100644 --- a/azalea/src/container.rs +++ b/azalea/src/container.rs @@ -1,10 +1,9 @@ -use std::fmt::Debug; -use std::fmt::Formatter; +use std::{fmt, fmt::Debug}; -use azalea_client::packet::game::ReceiveGamePacketEvent; use azalea_client::{ Client, inventory::{CloseContainerEvent, ContainerClickEvent, Inventory}, + packet::game::ReceiveGamePacketEvent, }; use azalea_core::position::BlockPos; use azalea_inventory::{ItemStack, Menu, operations::ClickOperation}; @@ -121,7 +120,7 @@ pub struct ContainerHandleRef { client: Client, } impl Debug for ContainerHandleRef { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ContainerHandle") .field("id", &self.id()) .finish() @@ -192,7 +191,7 @@ impl Drop for ContainerHandle { } } impl Debug for ContainerHandle { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ContainerHandle") .field("id", &self.id()) .finish() diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs index 26dde1fa..b3e8a7d9 100644 --- a/azalea/src/lib.rs +++ b/azalea/src/lib.rs @@ -13,8 +13,7 @@ pub mod pathfinder; pub mod prelude; pub mod swarm; -use std::net::SocketAddr; -use std::time::Duration; +use std::{net::SocketAddr, time::Duration}; use app::Plugins; pub use azalea_auth as auth; @@ -39,8 +38,7 @@ pub use bevy_ecs as ecs; pub use bot::*; use ecs::component::Component; use futures::{Future, future::BoxFuture}; -use protocol::connect::Proxy; -use protocol::{ServerAddress, resolver::ResolverError}; +use protocol::{ServerAddress, connect::Proxy, resolver::ResolverError}; use swarm::SwarmBuilder; use thiserror::Error; @@ -109,7 +107,11 @@ impl ClientBuilder { /// use azalea::app::PluginGroup; /// /// let client_builder = ClientBuilder::new_without_plugins() - /// .add_plugins(azalea::DefaultPlugins.build().disable::()) + /// .add_plugins( + /// azalea::DefaultPlugins + /// .build() + /// .disable::(), + /// ) /// .add_plugins(azalea::DefaultBotPlugins); /// # client_builder.set_handler(handle); /// # #[derive(Component, Clone, Default)] diff --git a/azalea/src/nearest_entity.rs b/azalea/src/nearest_entity.rs index aa6c6e6f..7ac4fff0 100644 --- a/azalea/src/nearest_entity.rs +++ b/azalea/src/nearest_entity.rs @@ -15,13 +15,16 @@ use bevy_ecs::{ /// applied filter. /// /// ``` -/// use azalea::chat::SendChatEvent; -/// use azalea::nearest_entity::EntityFinder; -/// use azalea_entity::metadata::{Player, AbstractMonster}; -/// use azalea_entity::LocalEntity; -/// use bevy_ecs::system::Query; -/// use bevy_ecs::prelude::{Entity, EventWriter}; -/// use bevy_ecs::query::With; +/// use azalea::{chat::SendChatEvent, nearest_entity::EntityFinder}; +/// use azalea_entity::{ +/// LocalEntity, +/// metadata::{AbstractMonster, Player}, +/// }; +/// use bevy_ecs::{ +/// prelude::{Entity, EventWriter}, +/// query::With, +/// system::Query, +/// }; /// /// /// All bots near aggressive mobs will scream in chat. /// pub fn bots_near_aggressive_mobs( diff --git a/azalea/src/pathfinder/astar.rs b/azalea/src/pathfinder/astar.rs index cd05d2ba..8d23ed2e 100644 --- a/azalea/src/pathfinder/astar.rs +++ b/azalea/src/pathfinder/astar.rs @@ -1,7 +1,7 @@ use std::{ cmp::{self}, collections::BinaryHeap, - fmt::Debug, + fmt::{self, Debug}, hash::{BuildHasherDefault, Hash}, time::{Duration, Instant}, }; @@ -221,7 +221,12 @@ where best_successor = Some(successor); } } - let found_successor = best_successor.expect("No successor found"); + let Some(found_successor) = best_successor else { + warn!( + "a successor stopped being possible while reconstructing the path, returning empty path" + ); + return vec![]; + }; path.push(Movement { target: node_position, @@ -239,6 +244,7 @@ pub struct Node { pub g_score: f32, } +#[derive(Clone, Debug)] pub struct Edge { pub movement: Movement, pub cost: f32, @@ -250,7 +256,7 @@ pub struct Movement { } impl Debug for Movement { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Movement") .field("target", &self.target) .field("data", &self.data) @@ -279,26 +285,11 @@ pub struct WeightedNode { impl Ord for WeightedNode { #[inline] fn cmp(&self, other: &Self) -> cmp::Ordering { - // we compare bits instead of floats because it's faster. this is the same as - // f32::total_cmp as long as the numbers aren't negative - - debug_assert!(self.f_score >= 0.0); - debug_assert!(other.f_score >= 0.0); - debug_assert!(self.g_score >= 0.0); - debug_assert!(other.g_score >= 0.0); - - let self_f_score = self.f_score.to_bits() as i32; - let other_f_score = other.f_score.to_bits() as i32; - - if self_f_score == other_f_score { - let self_g_score = self.g_score.to_bits() as i32; - let other_g_score = other.g_score.to_bits() as i32; - - return self_g_score.cmp(&other_g_score); - } - // intentionally inverted to make the BinaryHeap a min-heap - other_f_score.cmp(&self_f_score) + match other.f_score.total_cmp(&self.f_score) { + cmp::Ordering::Equal => self.g_score.total_cmp(&other.g_score), + s => s, + } } } impl Eq for WeightedNode {} diff --git a/azalea/src/pathfinder/custom_state.rs b/azalea/src/pathfinder/custom_state.rs new file mode 100644 index 00000000..f5129516 --- /dev/null +++ b/azalea/src/pathfinder/custom_state.rs @@ -0,0 +1,46 @@ +use std::{ + any::{Any, TypeId}, + sync::Arc, +}; + +use bevy_ecs::component::Component; +use parking_lot::RwLock; +use rustc_hash::FxHashMap; + +/// The component that holds the custom pathfinder state for one of our bots. +/// +/// See [`CustomPathfinderStateRef`] for more information about the inner type. +/// +/// Azalea won't automatically insert this component, so if you're trying to use +/// it then you should also have logic to insert the component if it's not +/// present. +/// +/// Be aware that a read lock is held on the `RwLock` while a path is being +/// calculated, which may take up to several seconds. For this reason, it may be +/// favorable to use [`RwLock::try_write`] instead of [`RwLock::write`] when +/// updating it to avoid blocking the current thread. +#[derive(Clone, Component, Default)] +pub struct CustomPathfinderState(pub Arc>); + +/// Arbitrary state that's passed to the pathfinder, intended to be used for +/// custom moves that need to access things that are usually inaccessible. +/// +/// This is included in [`PathfinderCtx`]. +/// +/// [`PathfinderCtx`]: crate::pathfinder::PathfinderCtx +#[derive(Debug, Default)] +pub struct CustomPathfinderStateRef { + map: FxHashMap>, +} + +impl CustomPathfinderStateRef { + pub fn insert(&mut self, t: T) { + self.map.insert(TypeId::of::(), Box::new(t)); + } + + pub fn get(&self) -> Option<&T> { + self.map + .get(&TypeId::of::()) + .map(|value| value.downcast_ref().unwrap()) + } +} diff --git a/azalea/src/pathfinder/debug.rs b/azalea/src/pathfinder/debug.rs index da27708d..b00e4272 100644 --- a/azalea/src/pathfinder/debug.rs +++ b/azalea/src/pathfinder/debug.rs @@ -1,12 +1,16 @@ -use azalea_client::{InstanceHolder, chat::SendChatEvent}; +use azalea_client::{chat::SendChatEvent, local_player::InstanceHolder}; use azalea_core::position::Vec3; use bevy_ecs::prelude::*; use super::ExecutingPath; /// A component that makes bots run /particle commands while pathfinding to show -/// where they're going. This requires the bots to have server operator -/// permissions, and it'll make them spam *a lot* of commands. +/// where they're going. +/// +/// This requires the bots to have server operator permissions, and it'll make +/// them spam *a lot* of commands. You may want to run `/gamerule +/// sendCommandFeedback false` to hide the "Displaying particle minecraft:dust" +/// spam. /// /// ``` /// # use azalea::prelude::*; @@ -54,7 +58,8 @@ pub fn debug_render_path_with_particles( let chunks = &instance_holder.instance.read().chunks; let mut start = executing_path.last_reached_node; - for (i, movement) in executing_path.path.iter().enumerate() { + for (i, edge) in executing_path.path.iter().enumerate() { + let movement = &edge.movement; let end = movement.target; let start_vec3 = start.center(); diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index 820d6f8b..08c72f9a 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -4,6 +4,7 @@ pub mod astar; pub mod costs; +pub mod custom_state; pub mod debug; pub mod goals; pub mod mining; @@ -12,28 +13,34 @@ pub mod rel_block_pos; pub mod simulation; pub mod world; -use std::collections::VecDeque; -use std::ops::RangeInclusive; -use std::sync::Arc; -use std::sync::atomic::{self, AtomicUsize}; -use std::time::{Duration, Instant}; -use std::{cmp, thread}; +use std::{ + cmp, + collections::VecDeque, + ops::RangeInclusive, + sync::{ + Arc, + atomic::{self, AtomicUsize}, + }, + thread, + time::{Duration, Instant}, +}; -use astar::PathfinderTimeout; -use azalea_client::inventory::{Inventory, InventorySet, SetSelectedHotbarSlotEvent}; -use azalea_client::mining::{Mining, StartMiningBlockEvent}; -use azalea_client::movement::MoveEventsSet; -use azalea_client::{InstanceHolder, StartSprintEvent, StartWalkEvent}; -use azalea_core::position::BlockPos; -use azalea_core::tick::GameTick; -use azalea_entity::LocalEntity; -use azalea_entity::metadata::Player; -use azalea_entity::{Physics, Position}; +use astar::{Edge, PathfinderTimeout}; +use azalea_client::{ + StartSprintEvent, StartWalkEvent, + inventory::{Inventory, InventorySet, SetSelectedHotbarSlotEvent}, + local_player::InstanceHolder, + mining::{Mining, MiningSet, StartMiningBlockEvent}, + movement::MoveEventsSet, +}; +use azalea_core::{position::BlockPos, tick::GameTick}; +use azalea_entity::{LocalEntity, Physics, Position, metadata::Player}; use azalea_physics::PhysicsSet; use azalea_world::{InstanceContainer, InstanceName}; use bevy_app::{PreUpdate, Update}; use bevy_ecs::prelude::*; use bevy_tasks::{AsyncComputeTaskPool, Task}; +use custom_state::{CustomPathfinderState, CustomPathfinderStateRef}; use futures_lite::future; use goals::BlockPosGoal; use parking_lot::RwLock; @@ -41,21 +48,25 @@ use rel_block_pos::RelBlockPos; use tokio::sync::broadcast::error::RecvError; use tracing::{debug, error, info, trace, warn}; -use self::debug::debug_render_path_with_particles; -use self::goals::Goal; -use self::mining::MiningCache; -use self::moves::{ExecuteCtx, IsReachedCtx, SuccessorsFn}; -use crate::app::{App, Plugin}; -use crate::bot::{JumpEvent, LookAtEvent}; -use crate::ecs::{ - component::Component, - entity::Entity, - event::{EventReader, EventWriter}, - query::{With, Without}, - system::{Commands, Query, Res}, +use self::{ + debug::debug_render_path_with_particles, + goals::Goal, + mining::MiningCache, + moves::{ExecuteCtx, IsReachedCtx, SuccessorsFn}, +}; +use crate::{ + BotClientExt, WalkDirection, + app::{App, Plugin}, + bot::{JumpEvent, LookAtEvent}, + ecs::{ + component::Component, + entity::Entity, + event::{EventReader, EventWriter}, + query::{With, Without}, + system::{Commands, Query, Res}, + }, + pathfinder::{astar::a_star, moves::PathfinderCtx, world::CachedWorld}, }; -use crate::pathfinder::{astar::a_star, moves::PathfinderCtx, world::CachedWorld}; -use crate::{BotClientExt, WalkDirection}; #[derive(Clone, Default)] pub struct PathfinderPlugin; @@ -79,7 +90,8 @@ impl Plugin for PathfinderPlugin { ) .chain() .after(PhysicsSet) - .after(azalea_client::movement::send_position), + .after(azalea_client::movement::send_position) + .after(MiningSet), ) .add_systems(PreUpdate, add_default_pathfinder) .add_systems( @@ -116,8 +128,8 @@ pub struct Pathfinder { /// pathfinder path. #[derive(Component, Clone)] pub struct ExecutingPath { - pub path: VecDeque>, - pub queued_path: Option>>, + pub path: VecDeque>, + pub queued_path: Option>>, pub last_reached_node: BlockPos, pub last_node_reached_at: Instant, pub is_path_partial: bool, @@ -134,7 +146,7 @@ pub struct GotoEvent { pub entity: Entity, pub goal: Arc, /// The function that's used for checking what moves are possible. Usually - /// `pathfinder::moves::default_move` + /// [`moves::default_move`]. pub successors_fn: SuccessorsFn, /// Whether the bot is allowed to break blocks while pathfinding. @@ -161,7 +173,7 @@ pub struct GotoEvent { pub struct PathFoundEvent { pub entity: Entity, pub start: BlockPos, - pub path: Option>>, + pub path: Option>>, pub is_partial: bool, pub successors_fn: SuccessorsFn, pub allow_mining: bool, @@ -190,6 +202,8 @@ impl PathfinderClientExt for azalea_client::Client { /// Pathfind to the given goal and wait until either the target is reached /// or the pathfinding is canceled. /// + /// You can use [`Self::start_goto`] instead if you don't want to wait. + /// /// ``` /// # use azalea::prelude::*; /// # use azalea::{BlockPos, pathfinder::goals::BlockPosGoal}; @@ -270,6 +284,7 @@ impl PathfinderClientExt for azalea_client::Client { #[derive(Component)] pub struct ComputePath(Task>); +#[allow(clippy::type_complexity)] pub fn goto_listener( mut commands: Commands, mut events: EventReader, @@ -279,13 +294,14 @@ pub fn goto_listener( &Position, &InstanceName, &Inventory, + Option<&CustomPathfinderState>, )>, instance_container: Res, ) { let thread_pool = AsyncComputeTaskPool::get(); for event in events.read() { - let Ok((mut pathfinder, executing_path, position, instance_name, inventory)) = + let Ok((mut pathfinder, executing_path, position, instance_name, inventory, custom_state)) = query.get_mut(event.entity) else { warn!("got goto event for an entity that can't pathfind"); @@ -313,7 +329,12 @@ pub fn goto_listener( && let Some(final_node) = executing_path.path.back() { // if we're currently pathfinding and got a goto event, start a little ahead - executing_path.path.get(50).unwrap_or(final_node).target + executing_path + .path + .get(50) + .unwrap_or(final_node) + .movement + .target } else { BlockPos::from(position) }; @@ -346,6 +367,8 @@ pub fn goto_listener( None }); + let custom_state = custom_state.cloned().unwrap_or_default(); + let min_timeout = event.min_timeout; let max_timeout = event.max_timeout; @@ -359,6 +382,7 @@ pub fn goto_listener( goto_id_atomic, allow_mining, mining_cache, + custom_state, min_timeout, max_timeout, }) @@ -377,6 +401,7 @@ pub struct CalculatePathOpts { pub goto_id_atomic: Arc, pub allow_mining: bool, pub mining_cache: MiningCache, + pub custom_state: CustomPathfinderState, /// Also see [`GotoEvent::min_timeout`]. pub min_timeout: PathfinderTimeout, pub max_timeout: PathfinderTimeout, @@ -398,7 +423,13 @@ pub fn calculate_path(opts: CalculatePathOpts) -> Option { let origin = opts.start; let cached_world = CachedWorld::new(opts.world_lock, origin); let successors = |pos: RelBlockPos| { - call_successors_fn(&cached_world, &opts.mining_cache, opts.successors_fn, pos) + call_successors_fn( + &cached_world, + &opts.mining_cache, + &opts.custom_state.0.read(), + opts.successors_fn, + pos, + ) }; let start_time = Instant::now(); @@ -447,14 +478,32 @@ pub fn calculate_path(opts: CalculatePathOpts) -> Option { debug!("this path is empty, we might be stuck :("); } - // replace the RelBlockPos types with BlockPos - let mapped_path = path - .into_iter() - .map(|movement| astar::Movement { - target: movement.target.apply(origin), - data: movement.data, - }) - .collect(); + let mut mapped_path = VecDeque::with_capacity(path.len()); + let mut current_position = RelBlockPos::get_origin(origin); + for movement in path { + let mut found_edge = None; + for edge in successors(current_position) { + if edge.movement.target == movement.target { + found_edge = Some(edge); + break; + } + } + + let found_edge = found_edge.expect( + "path should always still be possible because we're using the same world cache", + ); + current_position = found_edge.movement.target; + + // we don't just clone the found_edge because we're using BlockPos instead of + // RelBlockPos as the target type + mapped_path.push_back(Edge { + movement: astar::Movement { + target: movement.target.apply(origin), + data: movement.data, + }, + cost: found_edge.cost, + }); + } Some(PathFoundEvent { entity: opts.entity, @@ -485,6 +534,7 @@ pub fn handle_tasks( } // set the path for the target entity when we get the PathFoundEvent +#[allow(clippy::type_complexity)] pub fn path_found_listener( mut events: EventReader, mut query: Query<( @@ -492,12 +542,13 @@ pub fn path_found_listener( Option<&mut ExecutingPath>, &InstanceName, &Inventory, + Option<&CustomPathfinderState>, )>, instance_container: Res, mut commands: Commands, ) { for event in events.read() { - let (mut pathfinder, executing_path, instance_name, inventory) = query + let (mut pathfinder, executing_path, instance_name, inventory, custom_state) = query .get_mut(event.entity) .expect("Path found for an entity that doesn't have a pathfinder"); if let Some(path) = &event.path { @@ -518,15 +569,27 @@ pub fn path_found_listener( } else { None }); + let custom_state = custom_state.cloned().unwrap_or_default(); + let custom_state_ref = custom_state.0.read(); let successors = |pos: RelBlockPos| { - call_successors_fn(&cached_world, &mining_cache, successors_fn, pos) + call_successors_fn( + &cached_world, + &mining_cache, + &custom_state_ref, + successors_fn, + pos, + ) }; if let Some(first_node_of_new_path) = path.front() { - let last_target_of_current_path = - RelBlockPos::from_origin(origin, last_node_of_current_path.target); - let first_target_of_new_path = - RelBlockPos::from_origin(origin, first_node_of_new_path.target); + let last_target_of_current_path = RelBlockPos::from_origin( + origin, + last_node_of_current_path.movement.target, + ); + let first_target_of_new_path = RelBlockPos::from_origin( + origin, + first_node_of_new_path.movement.target, + ); if successors(last_target_of_current_path) .iter() @@ -589,11 +652,20 @@ pub fn timeout_movement( Option<&Mining>, &InstanceName, &Inventory, + Option<&CustomPathfinderState>, )>, instance_container: Res, ) { - for (entity, mut pathfinder, mut executing_path, position, mining, instance_name, inventory) in - &mut query + for ( + entity, + mut pathfinder, + mut executing_path, + position, + mining, + instance_name, + inventory, + custom_state, + ) in &mut query { // don't timeout if we're mining if let Some(mining) = mining { @@ -619,6 +691,8 @@ pub fn timeout_movement( .expect("Entity tried to pathfind but the entity isn't in a valid world"); let successors_fn: moves::SuccessorsFn = pathfinder.successors_fn.unwrap(); + let custom_state = custom_state.cloned().unwrap_or_default(); + // try to fix the path without recalculating everything. // (though, it'll still get fully recalculated by `recalculate_near_end_of_path` // if the new path is too short) @@ -630,6 +704,7 @@ pub fn timeout_movement( entity, successors_fn, world_lock, + custom_state, ); // reset last_node_reached_at so we don't immediately try to patch again executing_path.last_node_reached_at = Instant::now(); @@ -654,7 +729,7 @@ pub fn check_node_reached( // we don't unnecessarily execute a movement when it wasn't necessary // see if we already reached any future nodes and can skip ahead - for (i, movement) in executing_path + for (i, edge) in executing_path .path .clone() .into_iter() @@ -662,6 +737,7 @@ pub fn check_node_reached( .take(20) .rev() { + let movement = edge.movement; let is_reached_ctx = IsReachedCtx { target: movement.target, start: executing_path.last_reached_node, @@ -732,6 +808,7 @@ pub fn check_node_reached( } } +#[allow(clippy::type_complexity)] pub fn check_for_path_obstruction( mut query: Query<( Entity, @@ -739,10 +816,13 @@ pub fn check_for_path_obstruction( &mut ExecutingPath, &InstanceName, &Inventory, + Option<&CustomPathfinderState>, )>, instance_container: Res, ) { - for (entity, mut pathfinder, mut executing_path, instance_name, inventory) in &mut query { + for (entity, mut pathfinder, mut executing_path, instance_name, inventory, custom_state) in + &mut query + { let Some(successors_fn) = pathfinder.successors_fn else { continue; }; @@ -759,52 +839,67 @@ pub fn check_for_path_obstruction( } else { None }); - let successors = - |pos: RelBlockPos| call_successors_fn(&cached_world, &mining_cache, successors_fn, pos); + let custom_state = custom_state.cloned().unwrap_or_default(); + let custom_state_ref = custom_state.0.read(); + let successors = |pos: RelBlockPos| { + call_successors_fn( + &cached_world, + &mining_cache, + &custom_state_ref, + successors_fn, + pos, + ) + }; - if let Some(obstructed_index) = check_path_obstructed( + let Some(obstructed_index) = check_path_obstructed( origin, RelBlockPos::from_origin(origin, executing_path.last_reached_node), &executing_path.path, successors, - ) { - warn!( - "path obstructed at index {obstructed_index} (starting at {:?}, path: {:?})", - executing_path.last_reached_node, executing_path.path - ); - // if it's near the end, don't bother recalculating a patch, just truncate and - // mark it as partial - if obstructed_index + 5 > executing_path.path.len() { - debug!( - "obstruction is near the end of the path, truncating and marking path as partial" - ); - executing_path.path.truncate(obstructed_index); - executing_path.is_path_partial = true; - continue; - } - - let Some(successors_fn) = pathfinder.successors_fn else { - error!("got PatchExecutingPathEvent but the bot has no successors_fn"); - continue; - }; - - let world_lock = instance_container - .get(instance_name) - .expect("Entity tried to pathfind but the entity isn't in a valid world"); - - // patch up to 20 nodes - let patch_end_index = cmp::min(obstructed_index + 20, executing_path.path.len() - 1); - - patch_path( - obstructed_index..=patch_end_index, - &mut executing_path, - &mut pathfinder, - inventory, - entity, - successors_fn, - world_lock, + ) else { + continue; + }; + + drop(custom_state_ref); + + warn!( + "path obstructed at index {obstructed_index} (starting at {:?})", + executing_path.last_reached_node, + ); + debug!("obstructed path: {:?}", executing_path.path); + // if it's near the end, don't bother recalculating a patch, just truncate and + // mark it as partial + if obstructed_index + 5 > executing_path.path.len() { + debug!( + "obstruction is near the end of the path, truncating and marking path as partial" ); + executing_path.path.truncate(obstructed_index); + executing_path.is_path_partial = true; + continue; } + + let Some(successors_fn) = pathfinder.successors_fn else { + error!("got PatchExecutingPathEvent but the bot has no successors_fn"); + continue; + }; + + let world_lock = instance_container + .get(instance_name) + .expect("Entity tried to pathfind but the entity isn't in a valid world"); + + // patch up to 20 nodes + let patch_end_index = cmp::min(obstructed_index + 20, executing_path.path.len() - 1); + + patch_path( + obstructed_index..=patch_end_index, + &mut executing_path, + &mut pathfinder, + inventory, + entity, + successors_fn, + world_lock, + custom_state.clone(), + ); } } @@ -813,6 +908,7 @@ pub fn check_for_path_obstruction( /// /// You should avoid making the range too large, since the timeout for the A* /// calculation is very low. About 20 nodes is a good amount. +#[allow(clippy::too_many_arguments)] fn patch_path( patch_nodes: RangeInclusive, executing_path: &mut ExecutingPath, @@ -821,14 +917,17 @@ fn patch_path( entity: Entity, successors_fn: SuccessorsFn, world_lock: Arc>, + custom_state: CustomPathfinderState, ) { let patch_start = if *patch_nodes.start() == 0 { executing_path.last_reached_node } else { - executing_path.path[*patch_nodes.start() - 1].target + executing_path.path[*patch_nodes.start() - 1] + .movement + .target }; - let patch_end = executing_path.path[*patch_nodes.end()].target; + let patch_end = executing_path.path[*patch_nodes.end()].movement.target; // this doesn't override the main goal, it's just the goal for this A* // calculation @@ -853,6 +952,7 @@ fn patch_path( goto_id_atomic, allow_mining, mining_cache, + custom_state, min_timeout: PathfinderTimeout::Nodes(10_000), max_timeout: PathfinderTimeout::Nodes(10_000), }); @@ -928,8 +1028,7 @@ pub fn recalculate_near_end_of_path( allow_mining: pathfinder.allow_mining, min_timeout: if executing_path.path.len() == 50 { // we have quite some time until the node is reached, soooo we might as - // well burn some cpu cycles to get a good - // path + // well burn some cpu cycles to get a good path PathfinderTimeout::Time(Duration::from_secs(5)) } else { PathfinderTimeout::Time(Duration::from_secs(1)) @@ -996,10 +1095,10 @@ pub fn tick_execute_path( for (entity, executing_path, position, physics, mining, instance_holder, inventory_component) in &mut query { - if let Some(movement) = executing_path.path.front() { + if let Some(edge) = executing_path.path.front() { let ctx = ExecuteCtx { entity, - target: movement.target, + target: edge.movement.target, position: **position, start: executing_path.last_reached_node, physics, @@ -1018,7 +1117,7 @@ pub fn tick_execute_path( "executing move, position: {}, last_reached_node: {}", **position, executing_path.last_reached_node ); - (movement.data.execute)(ctx); + (edge.movement.data.execute)(ctx); } } } @@ -1110,26 +1209,36 @@ pub fn stop_pathfinding_on_instance_change( pub fn check_path_obstructed( origin: BlockPos, mut current_position: RelBlockPos, - path: &VecDeque>, + path: &VecDeque>, successors_fn: SuccessorsFn, ) -> Option where SuccessorsFn: Fn(RelBlockPos) -> Vec>, { - for (i, movement) in path.iter().enumerate() { - let movement_target = RelBlockPos::from_origin(origin, movement.target); + for (i, edge) in path.iter().enumerate() { + let movement_target = RelBlockPos::from_origin(origin, edge.movement.target); - let mut found_obstruction = false; - for edge in successors_fn(current_position) { - if edge.movement.target == movement_target { - current_position = movement_target; - found_obstruction = false; + let mut found_edge = None; + for candidate_edge in successors_fn(current_position) { + if candidate_edge.movement.target == movement_target { + found_edge = Some(candidate_edge); break; - } else { - found_obstruction = true; } } - if found_obstruction { + + current_position = movement_target; + // if found_edge is None or the cost increased, then return the index + if found_edge + .map(|found_edge| found_edge.cost > edge.cost) + .unwrap_or(true) + { + // if the node that we're currently executing was obstructed then it's often too + // late to change the path, so it's usually better to just ignore this case :/ + if i == 0 { + warn!("path obstructed at index 0, ignoring"); + continue; + } + return Some(i); } } @@ -1140,6 +1249,7 @@ where pub fn call_successors_fn( cached_world: &CachedWorld, mining_cache: &MiningCache, + custom_state: &CustomPathfinderStateRef, successors_fn: SuccessorsFn, pos: RelBlockPos, ) -> Vec> { @@ -1148,6 +1258,7 @@ pub fn call_successors_fn( edges: &mut edges, world: cached_world, mining_cache, + custom_state, }; successors_fn(&mut ctx, pos); edges @@ -1158,9 +1269,11 @@ mod tests { use std::{ collections::HashSet, sync::Arc, + thread, time::{Duration, Instant}, }; + use azalea_block::BlockState; use azalea_core::position::{BlockPos, ChunkPos, Vec3}; use azalea_world::{Chunk, ChunkStorage, PartialChunkStorage}; @@ -1176,9 +1289,9 @@ mod tests { partial_chunks: &mut PartialChunkStorage, start_pos: BlockPos, end_pos: BlockPos, - solid_blocks: Vec, + solid_blocks: &[BlockPos], ) -> Simulation { - let mut simulation = setup_simulation_world(partial_chunks, start_pos, solid_blocks); + let mut simulation = setup_simulation_world(partial_chunks, start_pos, solid_blocks, &[]); // you can uncomment this while debugging tests to get trace logs // simulation.app.add_plugins(bevy_log::LogPlugin { @@ -1201,10 +1314,14 @@ mod tests { fn setup_simulation_world( partial_chunks: &mut PartialChunkStorage, start_pos: BlockPos, - solid_blocks: Vec, + solid_blocks: &[BlockPos], + extra_blocks: &[(BlockPos, BlockState)], ) -> Simulation { let mut chunk_positions = HashSet::new(); - for block_pos in &solid_blocks { + for block_pos in solid_blocks { + chunk_positions.insert(ChunkPos::from(block_pos)); + } + for (block_pos, _) in extra_blocks { chunk_positions.insert(ChunkPos::from(block_pos)); } @@ -1213,8 +1330,12 @@ mod tests { partial_chunks.set(&chunk_pos, Some(Chunk::default()), &mut chunks); } for block_pos in solid_blocks { - chunks.set_block_state(&block_pos, azalea_registry::Block::Stone.into()); + chunks.set_block_state(block_pos, azalea_registry::Block::Stone.into()); } + for (block_pos, block_state) in extra_blocks { + chunks.set_block_state(block_pos, *block_state); + } + let player = SimulatedPlayerBundle::new(Vec3::new( start_pos.x as f64 + 0.5, start_pos.y as f64, @@ -1239,7 +1360,7 @@ mod tests { && start_time.elapsed() < Duration::from_millis(500) { simulation.tick(); - std::thread::yield_now(); + thread::yield_now(); } } @@ -1250,7 +1371,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(0, 71, 1), - vec![BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 1)], + &[BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 1)], ); assert_simulation_reaches(&mut simulation, 20, BlockPos::new(0, 71, 1)); } @@ -1262,7 +1383,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(2, 71, 2), - vec![ + &[ BlockPos::new(0, 70, 0), BlockPos::new(1, 70, 1), BlockPos::new(2, 70, 2), @@ -1280,7 +1401,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 3), BlockPos::new(5, 76, 0), - vec![ + &[ BlockPos::new(0, 70, 3), BlockPos::new(0, 70, 2), BlockPos::new(0, 70, 1), @@ -1302,7 +1423,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(0, 71, 3), - vec![BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 3)], + &[BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 3)], ); assert_simulation_reaches(&mut simulation, 40, BlockPos::new(0, 71, 3)); } @@ -1314,7 +1435,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(3, 67, 4), - vec![ + &[ BlockPos::new(0, 70, 0), BlockPos::new(0, 69, 1), BlockPos::new(0, 68, 2), @@ -1333,7 +1454,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(0, 70, 5), - vec![ + &[ BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 1), BlockPos::new(0, 69, 2), @@ -1350,7 +1471,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(0, 68, 3), - vec![ + &[ BlockPos::new(0, 70, 0), BlockPos::new(0, 69, 1), BlockPos::new(0, 68, 2), @@ -1367,7 +1488,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(3, 74, 0), - vec![ + &[ BlockPos::new(0, 70, 0), BlockPos::new(0, 71, 3), BlockPos::new(3, 72, 3), @@ -1384,7 +1505,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(4, 71, 12), - vec![ + &[ BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 4), BlockPos::new(0, 70, 8), @@ -1402,7 +1523,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(4, 74, 9), - vec![ + &[ BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 1), BlockPos::new(0, 70, 2), @@ -1416,4 +1537,41 @@ mod tests { ); assert_simulation_reaches(&mut simulation, 80, BlockPos::new(4, 74, 9)); } + + #[test] + fn test_mine_through_non_colliding_block() { + let mut partial_chunks = PartialChunkStorage::default(); + + let mut simulation = setup_simulation_world( + &mut partial_chunks, + // the pathfinder can't actually dig straight down, so we start a block to the side so + // it can descend correctly + BlockPos::new(0, 72, 1), + &[BlockPos::new(0, 71, 1)], + &[ + ( + BlockPos::new(0, 71, 0), + azalea_registry::Block::SculkVein.into(), + ), + ( + BlockPos::new(0, 70, 0), + azalea_registry::Block::GrassBlock.into(), + ), + // this is an extra check to make sure that we don't accidentally break the block + // below (since tnt will break instantly) + (BlockPos::new(0, 69, 0), azalea_registry::Block::Tnt.into()), + ], + ); + + simulation.app.world_mut().send_event(GotoEvent { + entity: simulation.entity, + goal: Arc::new(BlockPosGoal(BlockPos::new(0, 70, 0))), + successors_fn: moves::default_move, + allow_mining: true, + min_timeout: PathfinderTimeout::Nodes(1_000_000), + max_timeout: PathfinderTimeout::Nodes(5_000_000), + }); + + assert_simulation_reaches(&mut simulation, 200, BlockPos::new(0, 70, 0)); + } } diff --git a/azalea/src/pathfinder/moves/mod.rs b/azalea/src/pathfinder/moves/mod.rs index 24dc8ac1..6c26a507 100644 --- a/azalea/src/pathfinder/moves/mod.rs +++ b/azalea/src/pathfinder/moves/mod.rs @@ -1,7 +1,10 @@ pub mod basic; pub mod parkour; -use std::{fmt::Debug, sync::Arc}; +use std::{ + fmt::{self, Debug}, + sync::Arc, +}; use azalea_block::BlockState; use azalea_client::{ @@ -16,6 +19,7 @@ use parking_lot::RwLock; use super::{ astar, + custom_state::CustomPathfinderStateRef, mining::MiningCache, rel_block_pos::RelBlockPos, world::{CachedWorld, is_block_state_passable}, @@ -40,7 +44,7 @@ pub struct MoveData { pub is_reached: &'static (dyn Fn(IsReachedCtx) -> bool + Send + Sync), } impl Debug for MoveData { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("MoveData") // .field("move_kind", &self.move_kind) .finish() @@ -219,4 +223,6 @@ pub struct PathfinderCtx<'a> { pub edges: &'a mut Vec, pub world: &'a CachedWorld, pub mining_cache: &'a MiningCache, + + pub custom_state: &'a CustomPathfinderStateRef, } diff --git a/azalea/src/pathfinder/simulation.rs b/azalea/src/pathfinder/simulation.rs index 5a68bf88..337efda7 100644 --- a/azalea/src/pathfinder/simulation.rs +++ b/azalea/src/pathfinder/simulation.rs @@ -2,8 +2,13 @@ use std::sync::Arc; -use azalea_client::{PhysicsState, inventory::Inventory, packet::game::SendPacketEvent}; -use azalea_core::{position::Vec3, resource_location::ResourceLocation, tick::GameTick}; +use azalea_client::{ + PhysicsState, interact::CurrentSequenceNumber, inventory::Inventory, + local_player::LocalGameMode, mining::MineBundle, packet::game::SendPacketEvent, +}; +use azalea_core::{ + game_type::GameMode, position::Vec3, resource_location::ResourceLocation, tick::GameTick, +}; use azalea_entity::{ Attributes, EntityDimensions, LookDirection, Physics, Position, attributes::AttributeInstance, }; @@ -87,7 +92,7 @@ fn create_simulation_instance(chunks: ChunkStorage) -> (App, Arc>, player: &SimulatedPlayerBundle, -) -> impl Bundle + use<> { +) -> impl Bundle { let instance_name = simulation_instance_name(); ( @@ -100,12 +105,17 @@ fn create_simulation_player_complete_bundle( azalea_registry::EntityKind::Player, instance_name, ), - azalea_client::InstanceHolder { + azalea_client::local_player::InstanceHolder { // partial_instance is never actually used by the pathfinder so partial_instance: Arc::new(RwLock::new(PartialInstance::default())), instance: instance.clone(), }, Inventory::default(), + LocalGameMode::from(GameMode::Survival), + MineBundle::default(), + CurrentSequenceNumber::default(), + azalea_client::local_player::PermissionLevel::default(), + azalea_client::local_player::PlayerAbilities::default(), ) } diff --git a/azalea/src/pathfinder/world.rs b/azalea/src/pathfinder/world.rs index 940a7c84..b77183e8 100644 --- a/azalea/src/pathfinder/world.rs +++ b/azalea/src/pathfinder/world.rs @@ -9,7 +9,7 @@ use azalea_core::{ position::{BlockPos, ChunkPos, ChunkSectionBlockPos, ChunkSectionPos}, }; use azalea_physics::collision::BlockWithShape; -use azalea_world::Instance; +use azalea_world::{Instance, palette::PalettedContainer}; use parking_lot::RwLock; use super::{mining::MiningCache, rel_block_pos::RelBlockPos}; @@ -26,7 +26,12 @@ pub struct CachedWorld { // we store `PalettedContainer`s instead of `Chunk`s or `Section`s because it doesn't contain // any unnecessary data like heightmaps or biomes. - cached_chunks: RefCell)>>, + cached_chunks: RefCell< + Vec<( + ChunkPos, + Vec>, + )>, + >, last_chunk_cache_index: RefCell>, cached_blocks: UnsafeCell, @@ -82,11 +87,11 @@ impl CachedSections { pub struct CachedSection { pub pos: ChunkSectionPos, /// Blocks that we can fully pass through (like air). - pub passable_bitset: FixedBitSet<{ 4096_usize.div_ceil(8) }>, + pub passable_bitset: FixedBitSet<4096>, /// Blocks that we can stand on and do parkour from. - pub solid_bitset: FixedBitSet<{ 4096_usize.div_ceil(8) }>, + pub solid_bitset: FixedBitSet<4096>, /// Blocks that we can stand on but might not be able to parkour from. - pub standable_bitset: FixedBitSet<{ 4096_usize.div_ceil(8) }>, + pub standable_bitset: FixedBitSet<4096>, } impl CachedWorld { @@ -119,7 +124,7 @@ impl CachedWorld { fn with_section( &self, section_pos: ChunkSectionPos, - f: impl FnOnce(&azalea_world::palette::PalettedContainer) -> T, + f: impl FnOnce(&azalea_world::palette::PalettedContainer) -> T, ) -> Option { if section_pos.y * 16 < self.min_y { // y position is out of bounds @@ -143,7 +148,7 @@ impl CachedWorld { // y position is out of bounds return None; }; - let section: &azalea_world::palette::PalettedContainer = §ions[section_index]; + let section = §ions[section_index]; return Some(f(section)); } @@ -165,7 +170,7 @@ impl CachedWorld { return None; }; *self.last_chunk_cache_index.borrow_mut() = Some(chunk_index); - let section: &azalea_world::palette::PalettedContainer = §ions[section_index]; + let section = §ions[section_index]; return Some(f(section)); } @@ -173,11 +178,11 @@ impl CachedWorld { let chunk = world.chunks.get(&chunk_pos)?; let chunk = chunk.read(); - let sections: Vec = chunk + let sections = chunk .sections .iter() .map(|section| section.states.clone()) - .collect(); + .collect::>>(); if section_index >= sections.len() { // y position is out of bounds @@ -195,9 +200,9 @@ impl CachedWorld { fn calculate_bitsets_for_section(&self, section_pos: ChunkSectionPos) -> Option { self.with_section(section_pos, |section| { - let mut passable_bitset = FixedBitSet::<{ 4096_usize.div_ceil(8) }>::new(); - let mut solid_bitset = FixedBitSet::<{ 4096_usize.div_ceil(8) }>::new(); - let mut standable_bitset = FixedBitSet::<{ 4096_usize.div_ceil(8) }>::new(); + let mut passable_bitset = FixedBitSet::<4096>::new(); + let mut solid_bitset = FixedBitSet::<4096>::new(); + let mut standable_bitset = FixedBitSet::<4096>::new(); for i in 0..4096 { let block_state = section.get_at_index(i); if is_block_state_passable(block_state) { diff --git a/azalea/src/prelude.rs b/azalea/src/prelude.rs index 648fad6a..969b1e86 100644 --- a/azalea/src/prelude.rs +++ b/azalea/src/prelude.rs @@ -6,8 +6,10 @@ pub use azalea_core::tick::GameTick; // this is necessary to make the macros that reference bevy_ecs work pub use crate::ecs as bevy_ecs; -pub use crate::ecs::{component::Component, resource::Resource}; pub use crate::{ - ClientBuilder, bot::BotClientExt, container::ContainerClientExt, + ClientBuilder, + bot::BotClientExt, + container::ContainerClientExt, + ecs::{component::Component, resource::Resource}, pathfinder::PathfinderClientExt, }; diff --git a/azalea/src/swarm/events.rs b/azalea/src/swarm/events.rs index b8154c50..aff578a3 100644 --- a/azalea/src/swarm/events.rs +++ b/azalea/src/swarm/events.rs @@ -1,4 +1,4 @@ -use azalea_client::InstanceHolder; +use azalea_client::local_player::InstanceHolder; use azalea_world::MinecraftEntityId; use bevy_app::{App, Plugin, Update}; use bevy_ecs::prelude::*; diff --git a/azalea/src/swarm/mod.rs b/azalea/src/swarm/mod.rs index 170effc8..35007b9e 100644 --- a/azalea/src/swarm/mod.rs +++ b/azalea/src/swarm/mod.rs @@ -32,7 +32,7 @@ use bevy_app::{App, PluginGroup, PluginGroupBuilder, Plugins, SubApp}; use bevy_ecs::prelude::*; use futures::future::{BoxFuture, join_all}; use parking_lot::{Mutex, RwLock}; -use tokio::sync::mpsc; +use tokio::{sync::mpsc, time::sleep}; use tracing::{debug, error, warn}; use crate::{BoxHandleFn, DefaultBotPlugins, HandleFn, JoinOpts, NoState, StartError}; @@ -495,9 +495,7 @@ where for ((account, bot_join_opts), state) in accounts.iter().zip(states) { let mut join_opts = default_join_opts.clone(); join_opts.update(bot_join_opts); - swarm_clone - .add_and_retry_forever_with_opts(account, state, &join_opts) - .await; + let _ = swarm_clone.add_with_opts(account, state, &join_opts).await; tokio::time::sleep(join_delay).await; } } else { @@ -507,9 +505,9 @@ where |((account, bot_join_opts), state)| async { let mut join_opts = default_join_opts.clone(); join_opts.update(bot_join_opts); - swarm_borrow + let _ = swarm_borrow .clone() - .add_and_retry_forever_with_opts(account, state, &join_opts) + .add_with_opts(account, state, &join_opts) .await; }, )) @@ -689,7 +687,7 @@ impl Swarm { /// /// # Errors /// - /// Returns an `Err` if the bot could not do a handshake successfully. + /// Returns an error if the server's address could not be resolved. pub async fn add( &self, account: &Account, @@ -704,7 +702,7 @@ impl Swarm { /// /// # Errors /// - /// Returns an `Err` if the bot could not do a handshake successfully. + /// Returns an error if the server's address could not be resolved. pub async fn add_with_opts( &self, account: &Account, @@ -727,7 +725,7 @@ impl Swarm { let (tx, rx) = mpsc::unbounded_channel(); - let bot = Client::start_client(StartClientOpts { + let client = Client::start_client(StartClientOpts { ecs_lock: self.ecs_lock.clone(), account: account.clone(), connect_opts: ConnectOpts { @@ -737,14 +735,14 @@ impl Swarm { }, event_sender: Some(tx), }) - .await?; + .await; // add the state to the client { let mut ecs = self.ecs_lock.lock(); - ecs.entity_mut(bot.entity).insert(state); + ecs.entity_mut(client.entity).insert(state); } - let cloned_bot = bot.clone(); + let cloned_bot = client.clone(); let swarm_tx = self.swarm_tx.clone(); let bots_tx = self.bots_tx.clone(); @@ -753,7 +751,7 @@ impl Swarm { rx, swarm_tx, bots_tx, cloned_bot, join_opts, )); - Ok(bot) + Ok(client) } /// Copy the events from a client's receiver into bots_tx, until the bot is @@ -769,7 +767,9 @@ impl Swarm { if rx.len() > 1_000 { static WARNED_1_000: AtomicBool = AtomicBool::new(false); if !WARNED_1_000.swap(true, atomic::Ordering::Relaxed) { - warn!("the client's Event channel has more than 1000 items!") + warn!( + "the client's Event channel has more than 1,000 items! this is probably fine but if you're concerned about it, maybe consider disabling the packet-event feature in azalea to reduce the number of events?" + ) } if rx.len() > 10_000 { @@ -788,7 +788,7 @@ impl Swarm { static WARNED_1_000_000: AtomicBool = AtomicBool::new(false); if !WARNED_1_000_000.swap(true, atomic::Ordering::Relaxed) { warn!( - "the client's Event channel has more than 1,000,000 items!!!! i sincerely hope no one ever sees this warning" + "the client's Event channel has more than 1,000,000 items!!!! your code is almost certainly leaking memory" ) } } @@ -830,24 +830,29 @@ impl Swarm { /// /// This does exponential backoff (though very limited), starting at 5 /// seconds and doubling up to 15 seconds. + #[deprecated(note = "azalea has auto-reconnect functionality built-in now, use `add` instead")] pub async fn add_and_retry_forever( &self, account: &Account, state: S, ) -> Client { + #[allow(deprecated)] self.add_and_retry_forever_with_opts(account, state, &JoinOpts::default()) .await } /// Same as [`Self::add_and_retry_forever`], but allow passing custom join /// options. + #[deprecated( + note = "azalea has auto-reconnect functionality built-in now, use `add_with_opts` instead" + )] pub async fn add_and_retry_forever_with_opts( &self, account: &Account, state: S, opts: &JoinOpts, ) -> Client { - let mut disconnects = 0; + let mut disconnects: u32 = 0; loop { match self.add_with_opts(account, state.clone(), opts).await { Ok(bot) => return bot, @@ -857,20 +862,9 @@ impl Swarm { .min(Duration::from_secs(15)); let username = account.username.clone(); - match &e { - JoinError::Disconnect { reason } => { - error!( - "Error joining as {username}, server says: \"{reason}\". Waiting {delay:?} and trying again." - ); - } - _ => { - error!( - "Error joining as {username}: {e}. Waiting {delay:?} and trying again." - ); - } - } + error!("Error joining as {username}: {e}. Waiting {delay:?} and trying again."); - tokio::time::sleep(delay).await; + sleep(delay).await; } } } diff --git a/codegen/lib/code/blocks.py b/codegen/lib/code/blocks.py index 212d080c..550ccf83 100644 --- a/codegen/lib/code/blocks.py +++ b/codegen/lib/code/blocks.py @@ -1,9 +1,6 @@ from lib.utils import get_dir_location, to_camel_case -from ..mappings import Mappings -from typing import Optional -import re -BLOCKS_RS_DIR = get_dir_location('../azalea-block/src/generated.rs') +BLOCKS_RS_DIR = get_dir_location("../azalea-block/src/generated.rs") # Terminology: # - Property: A property of a block, like "direction" @@ -12,18 +9,23 @@ BLOCKS_RS_DIR = get_dir_location('../azalea-block/src/generated.rs') # - Block: Has properties and states. -def generate_blocks(blocks_report: dict, pumpkin_block_datas: dict, ordered_blocks: list[str], burger_data: dict): - with open(BLOCKS_RS_DIR, 'r') as f: +def generate_blocks( + blocks_report: dict, + pumpkin_block_datas: dict, + ordered_blocks: list[str], + burger_data: dict, +): + with open(BLOCKS_RS_DIR, "r") as f: existing_code = f.read().splitlines() new_make_block_states_macro_code = [] - new_make_block_states_macro_code.append('make_block_states! {') + new_make_block_states_macro_code.append("make_block_states! {") - burger_block_datas = burger_data[0]['blocks']['block'] + burger_block_datas = burger_data[0]["blocks"]["block"] pumpkin_block_map = {} - for block_data in pumpkin_block_datas['blocks']: - block_id = block_data['name'] + for block_data in pumpkin_block_datas["blocks"]: + block_id = block_data["name"] pumpkin_block_map[block_id] = block_data # Find properties @@ -32,20 +34,22 @@ def generate_blocks(blocks_report: dict, pumpkin_block_datas: dict, ordered_bloc # This dict looks like { 'FloweringAzaleaLeavesDistance': 'distance' } property_struct_names_to_names = {} for block_id in ordered_blocks: - block_data_report = blocks_report[f'minecraft:{block_id}'] + block_data_report = blocks_report[f"minecraft:{block_id}"] block_properties = {} - for property_id in list(block_data_report.get('properties', {}).keys()): - property_variants = block_data_report['properties'][property_id] + for property_id in list(block_data_report.get("properties", {}).keys()): + property_variants = block_data_report["properties"][property_id] - property_struct_name = get_property_struct_name(block_id, property_id, property_variants) + property_struct_name = get_property_struct_name( + block_id, property_id, property_variants + ) if property_struct_name in properties: if not properties[property_struct_name] == property_variants: raise Exception( - 'There are multiple enums with the same name! ' - f'Name: {property_struct_name}, variants: {property_variants}/{properties[property_struct_name]}. ' - 'This can be fixed by hardcoding a name in the get_property_struct_name function.' + "There are multiple enums with the same name! " + f"Name: {property_struct_name}, variants: {property_variants}/{properties[property_struct_name]}. " + "This can be fixed by hardcoding a name in the get_property_struct_name function." ) block_properties[property_struct_name] = property_variants @@ -55,7 +59,7 @@ def generate_blocks(blocks_report: dict, pumpkin_block_datas: dict, ordered_bloc properties.update(block_properties) # Property codegen - new_make_block_states_macro_code.append(' Properties => {') + new_make_block_states_macro_code.append(" Properties => {") for property_struct_name, property_variants in properties.items(): # "face" => Face { # Floor, @@ -65,99 +69,104 @@ def generate_blocks(blocks_report: dict, pumpkin_block_datas: dict, ordered_bloc property_id = property_struct_names_to_names[property_struct_name] # if the only variants are true and false, we make it unit struct with a boolean instead of an enum - if property_variants == ['true', 'false']: - property_shape_code = f'{property_struct_name}(bool)' + if property_variants == ["true", "false"]: + property_shape_code = f"{property_struct_name}(bool)" else: - property_shape_code = f'{property_struct_name} {{\n' + property_shape_code = f"{property_struct_name} {{\n" for variant in property_variants: - property_shape_code += f' {to_camel_case(variant)},\n' - property_shape_code += ' }' + property_shape_code += f" {to_camel_case(variant)},\n" + property_shape_code += " }" new_make_block_states_macro_code.append( - f' "{property_id}" => {property_shape_code},') + f' "{property_id}" => {property_shape_code},' + ) - new_make_block_states_macro_code.append(' },') + new_make_block_states_macro_code.append(" },") # Block codegen - new_make_block_states_macro_code.append(' Blocks => {') + new_make_block_states_macro_code.append(" Blocks => {") for block_id in ordered_blocks: - block_data_report = blocks_report['minecraft:' + block_id] + block_data_report = blocks_report["minecraft:" + block_id] block_data_burger = burger_block_datas.get(block_id, {}) block_data_pumpkin = pumpkin_block_map[block_id] default_property_variants: dict[str, str] = {} - for state in block_data_report['states']: - if state.get('default'): - default_property_variants = state.get('properties', {}) + for state in block_data_report["states"]: + if state.get("default"): + default_property_variants = state.get("properties", {}) - properties_code = '{' - for property_id in list(block_data_report.get('properties', {}).keys()): + properties_code = "{" + for property_id in list(block_data_report.get("properties", {}).keys()): property_default = default_property_variants.get(property_id) - property_variants = block_data_report['properties'][property_id] + property_variants = block_data_report["properties"][property_id] property_struct_name = get_property_struct_name( - block_id, property_id, property_variants) + block_id, property_id, property_variants + ) - is_boolean_property = property_variants == ['true', 'false'] + is_boolean_property = property_variants == ["true", "false"] if is_boolean_property: # if it's a boolean, keep the type lowercase # (so it's either `true` or `false`) - property_default_type = f'{property_struct_name}({property_default})' + property_default_type = f"{property_struct_name}({property_default})" else: - property_default_type = f'{property_struct_name}::{to_camel_case(property_default)}' + property_default_type = ( + f"{property_struct_name}::{to_camel_case(property_default)}" + ) assert property_default is not None this_property_code = f'"{property_id}": {property_default_type}' - properties_code += f'\n {this_property_code},' + properties_code += f"\n {this_property_code}," # if there's nothing inside the properties, keep it in one line - if properties_code == '{': - properties_code += '}' + if properties_code == "{": + properties_code += "}" else: - properties_code += '\n }' + properties_code += "\n }" # make the block behavior - behavior_constructor = 'BlockBehavior::new()' + behavior_constructor = "BlockBehavior::new()" # requires tool - if block_data_burger.get('requires_correct_tool_for_drops'): - behavior_constructor += '.requires_correct_tool_for_drops()' + if block_data_burger.get("requires_correct_tool_for_drops"): + behavior_constructor += ".requires_correct_tool_for_drops()" # strength - destroy_time = block_data_pumpkin.get('hardness') - explosion_resistance = block_data_pumpkin.get('blast_resistance') + destroy_time = block_data_pumpkin.get("hardness") + explosion_resistance = block_data_pumpkin.get("blast_resistance") if destroy_time and explosion_resistance: - behavior_constructor += f'.strength({destroy_time}, {explosion_resistance})' + behavior_constructor += f".strength({destroy_time}, {explosion_resistance})" elif destroy_time: - behavior_constructor += f'.destroy_time({destroy_time})' + behavior_constructor += f".destroy_time({destroy_time})" elif explosion_resistance: - behavior_constructor += f'.explosion_resistance({explosion_resistance})' + behavior_constructor += f".explosion_resistance({explosion_resistance})" # friction - friction = block_data_burger.get('friction') - if friction != None: - behavior_constructor += f'.friction({friction})' - + friction = block_data_burger.get("friction") + if friction is not None: + behavior_constructor += f".friction({friction})" + force_solid = None - if block_data_burger.get('force_solid_on'): - force_solid = 'true' - elif block_data_burger.get('force_solid_off'): - force_solid = 'false' - if force_solid != None: - behavior_constructor += f'.force_solid({force_solid})' + if block_data_burger.get("force_solid_on"): + force_solid = "true" + elif block_data_burger.get("force_solid_off"): + force_solid = "false" + if force_solid is not None: + behavior_constructor += f".force_solid({force_solid})" # TODO: use burger to generate the blockbehavior new_make_block_states_macro_code.append( - f' {block_id} => {behavior_constructor}, {properties_code},') + f" {block_id} => {behavior_constructor}, {properties_code}," + ) - new_make_block_states_macro_code.append(' }') - new_make_block_states_macro_code.append('}') + new_make_block_states_macro_code.append(" }") + new_make_block_states_macro_code.append("}") new_code = [] in_macro = False for line in existing_code: - if line == 'make_block_states! {': + if line == "make_block_states! {": in_macro = True - elif line == '}': + elif line == "}": if in_macro: in_macro = False new_code.extend(new_make_block_states_macro_code) @@ -166,69 +175,94 @@ def generate_blocks(blocks_report: dict, pumpkin_block_datas: dict, ordered_bloc continue new_code.append(line) # empty line at the end - new_code.append('') + new_code.append("") - with open(BLOCKS_RS_DIR, 'w') as f: - f.write('\n'.join(new_code)) + with open(BLOCKS_RS_DIR, "w") as f: + f.write("\n".join(new_code)) -def get_property_struct_name(block_id: str, property_id: str, property_variants: list[str]) -> str: + +def get_property_struct_name( + block_id: str, property_id: str, property_variants: list[str] +) -> str: # these are hardcoded because otherwise they cause conflicts # some names inspired by https://github.com/feather-rs/feather/blob/main/feather/blocks/src/generated/table.rs - if property_variants == ['north', 'east', 'south', 'west', 'up', 'down']: - return 'FacingCubic' - if property_variants == ['north', 'south', 'west', 'east']: - return 'FacingCardinal' - if property_variants == ['top', 'bottom']: - return 'TopBottom' - if property_variants == ['north_south', 'east_west', 'ascending_east', 'ascending_west', 'ascending_north', 'ascending_south']: - return 'RailShape' - if property_variants == ['straight', 'inner_left', 'inner_right', 'outer_left', 'outer_right']: - return 'StairShape' - if property_variants == ['normal', 'sticky']: - return 'PistonType' - if property_variants == ['x', 'z']: - return 'AxisXZ' - if property_variants == ['single', 'left', 'right']: - return 'ChestType' - if property_variants == ['compare', 'subtract']: - return 'ComparatorType' - if property_variants == ['inactive', 'waiting_for_players', 'active', 'waiting_for_reward_ejection', 'ejecting_reward', 'cooldown']: - return 'TrialSpawnerState' - if property_variants == ['inactive', 'active', 'unlocking', 'ejecting']: - return 'VaultState' - if property_variants == ['start', 'log', 'fail', 'accept']: - return 'TestMode' - if property_variants == ['save', 'load', 'corner', 'data']: - return 'StructureMode' - if 'harp' in property_variants and 'didgeridoo' in property_variants: - return 'Sound' + if property_variants == ["north", "east", "south", "west", "up", "down"]: + return "FacingCubic" + if property_variants == ["north", "south", "west", "east"]: + return "FacingCardinal" + if property_variants == ["top", "bottom"]: + return "TopBottom" + if property_variants == [ + "north_south", + "east_west", + "ascending_east", + "ascending_west", + "ascending_north", + "ascending_south", + ]: + return "RailShape" + if property_variants == [ + "straight", + "inner_left", + "inner_right", + "outer_left", + "outer_right", + ]: + return "StairShape" + if property_variants == ["normal", "sticky"]: + return "PistonType" + if property_variants == ["x", "z"]: + return "AxisXZ" + if property_variants == ["single", "left", "right"]: + return "ChestType" + if property_variants == ["compare", "subtract"]: + return "ComparatorType" + if property_variants == [ + "inactive", + "waiting_for_players", + "active", + "waiting_for_reward_ejection", + "ejecting_reward", + "cooldown", + ]: + return "TrialSpawnerState" + if property_variants == ["inactive", "active", "unlocking", "ejecting"]: + return "VaultState" + if property_variants == ["start", "log", "fail", "accept"]: + return "TestMode" + if property_variants == ["save", "load", "corner", "data"]: + return "StructureMode" + if "harp" in property_variants and "didgeridoo" in property_variants: + return "Sound" if is_list_of_string_integers(property_variants): # if the values are all integers, then prepend the block name return to_camel_case(block_id) + to_camel_case(property_id) - if property_variants == ['up', 'side', 'none']: - return 'Wire' + to_camel_case(property_id) - if property_variants == ['none', 'low', 'tall']: - return 'Wall' + to_camel_case(property_id) + if property_variants == ["up", "side", "none"]: + return "Wire" + to_camel_case(property_id) + if property_variants == ["none", "low", "tall"]: + return "Wall" + to_camel_case(property_id) return to_camel_case(property_id) -def is_list_of_string_integers(l: list[str]) -> bool: - return all(map(str.isdigit, l)) + +def is_list_of_string_integers(list_to_check: list[str]) -> bool: + return all(map(str.isdigit, list_to_check)) + def get_ordered_blocks(registries_report: dict[str, dict]) -> list[str]: - ''' + """ Returns a list of block ids (like ['air', 'stone', ...]) ordered by their protocol id. - ''' - blocks_registry = registries_report['minecraft:block'] + """ + blocks_registry = registries_report["minecraft:block"] - blocks_to_ids = {} - for block_id, value in blocks_registry['entries'].items(): - prefix = 'minecraft:' + blocks_to_ids = {} + for block_id, value in blocks_registry["entries"].items(): + prefix = "minecraft:" assert block_id.startswith(prefix) - block_id = block_id[len(prefix):] - protocol_id = value['protocol_id'] + block_id = block_id[len(prefix) :] + protocol_id = value["protocol_id"] blocks_to_ids[block_id] = protocol_id - + ordered_blocks = [] for block_id in sorted(blocks_to_ids, key=blocks_to_ids.get): ordered_blocks.append(block_id) diff --git a/codegen/lib/code/entity.py b/codegen/lib/code/entity.py index c27c4ce7..41a94991 100644 --- a/codegen/lib/code/entity.py +++ b/codegen/lib/code/entity.py @@ -1,18 +1,14 @@ from lib.utils import to_camel_case, to_snake_case, get_dir_location, upper_first_letter -from lib.code.packet import burger_instruction_to_code -from lib.code.utils import burger_type_to_rust_type from lib.mappings import Mappings from typing import Optional import re -METADATA_RS_DIR = get_dir_location( - '../azalea-entity/src/metadata.rs') +METADATA_RS_DIR = get_dir_location("../azalea-entity/src/metadata.rs") -DATA_RS_DIR = get_dir_location( - '../azalea-entity/src/data.rs') +DATA_RS_DIR = get_dir_location("../azalea-entity/src/data.rs") + +DIMENSIONS_RS_DIR = get_dir_location("../azalea-entity/src/dimensions.rs") -DIMENSIONS_RS_DIR = get_dir_location( - '../azalea-entity/src/dimensions.rs') def generate_metadata_names(burger_dataserializers: dict, mappings: Mappings): serializer_names: list[Optional[str]] = [None] * len(burger_dataserializers) @@ -20,56 +16,63 @@ def generate_metadata_names(burger_dataserializers: dict, mappings: Mappings): print(burger_serializer) # burger gives us the wrong class, so we do this instead - data_serializers_class = mappings.get_class_from_deobfuscated_name('net.minecraft.network.syncher.EntityDataSerializers') - mojmap_name = mappings.get_field(data_serializers_class, burger_serializer['field']).lower() + data_serializers_class = mappings.get_class_from_deobfuscated_name( + "net.minecraft.network.syncher.EntityDataSerializers" + ) + mojmap_name = mappings.get_field( + data_serializers_class, burger_serializer["field"] + ).lower() - if mojmap_name == 'component': - mojmap_name = 'formatted_text' - elif mojmap_name == 'optional_component': - mojmap_name = 'optional_formatted_text' + if mojmap_name == "component": + mojmap_name = "formatted_text" + elif mojmap_name == "optional_component": + mojmap_name = "optional_formatted_text" - serializer_names[burger_serializer['id']] = upper_first_letter(to_camel_case(mojmap_name)) + serializer_names[burger_serializer["id"]] = upper_first_letter( + to_camel_case(mojmap_name) + ) return serializer_names + def parse_metadata_types_from_code(): - with open(DATA_RS_DIR, 'r') as f: + with open(DATA_RS_DIR, "r") as f: lines = f.read().splitlines() - + data = [] in_enum = False for line in lines: - if line == 'pub enum EntityDataValue {': + if line == "pub enum EntityDataValue {": in_enum = True - elif line == '}': + elif line == "}": in_enum = False elif in_enum: line = line.strip() - if line.startswith('//'): continue - name, type = line.rstrip('),').split('(') + if line.startswith("//"): + continue + name, type = line.rstrip("),").split("(") is_var = False - if type.startswith('#[var] '): + if type.startswith("#[var] "): is_var = True - type = type[len('#[var] '):] - data.append({ - 'name': name, - 'type': type, - 'var': is_var - }) + type = type[len("#[var] ") :] + data.append({"name": name, "type": type, "var": is_var}) print(data) return data -def generate_entity_metadata(burger_entities_data: dict, mappings: Mappings): - burger_entity_metadata = burger_entities_data['entity'] - new_metadata_names = generate_metadata_names(burger_entities_data['dataserializers'], mappings) +def generate_entity_metadata(burger_entities_data: dict, mappings: Mappings): + burger_entity_metadata = burger_entities_data["entity"] + + new_metadata_names = generate_metadata_names( + burger_entities_data["dataserializers"], mappings + ) parsed_metadata_types = parse_metadata_types_from_code() parsed_metadata_names = [] for t in parsed_metadata_types: - parsed_metadata_names.append(t['name']) + parsed_metadata_names.append(t["name"]) - with open(DATA_RS_DIR, 'r') as f: + with open(DATA_RS_DIR, "r") as f: lines = f.read().splitlines() # add the metadata names that weren't there before to the end of the enum. # this technically might cause them to be in the wrong order but i decided @@ -81,26 +84,28 @@ def generate_entity_metadata(burger_entities_data: dict, mappings: Mappings): if added_metadata_names != []: in_enum = False for i, line in enumerate(list(lines)): - if line == 'pub enum EntityDataValue {': + if line == "pub enum EntityDataValue {": in_enum = True - elif in_enum and line == '}': + elif in_enum and line == "}": in_enum = False for n in added_metadata_names: - lines.insert(i, f'{n}(TODO),') + lines.insert(i, f"{n}(TODO),") i += 1 print(lines) - with open(DATA_RS_DIR, 'w') as f: - f.write('\n'.join(lines)) - print('Expected metadata types:\n' + '\n'.join(new_metadata_names)) - print('Updated metadata types in azalea-entity/src/data.rs, go make sure they\'re correct (check EntityDataSerializers.java) and then press enter') + with open(DATA_RS_DIR, "w") as f: + f.write("\n".join(lines)) + print("Expected metadata types:\n" + "\n".join(new_metadata_names)) + print( + "Updated metadata types in azalea-entity/src/data.rs, go make sure they're correct (check EntityDataSerializers.java) and then press enter" + ) input() - + metadata_types = parse_metadata_types_from_code() code = [] - code.append('''#![allow(clippy::single_match)] + code.append("""#![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; @@ -131,10 +136,10 @@ impl From for UpdateMetadataError { Self::WrongType(value) } } -''') +""") # types that are only ever used in one entity - single_use_imported_types = {'particle', 'pose'} + single_use_imported_types = {"particle", "pose"} added_metadata_fields = set() @@ -146,11 +151,13 @@ impl From for UpdateMetadataError { duplicate_field_names = set() # some generic names... we don't like these - duplicate_field_names.add('state') # SnifferState instead of State + duplicate_field_names.add("state") # SnifferState instead of State for entity_id in burger_entity_metadata.keys(): field_name_map[entity_id] = {} - for field_name_or_bitfield in get_entity_metadata_names(entity_id, burger_entity_metadata, mappings).values(): + for field_name_or_bitfield in get_entity_metadata_names( + entity_id, burger_entity_metadata, mappings + ).values(): if isinstance(field_name_or_bitfield, str): if field_name_or_bitfield in previous_field_names: duplicate_field_names.add(field_name_or_bitfield) @@ -170,25 +177,30 @@ impl From for UpdateMetadataError { # make sure these types are only ever made once for name in single_use_imported_types: if name in duplicate_field_names: - raise Exception(f'{name} should only exist once') + raise Exception(f"{name} should only exist once") # and now figure out what to rename them to for entity_id in burger_entity_metadata.keys(): - for index, field_name_or_bitfield in get_entity_metadata_names(entity_id, burger_entity_metadata, mappings).items(): + for index, field_name_or_bitfield in get_entity_metadata_names( + entity_id, burger_entity_metadata, mappings + ).items(): if isinstance(field_name_or_bitfield, str): new_field_name = field_name_or_bitfield - if new_field_name == 'type': - new_field_name = 'kind' + if new_field_name == "type": + new_field_name = "kind" if field_name_or_bitfield in duplicate_field_names: - field_name_map[entity_id][ - field_name_or_bitfield] = f'{entity_id.strip("~")}_{new_field_name}' + field_name_map[entity_id][field_name_or_bitfield] = ( + f"{entity_id.strip('~')}_{new_field_name}" + ) else: for mask, name in field_name_or_bitfield.items(): new_field_name = name - if new_field_name == 'type': - new_field_name = 'kind' + if new_field_name == "type": + new_field_name = "kind" if name in duplicate_field_names: - field_name_map[entity_id][name] = f'{entity_id.strip("~")}_{new_field_name}' + field_name_map[entity_id][name] = ( + f"{entity_id.strip('~')}_{new_field_name}" + ) def new_entity(entity_id: str): # note: fields are components @@ -199,25 +211,35 @@ impl From for UpdateMetadataError { entity_metadatas = [] def maybe_rename_field(name: str, index: int) -> str: - if name in field_name_map[entity_ids_for_all_field_names_or_bitfields[index]]: - return field_name_map[entity_ids_for_all_field_names_or_bitfields[index]][name] + if ( + name + in field_name_map[entity_ids_for_all_field_names_or_bitfields[index]] + ): + return field_name_map[ + entity_ids_for_all_field_names_or_bitfields[index] + ][name] return name parents = get_entity_parents(entity_id, burger_entity_metadata) for parent_id in list(reversed(parents)): - for index, name_or_bitfield in get_entity_metadata_names(parent_id, burger_entity_metadata, mappings).items(): + for index, name_or_bitfield in get_entity_metadata_names( + parent_id, burger_entity_metadata, mappings + ).items(): assert index == len(all_field_names_or_bitfields) all_field_names_or_bitfields.append(name_or_bitfield) entity_ids_for_all_field_names_or_bitfields.append(parent_id) - entity_metadatas.extend(get_entity_metadata( - parent_id, burger_entity_metadata)) + entity_metadatas.extend( + get_entity_metadata(parent_id, burger_entity_metadata) + ) parent_id = parents[1] if len(parents) > 1 else None # now add all the fields/component structs for index, name_or_bitfield in enumerate(all_field_names_or_bitfields): # make sure we only ever make these structs once - hashable_name_or_bitfield = str( - name_or_bitfield) + entity_ids_for_all_field_names_or_bitfields[index] + hashable_name_or_bitfield = ( + str(name_or_bitfield) + + entity_ids_for_all_field_names_or_bitfields[index] + ) if hashable_name_or_bitfield in added_metadata_fields: continue added_metadata_fields.add(hashable_name_or_bitfield) @@ -229,30 +251,33 @@ impl From for UpdateMetadataError { name_or_bitfield = maybe_rename_field(name_or_bitfield, index) - struct_name = upper_first_letter( - to_camel_case(name_or_bitfield)) - type_id = next(filter(lambda i: i['index'] == index, entity_metadatas))['type_id'] + struct_name = upper_first_letter(to_camel_case(name_or_bitfield)) + type_id = next(filter(lambda i: i["index"] == index, entity_metadatas))[ + "type_id" + ] metadata_type_data = metadata_types[type_id] - rust_type = metadata_type_data['type'] + rust_type = metadata_type_data["type"] - code.append(f'#[derive(Component, Deref, DerefMut, Clone)]') - code.append(f'pub struct {struct_name}(pub {rust_type});') + code.append("#[derive(Component, Deref, DerefMut, Clone)]") + code.append(f"pub struct {struct_name}(pub {rust_type});") else: # if it's a bitfield just make a struct for each bit for mask, name in name_or_bitfield.items(): name = maybe_rename_field(name, index) struct_name = upper_first_letter(to_camel_case(name)) - code.append(f'#[derive(Component, Deref, DerefMut, Clone, Copy)]') - code.append(f'pub struct {struct_name}(pub bool);') + code.append("#[derive(Component, Deref, DerefMut, Clone, Copy)]") + code.append(f"pub struct {struct_name}(pub bool);") # add the entity struct and Bundle struct - struct_name: str = upper_first_letter( - to_camel_case(entity_id.lstrip('~'))) - code.append(f'#[derive(Component)]') - code.append(f'pub struct {struct_name};') + struct_name: str = upper_first_letter(to_camel_case(entity_id.lstrip("~"))) + code.append("#[derive(Component)]") + code.append(f"pub struct {struct_name};") - parent_struct_name = upper_first_letter( - to_camel_case(parent_id.lstrip("~"))) if parent_id else None + parent_struct_name = ( + upper_first_letter(to_camel_case(parent_id.lstrip("~"))) + if parent_id + else None + ) # impl Allay { # pub fn apply_metadata( @@ -267,58 +292,67 @@ impl From for UpdateMetadataError { # Ok(()) # } # } - code.append(f'impl {struct_name} {{') + code.append(f"impl {struct_name} {{") code.append( - f' pub fn apply_metadata(entity: &mut bevy_ecs::system::EntityCommands, d: EntityDataItem) -> Result<(), UpdateMetadataError> {{') - code.append(f' match d.index {{') + " pub fn apply_metadata(entity: &mut bevy_ecs::system::EntityCommands, d: EntityDataItem) -> Result<(), UpdateMetadataError> {" + ) + code.append(" match d.index {") parent_last_index = -1 for index, name_or_bitfield in enumerate(all_field_names_or_bitfields): - is_from_parent = entity_ids_for_all_field_names_or_bitfields[index] != entity_id + is_from_parent = ( + entity_ids_for_all_field_names_or_bitfields[index] != entity_id + ) if is_from_parent: parent_last_index = index if parent_last_index != -1: code.append( - f' 0..={parent_last_index} => {parent_struct_name}::apply_metadata(entity, d)?,') + f" 0..={parent_last_index} => {parent_struct_name}::apply_metadata(entity, d)?," + ) for index, name_or_bitfield in enumerate(all_field_names_or_bitfields): if index <= parent_last_index: continue if isinstance(name_or_bitfield, str): - name_or_bitfield = maybe_rename_field( - name_or_bitfield, index) + name_or_bitfield = maybe_rename_field(name_or_bitfield, index) - field_struct_name = upper_first_letter( - to_camel_case(name_or_bitfield)) + field_struct_name = upper_first_letter(to_camel_case(name_or_bitfield)) if name_or_bitfield in single_use_imported_types: - field_struct_name = '' + field_struct_name = "" - type_id = next(filter(lambda i: i['index'] == index, entity_metadatas))['type_id'] + type_id = next(filter(lambda i: i["index"] == index, entity_metadatas))[ + "type_id" + ] metadata_type_data = metadata_types[type_id] - rust_type = metadata_type_data['type'] - type_name = metadata_type_data['name'] + rust_type = metadata_type_data["type"] + type_name = metadata_type_data["name"] type_name_field = to_snake_case(type_name) - read_field_code = f'{field_struct_name}(d.value.into_{type_name_field}()?)' if field_struct_name else f'd.value.into_{type_name_field}()?' + read_field_code = ( + f"{field_struct_name}(d.value.into_{type_name_field}()?)" + if field_struct_name + else f"d.value.into_{type_name_field}()?" + ) code.append( - f' {index} => {{ entity.insert({read_field_code}); }},') + f" {index} => {{ entity.insert({read_field_code}); }}," + ) else: - code.append(f' {index} => {{') - code.append( - f'let bitfield = d.value.into_byte()?;') + code.append(f" {index} => {{") + code.append("let bitfield = d.value.into_byte()?;") for mask, name in name_or_bitfield.items(): name = maybe_rename_field(name, index) field_struct_name = upper_first_letter(to_camel_case(name)) code.append( - f'entity.insert({field_struct_name}(bitfield & {mask} != 0));') - code.append(' },') - code.append(' _ => {}') - code.append(' }') - code.append(' Ok(())') - code.append(' }') - code.append('}') - code.append('') + f"entity.insert({field_struct_name}(bitfield & {mask} != 0));" + ) + code.append(" },") + code.append(" _ => {}") + code.append(" }") + code.append(" Ok(())") + code.append(" }") + code.append("}") + code.append("") # #[derive(Bundle)] # struct AllayBundle { @@ -327,30 +361,27 @@ impl From for UpdateMetadataError { # dancing: Dancing, # can_duplicate: CanDuplicate, # } - bundle_struct_name = f'{struct_name}MetadataBundle' - code.append(f'') - code.append(f'#[derive(Bundle)]') - code.append(f'pub struct {bundle_struct_name} {{') - code.append( - f' _marker: {struct_name},') + bundle_struct_name = f"{struct_name}MetadataBundle" + code.append("") + code.append("#[derive(Bundle)]") + code.append(f"pub struct {bundle_struct_name} {{") + code.append(f" _marker: {struct_name},") if parent_struct_name: - code.append( - f' parent: {parent_struct_name}MetadataBundle,') - for index, name_or_bitfield in get_entity_metadata_names(entity_id, burger_entity_metadata, mappings).items(): + code.append(f" parent: {parent_struct_name}MetadataBundle,") + for index, name_or_bitfield in get_entity_metadata_names( + entity_id, burger_entity_metadata, mappings + ).items(): if isinstance(name_or_bitfield, str): - name_or_bitfield = maybe_rename_field( - name_or_bitfield, index) - struct_name = upper_first_letter( - to_camel_case(name_or_bitfield)) - code.append( - f' {name_or_bitfield}: {struct_name},') + name_or_bitfield = maybe_rename_field(name_or_bitfield, index) + struct_name = upper_first_letter(to_camel_case(name_or_bitfield)) + code.append(f" {name_or_bitfield}: {struct_name},") else: for mask, name in name_or_bitfield.items(): name = maybe_rename_field(name, index) struct_name = upper_first_letter(to_camel_case(name)) - code.append(f' {name}: {struct_name},') - code.append('}') + code.append(f" {name}: {struct_name},") + code.append("}") # impl Default for AllayBundle { # fn default() -> Self { @@ -365,9 +396,8 @@ impl From for UpdateMetadataError { # } # } # } - code.append(f'impl Default for {bundle_struct_name} {{') - code.append( - ' fn default() -> Self {') + code.append(f"impl Default for {bundle_struct_name} {{") + code.append(" fn default() -> Self {") def generate_fields(this_entity_id: str): # on_fire: OnFire(false), @@ -375,32 +405,39 @@ impl From for UpdateMetadataError { # _marker this_entity_struct_name = upper_first_letter( - to_camel_case(this_entity_id.lstrip('~'))) - code.append( - f' _marker: {this_entity_struct_name},') + to_camel_case(this_entity_id.lstrip("~")) + ) + code.append(f" _marker: {this_entity_struct_name},") # if it has a parent, put it (do recursion) # parent: AbstractCreatureBundle { ... }, this_entity_parent_ids = get_entity_parents( - this_entity_id, burger_entity_metadata) - this_entity_parent_id = this_entity_parent_ids[1] if len( - this_entity_parent_ids) > 1 else None + this_entity_id, burger_entity_metadata + ) + this_entity_parent_id = ( + this_entity_parent_ids[1] if len(this_entity_parent_ids) > 1 else None + ) if this_entity_parent_id: - bundle_struct_name = upper_first_letter( - to_camel_case(this_entity_parent_id.lstrip('~'))) + 'MetadataBundle' - code.append( - f' parent: {bundle_struct_name} {{') + bundle_struct_name = ( + upper_first_letter(to_camel_case(this_entity_parent_id.lstrip("~"))) + + "MetadataBundle" + ) + code.append(f" parent: {bundle_struct_name} {{") generate_fields(this_entity_parent_id) - code.append( - ' },') + code.append(" },") - for index, name_or_bitfield in get_entity_metadata_names(this_entity_id, burger_entity_metadata, mappings).items(): - default = next(filter(lambda i: i['index'] == index, entity_metadatas)).get('default', 'Default::default()') + for index, name_or_bitfield in get_entity_metadata_names( + this_entity_id, burger_entity_metadata, mappings + ).items(): + default = next( + filter(lambda i: i["index"] == index, entity_metadatas) + ).get("default", "Default::default()") if isinstance(name_or_bitfield, str): - type_id = next(filter(lambda i: i['index'] == index, entity_metadatas))[ - 'type_id'] + type_id = next( + filter(lambda i: i["index"] == index, entity_metadatas) + )["type_id"] metadata_type_data = metadata_types[type_id] - type_name = metadata_type_data['name'] + type_name = metadata_type_data["name"] name = maybe_rename_field(name_or_bitfield, index) @@ -409,8 +446,8 @@ impl From for UpdateMetadataError { # wrong default metadatas. This should be added to Burger. if default is None: # some types don't have Default implemented - if type_name == 'CompoundTag': - default = 'simdnbt::owned::NbtCompound::default()' + if type_name == "CompoundTag": + default = "simdnbt::owned::NbtCompound::default()" # elif type_name == 'CatVariant': # # TODO: the default should be Tabby but we don't have a way to get that from here # default = 'azalea_registry::CatVariant::new_raw(0)' @@ -418,49 +455,84 @@ impl From for UpdateMetadataError { # default = 'azalea_registry::PaintingVariant::Kebab' # elif type_name == 'FrogVariant': # default = 'azalea_registry::FrogVariant::Temperate' - elif type_name.endswith('Variant'): - default = f'azalea_registry::{type_name}::new_raw(0)' - elif type_name == 'VillagerData': - default = 'VillagerData { kind: azalea_registry::VillagerKind::Plains, profession: azalea_registry::VillagerProfession::None, level: 0 }' + elif type_name.endswith("Variant"): + default = f"azalea_registry::{type_name}::new_raw(0)" + elif type_name == "VillagerData": + default = "VillagerData { kind: azalea_registry::VillagerKind::Plains, profession: azalea_registry::VillagerProfession::None, level: 0 }" else: - default = f'{type_name}::default()' if name in single_use_imported_types else 'Default::default()' + default = ( + f"{type_name}::default()" + if name in single_use_imported_types + else "Default::default()" + ) else: - if type_name == 'Boolean': - default = 'true' if default else 'false' - elif type_name == 'String': + if type_name == "Boolean": + default = "true" if default else "false" + elif type_name == "String": string_escaped = default.replace('"', '\\"') default = f'"{string_escaped}".to_string()' - elif type_name == 'BlockPos': - default = f'BlockPos::new{default}' - elif type_name == 'OptionalBlockPos': # Option - default = f'Some(BlockPos::new{default})' if default != 'Empty' else 'None' - elif type_name == 'OptionalLivingEntityReference': - default = f'Some(uuid::uuid!({default}))' if default != 'Empty' else 'None' - elif type_name == 'OptionalUnsignedInt': - default = f'OptionalUnsignedInt(Some({default}))' if default != 'Empty' else 'OptionalUnsignedInt(None)' - elif type_name == 'ItemStack': - default = f'ItemStack::Present({default})' if default != 'Empty' else 'ItemStack::Empty' - elif type_name == 'BlockState': - default = f'{default}' if default != 'Empty' else 'azalea_block::BlockState::AIR' - elif type_name == 'OptionalBlockState': - default = f'{default}' if default != 'Empty' else 'azalea_block::BlockState::AIR' - elif type_name == 'OptionalFormattedText': - default = f'Some({default})' if default != 'Empty' else 'None' - elif type_name == 'CompoundTag': - default = f'simdnbt::owned::NbtCompound({default})' if default != 'Empty' else 'simdnbt::owned::NbtCompound::default()' - elif type_name == 'Quaternion': - default = f'Quaternion {{ x: {float(default["x"])}, y: {float(default["y"])}, z: {float(default["z"])}, w: {float(default["w"])} }}' - elif type_name == 'Vector3': - default = f'Vec3 {{ x: {float(default["x"])}, y: {float(default["y"])}, z: {float(default["z"])} }}' - elif type_name == 'Byte': + elif type_name == "BlockPos": + default = f"BlockPos::new{default}" + elif type_name == "OptionalBlockPos": # Option + default = ( + f"Some(BlockPos::new{default})" + if default != "Empty" + else "None" + ) + elif type_name == "OptionalLivingEntityReference": + default = ( + f"Some(uuid::uuid!({default}))" + if default != "Empty" + else "None" + ) + elif type_name == "OptionalUnsignedInt": + default = ( + f"OptionalUnsignedInt(Some({default}))" + if default != "Empty" + else "OptionalUnsignedInt(None)" + ) + elif type_name == "ItemStack": + default = ( + f"ItemStack::Present({default})" + if default != "Empty" + else "ItemStack::Empty" + ) + elif type_name == "BlockState": + default = ( + f"{default}" + if default != "Empty" + else "azalea_block::BlockState::AIR" + ) + elif type_name == "OptionalBlockState": + default = ( + f"{default}" + if default != "Empty" + else "azalea_block::BlockState::AIR" + ) + elif type_name == "OptionalFormattedText": + default = ( + f"Some({default})" if default != "Empty" else "None" + ) + elif type_name == "CompoundTag": + default = ( + f"simdnbt::owned::NbtCompound({default})" + if default != "Empty" + else "simdnbt::owned::NbtCompound::default()" + ) + elif type_name == "Quaternion": + default = f"Quaternion {{ x: {float(default['x'])}, y: {float(default['y'])}, z: {float(default['z'])}, w: {float(default['w'])} }}" + elif type_name == "Vector3": + default = f"Vec3 {{ x: {float(default['x'])}, y: {float(default['y'])}, z: {float(default['z'])} }}" + elif type_name == "Byte": # in 1.19.4 TextOpacity is a -1 by default if default < 0: default += 128 if name in single_use_imported_types: - code.append(f' {name}: {default},') + code.append(f" {name}: {default},") else: code.append( - f' {name}: {upper_first_letter(to_camel_case(name))}({default}),') + f" {name}: {upper_first_letter(to_camel_case(name))}({default})," + ) else: # if it's a bitfield, we'll have to extract the default for # each bool from each bit in the default @@ -468,17 +540,19 @@ impl From for UpdateMetadataError { name = maybe_rename_field(name, index) mask = int(mask, 0) if default is None: - bit_default = 'false' + bit_default = "false" else: - bit_default = 'true' if (default & mask != 0) else 'false' + bit_default = "true" if (default & mask != 0) else "false" code.append( - f' {name}: {upper_first_letter(to_camel_case(name))}({bit_default}),') - code.append(' Self {') + f" {name}: {upper_first_letter(to_camel_case(name))}({bit_default})," + ) + + code.append(" Self {") generate_fields(entity_id) - code.append(' }') - code.append(' }') - code.append('}') - code.append('') + code.append(" }") + code.append(" }") + code.append("}") + code.append("") # parent_field_name = None for entity_id in burger_entity_metadata: @@ -499,28 +573,27 @@ impl From for UpdateMetadataError { # Ok(()) # } code.append( - f'''pub fn apply_metadata( + """pub fn apply_metadata( entity: &mut bevy_ecs::system::EntityCommands, entity_kind: azalea_registry::EntityKind, items: Vec, -) -> Result<(), UpdateMetadataError> {{ - match entity_kind {{''') +) -> Result<(), UpdateMetadataError> { + match entity_kind {""" + ) for entity_id in burger_entity_metadata: - if entity_id.startswith('~'): + if entity_id.startswith("~"): # not actually an entity continue struct_name: str = upper_first_letter(to_camel_case(entity_id)) - code.append( - f' azalea_registry::EntityKind::{struct_name} => {{') - code.append(' for d in items {') - code.append( - f' {struct_name}::apply_metadata(entity, d)?;') - code.append(' }') - code.append(' },') - code.append(' }') - code.append(' Ok(())') - code.append('}') - code.append('') + code.append(f" azalea_registry::EntityKind::{struct_name} => {{") + code.append(" for d in items {") + code.append(f" {struct_name}::apply_metadata(entity, d)?;") + code.append(" }") + code.append(" },") + code.append(" }") + code.append(" Ok(())") + code.append("}") + code.append("") # pub fn apply_default_metadata(entity: &mut bevy_ecs::system::EntityCommands, kind: azalea_registry::EntityKind) { # match kind { @@ -530,57 +603,61 @@ impl From for UpdateMetadataError { # } # } code.append( - 'pub fn apply_default_metadata(entity: &mut bevy_ecs::system::EntityCommands, kind: azalea_registry::EntityKind) {') - code.append(' match kind {') + "pub fn apply_default_metadata(entity: &mut bevy_ecs::system::EntityCommands, kind: azalea_registry::EntityKind) {" + ) + code.append(" match kind {") for entity_id in burger_entity_metadata: - if entity_id.startswith('~'): + if entity_id.startswith("~"): # not actually an entity continue struct_name: str = upper_first_letter(to_camel_case(entity_id)) + code.append(f" azalea_registry::EntityKind::{struct_name} => {{") code.append( - f' azalea_registry::EntityKind::{struct_name} => {{') - code.append( - f' entity.insert({struct_name}MetadataBundle::default());') - code.append(' },') - code.append(' }') - code.append('}') - code.append('') + f" entity.insert({struct_name}MetadataBundle::default());" + ) + code.append(" },") + code.append(" }") + code.append("}") + code.append("") + + with open(METADATA_RS_DIR, "w") as f: + f.write("\n".join(code)) - with open(METADATA_RS_DIR, 'w') as f: - f.write('\n'.join(code)) def generate_entity_dimensions(burger_entities_data: dict): # lines look like # EntityKind::Player => EntityDimensions::new(0.6, 1.8), new_match_lines = [] - for entity_id, entity_data in burger_entities_data['entity'].items(): - if entity_id.startswith('~'): + for entity_id, entity_data in burger_entities_data["entity"].items(): + if entity_id.startswith("~"): # not actually an entity continue variant_name: str = upper_first_letter(to_camel_case(entity_id)) - width = entity_data['width'] - height = entity_data['height'] + width = entity_data["width"] + height = entity_data["height"] new_match_lines.append( - f' EntityKind::{variant_name} => EntityDimensions::new({width}, {height}),') + f" EntityKind::{variant_name} => EntityDimensions::new({width}, {height})," + ) - with open(DIMENSIONS_RS_DIR, 'r') as f: - lines = f.read().split('\n') + with open(DIMENSIONS_RS_DIR, "r") as f: + lines = f.read().split("\n") new_lines = [] in_match = False for i, line in enumerate(lines): if not in_match: new_lines.append(line) - if line.strip() == 'match entity {': + if line.strip() == "match entity {": in_match = True else: - if line.strip() == '}': + if line.strip() == "}": new_lines.extend(new_match_lines) new_lines.extend(lines[i:]) break - with open(DIMENSIONS_RS_DIR, 'w') as f: - f.write('\n'.join(new_lines)) + with open(DIMENSIONS_RS_DIR, "w") as f: + f.write("\n".join(new_lines)) + def get_entity_parents(entity_id: str, burger_entity_metadata: dict): parents = [] @@ -591,59 +668,68 @@ def get_entity_parents(entity_id: str, burger_entity_metadata: dict): def get_entity_parent(entity_id: str, burger_entity_metadata: dict): - entity_metadata = burger_entity_metadata[entity_id]['metadata'] + entity_metadata = burger_entity_metadata[entity_id]["metadata"] first_metadata = entity_metadata[0] - return first_metadata.get('entity') + return first_metadata.get("entity") def get_entity_metadata(entity_id: str, burger_entity_metadata: dict): - entity_metadata = burger_entity_metadata[entity_id]['metadata'] + entity_metadata = burger_entity_metadata[entity_id]["metadata"] entity_useful_metadata = [] for metadata_item in entity_metadata: - if 'data' in metadata_item: - for metadata_attribute in metadata_item['data']: - entity_useful_metadata.append({ - 'index': metadata_attribute['index'], - 'type_id': metadata_attribute['serializer_id'], - 'default': metadata_attribute.get('default') - }) + if "data" in metadata_item: + for metadata_attribute in metadata_item["data"]: + entity_useful_metadata.append( + { + "index": metadata_attribute["index"], + "type_id": metadata_attribute["serializer_id"], + "default": metadata_attribute.get("default"), + } + ) return entity_useful_metadata + # returns a dict of {index: (name or bitfield)} -def get_entity_metadata_names(entity_id: str, burger_entity_metadata: dict, mappings: Mappings): - entity_metadata = burger_entity_metadata[entity_id]['metadata'] +def get_entity_metadata_names( + entity_id: str, burger_entity_metadata: dict, mappings: Mappings +): + entity_metadata = burger_entity_metadata[entity_id]["metadata"] mapped_metadata_names = {} for metadata_item in entity_metadata: - if 'data' in metadata_item: - obfuscated_class = metadata_item['class'] - mojang_class = mappings.get_class(obfuscated_class) + if "data" in metadata_item: + obfuscated_class = metadata_item["class"] + # mojang_class = mappings.get_class(obfuscated_class) first_byte_index = None - for metadata_attribute in metadata_item['data']: - obfuscated_field = metadata_attribute['field'] - mojang_field = mappings.get_field( - obfuscated_class, obfuscated_field) + for metadata_attribute in metadata_item["data"]: + obfuscated_field = metadata_attribute["field"] + mojang_field = mappings.get_field(obfuscated_class, obfuscated_field) pretty_mojang_name = prettify_mojang_field(mojang_field) - mapped_metadata_names[metadata_attribute['index'] - ] = pretty_mojang_name + mapped_metadata_names[metadata_attribute["index"]] = pretty_mojang_name - if metadata_attribute['serializer'] == 'Byte' and first_byte_index is None: - first_byte_index = metadata_attribute['index'] + if ( + metadata_attribute["serializer"] == "Byte" + and first_byte_index is None + ): + first_byte_index = metadata_attribute["index"] - if metadata_item['bitfields'] and first_byte_index is not None: + if metadata_item["bitfields"] and first_byte_index is not None: clean_bitfield = {} - for bitfield_item in metadata_item['bitfields']: + for bitfield_item in metadata_item["bitfields"]: bitfield_item_obfuscated_class = bitfield_item.get( - 'class', obfuscated_class) + "class", obfuscated_class + ) mojang_bitfield_item_name = mappings.get_method( - bitfield_item_obfuscated_class, bitfield_item['method'], '') + bitfield_item_obfuscated_class, bitfield_item["method"], "" + ) bitfield_item_name = prettify_mojang_method( - mojang_bitfield_item_name) - bitfield_hex_mask = hex(bitfield_item['mask']) + mojang_bitfield_item_name + ) + bitfield_hex_mask = hex(bitfield_item["mask"]) clean_bitfield[bitfield_hex_mask] = bitfield_item_name mapped_metadata_names[first_byte_index] = clean_bitfield return mapped_metadata_names @@ -652,14 +738,14 @@ def get_entity_metadata_names(entity_id: str, burger_entity_metadata: dict, mapp def prettify_mojang_field(mojang_field: str): # mojang names are like "DATA_AIR_SUPPLY" and that's ugly better_name = mojang_field - if better_name.startswith('DATA_'): + if better_name.startswith("DATA_"): better_name = better_name[5:] # remove the weird "Id" from the end of names - if better_name.endswith('_ID'): + if better_name.endswith("_ID"): better_name = better_name[:-3] # remove the weird "id" from the front of names - if better_name.startswith('ID_'): + if better_name.startswith("ID_"): better_name = better_name[3:] return better_name.lower() @@ -667,9 +753,8 @@ def prettify_mojang_field(mojang_field: str): def prettify_mojang_method(mojang_method: str): better_name = mojang_method - if better_name.endswith('()'): + if better_name.endswith("()"): better_name = better_name[:-2] - if re.match(r'is[A-Z]', better_name): + if re.match(r"is[A-Z]", better_name): better_name = better_name[2:] return to_snake_case(better_name) - diff --git a/codegen/lib/code/inventory.py b/codegen/lib/code/inventory.py index caab57f2..066319e0 100644 --- a/codegen/lib/code/inventory.py +++ b/codegen/lib/code/inventory.py @@ -1,25 +1,21 @@ -from lib.utils import padded_hex, to_snake_case, to_camel_case, get_dir_location -from lib.code.utils import burger_type_to_rust_type, write_packet_file -from lib.mappings import Mappings -from typing import Any, Optional -import os -import re +from typing import Any +from lib.utils import to_camel_case, get_dir_location # The directory where declare_menus! {} is done -inventory_menus_dir = get_dir_location(f'../azalea-inventory/src/lib.rs') +inventory_menus_dir = get_dir_location("../azalea-inventory/src/lib.rs") def update_menus(initial_menu_entries: dict[str, Any]): # new_menus is a dict of { menu_id: { "protocol_id": protocol_id } } # so convert that into an array where the protocol id is the index and the # values are enum variant names - new_menus: list[str] = [''] * len(initial_menu_entries) + new_menus: list[str] = [""] * len(initial_menu_entries) for menu_id, menu in initial_menu_entries.items(): - new_menus[menu['protocol_id']] = menu_name_to_enum_name(menu_id) + new_menus[menu["protocol_id"]] = menu_name_to_enum_name(menu_id) - new_menus.insert(0, 'Player') + new_menus.insert(0, "Player") - with open(inventory_menus_dir, 'r') as f: + with open(inventory_menus_dir, "r") as f: menus_rs = f.read().splitlines() start_line_index = 0 @@ -27,17 +23,17 @@ def update_menus(initial_menu_entries: dict[str, Any]): current_menus = [] in_the_macro = False for i, line in enumerate(menus_rs): - if line.startswith('declare_menus!'): + if line.startswith("declare_menus!"): in_the_macro = True start_line_index = i if in_the_macro: - if line.startswith(' ') and line.endswith('{'): + if line.startswith(" ") and line.endswith("{"): # get the variant name for this menu current_menu = line[:-1].strip() current_menus.append(current_menu) - print('current_menus', current_menus) - print('new_menus', new_menus) + print("current_menus", current_menus) + print("new_menus", new_menus) # now we have the current menus, so compare that with the expected # menus and update the file if needed @@ -52,57 +48,84 @@ def update_menus(initial_menu_entries: dict[str, Any]): if ( current_menus_list_index < len(current_menus) and new_menus_list_index < len(new_menus) - and current_menus[current_menus_list_index] == new_menus[new_menus_list_index] + and current_menus[current_menus_list_index] + == new_menus[new_menus_list_index] ): current_menus_list_index += 1 new_menus_list_index += 1 # increase insert_line_index until we get a line that starts with } - while not menus_rs[insert_line_index].strip().startswith('}'): + while not menus_rs[insert_line_index].strip().startswith("}"): insert_line_index += 1 insert_line_index += 1 # print('same', current_menus_list_index, # new_menus_list_index, insert_line_index) # something was added to new_menus but not current_menus - elif new_menus_list_index < len(new_menus) and new_menus[new_menus_list_index] not in current_menus: + elif ( + new_menus_list_index < len(new_menus) + and new_menus[new_menus_list_index] not in current_menus + ): # insert the new menu menus_rs.insert( - insert_line_index, f' {new_menus[new_menus_list_index]} {{\n todo!()\n }},') + insert_line_index, + f" {new_menus[new_menus_list_index]} {{\n todo!()\n }},", + ) insert_line_index += 1 new_menus_list_index += 1 - print('added', current_menus_list_index, - new_menus_list_index, insert_line_index) + print( + "added", + current_menus_list_index, + new_menus_list_index, + insert_line_index, + ) # something was removed from new_menus but is still in current_menus - elif current_menus_list_index < len(current_menus) and current_menus[current_menus_list_index] not in new_menus: + elif ( + current_menus_list_index < len(current_menus) + and current_menus[current_menus_list_index] not in new_menus + ): # remove the current menu - while not menus_rs[insert_line_index].strip().startswith('}'): + while not menus_rs[insert_line_index].strip().startswith("}"): menus_rs.pop(insert_line_index) menus_rs.pop(insert_line_index) current_menus_list_index += 1 - print('removed', current_menus_list_index, - new_menus_list_index, insert_line_index) + print( + "removed", + current_menus_list_index, + new_menus_list_index, + insert_line_index, + ) # if current_menus_list_index overflowed, then add the rest of the new menus elif current_menus_list_index >= len(current_menus): for i in range(new_menus_list_index, len(new_menus)): menus_rs.insert( - insert_line_index, f' {new_menus[i]} {{\n todo!()\n }},') + insert_line_index, + f" {new_menus[i]} {{\n todo!()\n }},", + ) insert_line_index += 1 - print('current_menus_list_index overflowed', current_menus_list_index, - new_menus_list_index, insert_line_index) + print( + "current_menus_list_index overflowed", + current_menus_list_index, + new_menus_list_index, + insert_line_index, + ) break # if new_menus_list_index overflowed, then remove the rest of the current menus elif new_menus_list_index >= len(new_menus): for _ in range(current_menus_list_index, len(current_menus)): - while not menus_rs[insert_line_index].strip().startswith('}'): + while not menus_rs[insert_line_index].strip().startswith("}"): menus_rs.pop(insert_line_index) menus_rs.pop(insert_line_index) # current_menus_list_index += 1 - print('new_menus_list_index overflowed', current_menus_list_index, - new_menus_list_index, insert_line_index) + print( + "new_menus_list_index overflowed", + current_menus_list_index, + new_menus_list_index, + insert_line_index, + ) break - with open(inventory_menus_dir, 'w') as f: - f.write('\n'.join(menus_rs)) + with open(inventory_menus_dir, "w") as f: + f.write("\n".join(menus_rs)) def menu_name_to_enum_name(menu_name: str) -> str: - return to_camel_case(menu_name.split(':')[-1]) + return to_camel_case(menu_name.split(":")[-1]) diff --git a/codegen/lib/code/item_components.py b/codegen/lib/code/item_components.py index 3d4bf6a6..ce5d6d82 100644 --- a/codegen/lib/code/item_components.py +++ b/codegen/lib/code/item_components.py @@ -1,15 +1,10 @@ -import lib.code.inventory -import lib.code.registry -import lib.code.version -import lib.code.packet import lib.code.utils -import lib.code.tags -import lib.download import lib.extract import lib.utils -ITEM_COMPONENTS_DIR = 'azalea-inventory/src/components.rs' +ITEM_COMPONENTS_DIR = "azalea-inventory/src/components.rs" + def generate(version_id: str): expected_variants = get_expected_variants(version_id) @@ -25,61 +20,65 @@ def generate(version_id: str): if variant not in expected_variants: removed_variants.append(variant) - print('New variants:') + print("New variants:") for variant in new_variants: - print('-', variant) + print("-", variant) print() - print('Removed variants:') + print("Removed variants:") for variant in removed_variants: - print('-', variant) + print("-", variant) print() for variant in removed_variants: - print(f'Removing {variant}...') + print(f"Removing {variant}...") remove_variant(variant) for variant in new_variants: - print(f'Adding {variant}...') + print(f"Adding {variant}...") add_variant(variant) lib.code.utils.fmt() - print('Done!') + print("Done!") + def get_expected_variants(version_id: str): expected_variants = [] registries = lib.extract.get_registries_report(version_id) - registry = registries['minecraft:data_component_type'] + registry = registries["minecraft:data_component_type"] registry_entries = sorted( - registry['entries'].items(), key=lambda x: x[1]['protocol_id']) + registry["entries"].items(), key=lambda x: x[1]["protocol_id"] + ) for variant_name, _variant in registry_entries: - variant_struct_name = lib.utils.to_camel_case(variant_name.split(':')[-1]) + variant_struct_name = lib.utils.to_camel_case(variant_name.split(":")[-1]) expected_variants.append(variant_struct_name) return expected_variants + def get_actual_variants(): actual_variants = [] - with open(ITEM_COMPONENTS_DIR, 'r') as f: - code = f.read().split('\n') + with open(ITEM_COMPONENTS_DIR, "r") as f: + code = f.read().split("\n") in_match = False for line in code: if in_match: - if line == ' })': + if line == " })": break - variant_line_prefix = ' DataComponentKind::' + variant_line_prefix = " DataComponentKind::" if line.startswith(variant_line_prefix): - variant = line[len(variant_line_prefix):].split(' ', 1)[0] + variant = line[len(variant_line_prefix) :].split(" ", 1)[0] actual_variants.append(variant) - elif line == ' Ok(match kind {': + elif line == " Ok(match kind {": in_match = True return actual_variants + def remove_variant(variant: str): - with open(ITEM_COMPONENTS_DIR, 'r') as f: - code = f.read().split('\n') + with open(ITEM_COMPONENTS_DIR, "r") as f: + code = f.read().split("\n") first_line_with_variant = None line_after_variant = None @@ -87,78 +86,83 @@ def remove_variant(variant: str): in_match = False for i, line in enumerate(list(code)): if in_match: - if line == ' })': + if line == " })": line_after_variant = i break - variant_line_prefix = ' DataComponentKind::' + variant_line_prefix = " DataComponentKind::" if line.startswith(variant_line_prefix): if first_line_with_variant is not None: line_after_variant = i break - variant_name = line[len(variant_line_prefix):].split(' ', 1)[0] + variant_name = line[len(variant_line_prefix) :].split(" ", 1)[0] if variant_name == variant: first_line_with_variant = i - elif line == ' Ok(match kind {': + elif line == " Ok(match kind {": in_match = True - + if first_line_with_variant is None: - raise ValueError(f'Variant {variant} not found') + raise ValueError(f"Variant {variant} not found") if line_after_variant is None: - raise ValueError(f'Couldn\'t find end of variant {variant}') + raise ValueError(f"Couldn't find end of variant {variant}") code = code[:first_line_with_variant] + code[line_after_variant:] # now remove the struct - line_before_struct = None # this is the #[derive] line - line_after_struct = None # impl DataComponent for ... {\n...\n} + line_before_struct = None # this is the #[derive] line + line_after_struct = None # impl DataComponent for ... {\n...\n} for i, line in enumerate(list(code)): - if line == f'pub struct {variant} {{' or line == f'pub struct {variant};': + if line == f"pub struct {variant} {{" or line == f"pub struct {variant};": line_before_struct = i - 1 - elif line == f'impl DataComponent for {variant} {{': + elif line == f"impl DataComponent for {variant} {{": line_after_struct = i + 3 break if line_before_struct is None: - raise ValueError(f'Couldn\'t find struct {variant}') + raise ValueError(f"Couldn't find struct {variant}") if line_after_struct is None: - raise ValueError(f'Couldn\'t find impl DataComponent for {variant}') - + raise ValueError(f"Couldn't find impl DataComponent for {variant}") + code = code[:line_before_struct] + code[line_after_struct:] - with open(ITEM_COMPONENTS_DIR, 'w') as f: - f.write('\n'.join(code)) + with open(ITEM_COMPONENTS_DIR, "w") as f: + f.write("\n".join(code)) + def add_variant(variant: str): - with open(ITEM_COMPONENTS_DIR, 'r') as f: - code = f.read().split('\n') + with open(ITEM_COMPONENTS_DIR, "r") as f: + code = f.read().split("\n") in_match = False last_line_in_match = None for i, line in enumerate(list(code)): if in_match: - if line == ' })': + if line == " })": last_line_in_match = i break - elif line == ' Ok(match kind {': + elif line == " Ok(match kind {": in_match = True if last_line_in_match is None: - raise ValueError('Couldn\'t find end of match') - - code = code[:last_line_in_match] + [ - f' DataComponentKind::{variant} => Box::new({variant}::azalea_read(buf)?),', - ] + code[last_line_in_match:] + raise ValueError("Couldn't find end of match") + + code = ( + code[:last_line_in_match] + + [ + f" DataComponentKind::{variant} => Box::new({variant}::azalea_read(buf)?),", + ] + + code[last_line_in_match:] + ) # now insert the struct - code.append('') - code.append('#[derive(Clone, PartialEq, AzBuf)]') - code.append(f'pub struct {variant} {{') - code.append(' pub todo: todo!(), // see DataComponents.java') - code.append('}') - code.append(f'impl DataComponent for {variant} {{') - code.append(f' const KIND: DataComponentKind = DataComponentKind::{variant};') - code.append('}') + code.append("") + code.append("#[derive(Clone, PartialEq, AzBuf)]") + code.append(f"pub struct {variant} {{") + code.append(" pub todo: todo!(), // see DataComponents.java") + code.append("}") + code.append(f"impl DataComponent for {variant} {{") + code.append(f" const KIND: DataComponentKind = DataComponentKind::{variant};") + code.append("}") - with open(ITEM_COMPONENTS_DIR, 'w') as f: - f.write('\n'.join(code)) + with open(ITEM_COMPONENTS_DIR, "w") as f: + f.write("\n".join(code)) lib.code.utils.fmt() diff --git a/codegen/lib/code/language.py b/codegen/lib/code/language.py index d27dde81..ff3e6906 100644 --- a/codegen/lib/code/language.py +++ b/codegen/lib/code/language.py @@ -1,8 +1,9 @@ from lib.utils import get_dir_location import json -LANGUAGE_DIR = get_dir_location('../azalea-language/src/en_us.json') +LANGUAGE_DIR = get_dir_location("../azalea-language/src/en_us.json") + def write_language(contents: dict): - with open(LANGUAGE_DIR, 'w') as f: - f.write(json.dumps(contents, indent=' ')) \ No newline at end of file + with open(LANGUAGE_DIR, "w") as f: + f.write(json.dumps(contents, indent=" ")) diff --git a/codegen/lib/code/packet.py b/codegen/lib/code/packet.py index aaf6f855..fe0f9f9f 100644 --- a/codegen/lib/code/packet.py +++ b/codegen/lib/code/packet.py @@ -1,4 +1,4 @@ -from lib.utils import padded_hex, to_snake_case, to_camel_case, get_dir_location +from lib.utils import to_snake_case, to_camel_case, get_dir_location from lib.code.utils import burger_type_to_rust_type, write_packet_file from lib.mappings import Mappings from typing import Optional @@ -8,92 +8,106 @@ import re MOJMAP_TO_AZALEA_STATE_NAME_MAPPING = { # shorter name, i like it more - 'configuration': 'config', + "configuration": "config", # in the files mojang calls the directory "game" so we do that too - 'play': 'game' + "play": "game", +} +AZALEA_TO_MOJMAP_STATE_NAME_MAPPING = { + v: k for k, v in MOJMAP_TO_AZALEA_STATE_NAME_MAPPING.items() } -AZALEA_TO_MOJMAP_STATE_NAME_MAPPING = {v: k for k, v in MOJMAP_TO_AZALEA_STATE_NAME_MAPPING.items()} -PACKETS_DIR = '../azalea-protocol/src/packets' +PACKETS_DIR = "../azalea-protocol/src/packets" + def generate_packet(packets_report, packet_name, direction, state): mojmap_state = AZALEA_TO_MOJMAP_STATE_NAME_MAPPING.get(state, state) - _packet_report = packets_report[mojmap_state][direction]['minecraft:' + packet_name] + _packet_report = packets_report[mojmap_state][direction]["minecraft:" + packet_name] code = [] - uses = set() - packet_derive_name = f'{to_camel_case(direction)}{to_camel_case(state)}Packet' + packet_derive_name = f"{to_camel_case(direction)}{to_camel_case(state)}Packet" - packet_struct_name = to_camel_case(f'{direction}_{packet_name}') + packet_struct_name = to_camel_case(f"{direction}_{packet_name}") packet_module_name = get_packet_module_name(packet_name, direction) - code.append(f'use azalea_buf::AzBuf;') - code.append(f'use azalea_protocol_macros::{packet_derive_name};') - code.append('') - - code.append( - f'#[derive(Clone, Debug, AzBuf, {packet_derive_name})]') - code.append( - f'pub struct {packet_struct_name} {{') - code.append(' TODO') - code.append('}') + code.append("use azalea_buf::AzBuf;") + code.append(f"use azalea_protocol_macros::{packet_derive_name};") + code.append("") + + code.append(f"#[derive(Clone, Debug, AzBuf, {packet_derive_name})]") + code.append(f"pub struct {packet_struct_name} {{") + code.append(" TODO") + code.append("}") print(code) - write_packet_file(state, packet_module_name, '\n'.join(code)) + write_packet_file(state, packet_module_name, "\n".join(code)) # this won't handle writing to the packets/{state}/mod.rs file since we'd need to know the full packet list -def get_packet_module_name(packet_name: str, direction: str): - return f'{direction[0]}_{packet_name}' -def set_packets(packets_report): +def get_packet_module_name(packet_name: str, direction: str): + return f"{direction[0]}_{packet_name}" + + +def set_packets(packets_report): for mojmap_state in packets_report: state = MOJMAP_TO_AZALEA_STATE_NAME_MAPPING.get(mojmap_state, mojmap_state) expected_packet_module_names = set() - state_dir = get_dir_location(f'{PACKETS_DIR}/{state}') - mod_rs_dir = get_dir_location(f'{state_dir}/mod.rs') + state_dir = get_dir_location(f"{PACKETS_DIR}/{state}") + mod_rs_dir = get_dir_location(f"{state_dir}/mod.rs") - serverbound_packets = packet_direction_report_to_packet_names(packets_report[mojmap_state]['serverbound']) - clientbound_packets = packet_direction_report_to_packet_names(packets_report[mojmap_state].get('clientbound', {})) + serverbound_packets = packet_direction_report_to_packet_names( + packets_report[mojmap_state]["serverbound"] + ) + clientbound_packets = packet_direction_report_to_packet_names( + packets_report[mojmap_state].get("clientbound", {}) + ) code = [] - code.append('// NOTE: This file is generated automatically by codegen/packet.py.') + code.append( + "// NOTE: This file is @generated automatically by codegen/packet.py." + ) code.append("// Don't edit it directly!") - code.append('') - code.append('use azalea_protocol_macros::declare_state_packets;') - code.append('') - code.append(f'declare_state_packets!({to_camel_case(state)}Packet,') - code.append(' Clientbound => [') + code.append("") + code.append("use azalea_protocol_macros::declare_state_packets;") + code.append("") + code.append(f"declare_state_packets!({to_camel_case(state)}Packet,") + code.append(" Clientbound => [") for packet_id, packet_name in enumerate(clientbound_packets): - code.append(f' {packet_name},') - expected_packet_module_names.add(get_packet_module_name(packet_name, 'clientbound')) - code.append(' ],') - code.append(' Serverbound => [') + code.append(f" {packet_name},") + expected_packet_module_names.add( + get_packet_module_name(packet_name, "clientbound") + ) + code.append(" ],") + code.append(" Serverbound => [") for packet_id, packet_name in enumerate(serverbound_packets): - code.append(f' {packet_name},') - expected_packet_module_names.add(get_packet_module_name(packet_name, 'serverbound')) - code.append(' ]') - code.append(');') - code.append('') + code.append(f" {packet_name},") + expected_packet_module_names.add( + get_packet_module_name(packet_name, "serverbound") + ) + code.append(" ]") + code.append(");") + code.append("") - with open(mod_rs_dir, 'w') as f: - f.write('\n'.join(code)) + with open(mod_rs_dir, "w") as f: + f.write("\n".join(code)) existing_packet_module_names = set() # iterate over the directory for file in os.listdir(state_dir): - if file.endswith('.rs') and file != 'mod.rs': - existing_packet_module_names.add(file[:-len('.rs')]) - for packet_module_name in expected_packet_module_names - existing_packet_module_names: + if file.endswith(".rs") and file != "mod.rs": + existing_packet_module_names.add(file[: -len(".rs")]) + for packet_module_name in ( + expected_packet_module_names - existing_packet_module_names + ): direction = None - if packet_module_name.startswith('c_'): - direction = 'clientbound' - elif packet_module_name.startswith('s_'): - direction = 'serverbound' + if packet_module_name.startswith("c_"): + direction = "clientbound" + elif packet_module_name.startswith("s_"): + direction = "serverbound" else: - raise Exception(f'Invalid packet module name: {packet_module_name}') + raise Exception(f"Invalid packet module name: {packet_module_name}") packet = packet_module_name[2:] generate_packet(packets_report, packet, direction, state) @@ -101,15 +115,16 @@ def set_packets(packets_report): def packet_direction_report_to_packet_names(report): name_to_id = {} for resource_location, packet in report.items(): - packet_id = packet['protocol_id'] - name_to_id[resource_location.split(':')[-1]] = packet_id - + packet_id = packet["protocol_id"] + name_to_id[resource_location.split(":")[-1]] = packet_id + names_sorted = [name for name in sorted(name_to_id, key=lambda x: name_to_id[x])] return names_sorted + def get_packets(direction: str, state: str): - mod_rs_dir = get_dir_location(f'{PACKETS_DIR}/{state}/mod.rs') - with open(mod_rs_dir, 'r') as f: + mod_rs_dir = get_dir_location(f"{PACKETS_DIR}/{state}/mod.rs") + with open(mod_rs_dir, "r") as f: mod_rs = f.read().splitlines() in_serverbound = False @@ -119,90 +134,125 @@ def get_packets(direction: str, state: str): packet_class_names: list[str] = [] for line in mod_rs: - if line.strip() == 'Serverbound => {': + if line.strip() == "Serverbound => {": in_serverbound = True continue - elif line.strip() == 'Clientbound => {': + elif line.strip() == "Clientbound => {": in_clientbound = True continue - elif line.strip() in ('}', '},'): - if (in_serverbound and direction == 'serverbound') or (in_clientbound and direction == 'clientbound'): + elif line.strip() in ("}", "},"): + if (in_serverbound and direction == "serverbound") or ( + in_clientbound and direction == "clientbound" + ): break in_serverbound = in_clientbound = False continue - if line.strip() == '' or line.strip().startswith('//') or (not in_serverbound and direction == 'serverbound') or (not in_clientbound and direction == 'clientbound'): + if ( + line.strip() == "" + or line.strip().startswith("//") + or (not in_serverbound and direction == "serverbound") + or (not in_clientbound and direction == "clientbound") + ): continue - line_packet_id_hex = line.strip().split(':')[0] - assert line_packet_id_hex.startswith('0x') + line_packet_id_hex = line.strip().split(":")[0] + assert line_packet_id_hex.startswith("0x") line_packet_id = int(line_packet_id_hex[2:], 16) packet_ids.append(line_packet_id) - packet_class_name = line.strip().split(':')[1].strip() + packet_class_name = line.strip().split(":")[1].strip() packet_class_names.append(packet_class_name) return packet_ids, packet_class_names -def burger_instruction_to_code(instructions: list[dict], index: int, generated_packet_code: list[str], mappings: Mappings, obfuscated_class_name: str, uses: set, extra_code: list[str], known_variable_types={}) -> Optional[int]: - ''' +def burger_instruction_to_code( + instructions: list[dict], + index: int, + generated_packet_code: list[str], + mappings: Mappings, + obfuscated_class_name: str, + uses: set, + extra_code: list[str], + known_variable_types={}, +) -> Optional[int]: + """ Generate a field for an instruction, returns the number of instructions to skip (if any). - ''' + """ instruction = instructions[index] - next_instruction = instructions[index + - 1] if index + 1 < len(instructions) else None - next_next_instruction = instructions[index + - 2] if index + 2 < len(instructions) else None + next_instruction = ( + instructions[index + 1] if index + 1 < len(instructions) else None + ) + next_next_instruction = ( + instructions[index + 2] if index + 2 < len(instructions) else None + ) is_var = False skip = 0 field_type_rs = None field_comment = None - print('instruction', instruction, next_instruction, next_next_instruction) + print("instruction", instruction, next_instruction, next_next_instruction) # iterators - if instruction['operation'] == 'write'\ - and instruction['field'].endswith('.size()')\ - and next_instruction\ - and next_instruction['type'] == 'Iterator'\ - and next_next_instruction\ - and next_next_instruction['operation'] == 'loop': - obfuscated_field_name = instruction['field'].split('.')[0] - field_name = mappings.get_field( - obfuscated_class_name, obfuscated_field_name) + if ( + instruction["operation"] == "write" + and instruction["field"].endswith(".size()") + and next_instruction + and next_instruction["type"] == "Iterator" + and next_next_instruction + and next_next_instruction["operation"] == "loop" + ): + obfuscated_field_name = instruction["field"].split(".")[0] + field_name = mappings.get_field(obfuscated_class_name, obfuscated_field_name) # figure out what kind of iterator it is - loop_instructions = next_next_instruction['instructions'] + loop_instructions = next_next_instruction["instructions"] if len(loop_instructions) == 2: entry_type_rs, is_var, value_uses, extra_code = burger_type_to_rust_type( - loop_instructions[1]['type'], None, loop_instructions[1], mappings, obfuscated_class_name) - field_type_rs = f'Vec<{entry_type_rs}>' + loop_instructions[1]["type"], + None, + loop_instructions[1], + mappings, + obfuscated_class_name, + ) + field_type_rs = f"Vec<{entry_type_rs}>" uses.update(value_uses) elif len(loop_instructions) == 3: - is_map = loop_instructions[0]['type'].startswith( - 'Map.Entry<') + is_map = loop_instructions[0]["type"].startswith("Map.Entry<") if is_map: - assert loop_instructions[1]['field'].endswith( - '.getKey()') - assert loop_instructions[2]['field'].endswith( - '.getValue()') + assert loop_instructions[1]["field"].endswith(".getKey()") + assert loop_instructions[2]["field"].endswith(".getValue()") # generate the type for the key - key_type_rs, is_key_var, key_uses, key_extra_code = burger_type_to_rust_type( - loop_instructions[1]['type'], None, loop_instructions[1], mappings, obfuscated_class_name) + key_type_rs, is_key_var, key_uses, key_extra_code = ( + burger_type_to_rust_type( + loop_instructions[1]["type"], + None, + loop_instructions[1], + mappings, + obfuscated_class_name, + ) + ) uses.update(key_uses) extra_code.extend(key_extra_code) # generate the type for the value - value_type_rs, is_value_var, value_uses, value_extra_code = burger_type_to_rust_type( - loop_instructions[2]['type'], None, loop_instructions[2], mappings, obfuscated_class_name) + value_type_rs, is_value_var, value_uses, value_extra_code = ( + burger_type_to_rust_type( + loop_instructions[2]["type"], + None, + loop_instructions[2], + mappings, + obfuscated_class_name, + ) + ) uses.update(value_uses) extra_code.extend(value_extra_code) - field_type_rs = f'HashMap<{key_type_rs}, {value_type_rs}>' - uses.add('std::collections::HashMap') + field_type_rs = f"HashMap<{key_type_rs}, {value_type_rs}>" + uses.add("std::collections::HashMap") # only the key is var since the value can be made var in other ways is_var = is_key_var @@ -210,160 +260,206 @@ def burger_instruction_to_code(instructions: list[dict], index: int, generated_p skip = 2 # skip the next 2 instructions # Option - elif instruction['operation'] == 'write' and (instruction['field'].endswith('.isPresent()') or instruction['field'].endswith(' != null')) and next_instruction and (next_instruction.get('condition', '').endswith('.isPresent()') or next_instruction.get('condition', '').endswith(' != null')): - print('ok is option') - obfuscated_field_name = instruction['field'].split('.')[ - 0].split(' ')[0] - + elif ( + instruction["operation"] == "write" + and ( + instruction["field"].endswith(".isPresent()") + or instruction["field"].endswith(" != null") + ) + and next_instruction + and ( + next_instruction.get("condition", "").endswith(".isPresent()") + or next_instruction.get("condition", "").endswith(" != null") + ) + ): + print("ok is option") + obfuscated_field_name = instruction["field"].split(".")[0].split(" ")[0] + if obfuscated_field_name in known_variable_types: # just use the known name since it's not gonna be in the mappings obfuscated_field_name = known_variable_types[obfuscated_field_name] - - field_name = mappings.get_field( - obfuscated_class_name, obfuscated_field_name) - if field_name is None: field_name = obfuscated_field_name.split('/')[-1] - if '<' in field_name: - field_name = 'value' + field_name = mappings.get_field(obfuscated_class_name, obfuscated_field_name) - condition_instructions = next_instruction['instructions'] + if field_name is None: + field_name = obfuscated_field_name.split("/")[-1] + if "<" in field_name: + field_name = "value" + + condition_instructions = next_instruction["instructions"] condition_types_rs = [] for condition_instruction in condition_instructions: - print('condition_instruction', condition_instruction) - if 'type' not in condition_instruction: + print("condition_instruction", condition_instruction) + if "type" not in condition_instruction: # weird type, maybe it's a loop or something - condition_types_rs.append('todo!("weird type, maybe it\'s a loop or something")') + condition_types_rs.append( + 'todo!("weird type, maybe it\'s a loop or something")' + ) continue - condition_type_rs, is_var, this_uses, this_extra_code = burger_type_to_rust_type( - condition_instruction['type'], None, condition_instruction, mappings, obfuscated_class_name) + condition_type_rs, is_var, this_uses, this_extra_code = ( + burger_type_to_rust_type( + condition_instruction["type"], + None, + condition_instruction, + mappings, + obfuscated_class_name, + ) + ) condition_types_rs.append(condition_type_rs) uses.update(this_uses) extra_code.extend(this_extra_code) - field_type_rs = f'Option<({", ".join(condition_types_rs)})>' if len( - condition_types_rs) != 1 else f'Option<{condition_types_rs[0]}>' + field_type_rs = ( + f"Option<({', '.join(condition_types_rs)})>" + if len(condition_types_rs) != 1 + else f"Option<{condition_types_rs[0]}>" + ) skip = 1 else: - field_type = instruction['type'] - obfuscated_field_name = instruction['field'] + field_type = instruction["type"] + obfuscated_field_name = instruction["field"] - if obfuscated_field_name.startswith('(float)'): - obfuscated_field_name = obfuscated_field_name[len('(float)'):] + if obfuscated_field_name.startswith("(float)"): + obfuscated_field_name = obfuscated_field_name[len("(float)") :] field_name = mappings.get_field( - obfuscated_class_name, obfuscated_field_name) or mappings.get_field( - obfuscated_class_name.split('$')[0], obfuscated_field_name) + obfuscated_class_name, obfuscated_field_name + ) or mappings.get_field( + obfuscated_class_name.split("$")[0], obfuscated_field_name + ) - field_type_rs, is_var, instruction_uses, instruction_extra_code = burger_type_to_rust_type( - field_type, field_name, instruction, mappings, obfuscated_class_name) + field_type_rs, is_var, instruction_uses, instruction_extra_code = ( + burger_type_to_rust_type( + field_type, field_name, instruction, mappings, obfuscated_class_name + ) + ) if obfuscated_field_name in known_variable_types: # just use the known name since it's not gonna be in the mappings field_name = obfuscated_field_name - elif '.' in obfuscated_field_name or ' ' in obfuscated_field_name or '(' in obfuscated_field_name: + elif ( + "." in obfuscated_field_name + or " " in obfuscated_field_name + or "(" in obfuscated_field_name + ): field_type_rs2, obfuscated_field_name, field_comment = burger_field_to_type( - obfuscated_field_name, mappings, obfuscated_class_name, known_variable_types) + obfuscated_field_name, + mappings, + obfuscated_class_name, + known_variable_types, + ) if not field_type_rs2: - generated_packet_code.append(f'// TODO: {instruction}') + generated_packet_code.append(f"// TODO: {instruction}") return if obfuscated_field_name in known_variable_types: # just use the known name since it's not gonna be in the mappings obfuscated_field_name = known_variable_types[obfuscated_field_name] - print('got obfuscated_field_name', obfuscated_field_name) + print("got obfuscated_field_name", obfuscated_field_name) # try to get the field name again with the new stuff we know field_name = mappings.get_field( - obfuscated_class_name, obfuscated_field_name) or mappings.get_field( - obfuscated_class_name.split('$')[0], obfuscated_field_name) + obfuscated_class_name, obfuscated_field_name + ) or mappings.get_field( + obfuscated_class_name.split("$")[0], obfuscated_field_name + ) if field_name is None: - field_name = obfuscated_field_name.split('/')[-1] + field_name = obfuscated_field_name.split("/")[-1] uses.update(instruction_uses) extra_code.extend(instruction_extra_code) if not field_name: - generated_packet_code.append( - f'// TODO: unknown field {instruction}') + generated_packet_code.append(f"// TODO: unknown field {instruction}") return skip if is_var: - generated_packet_code.append('#[var]') - line = f'pub {to_snake_case(field_name)}: {field_type_rs or "todo!()"},' + generated_packet_code.append("#[var]") + line = f"pub {to_snake_case(field_name)}: {field_type_rs or 'todo!()'}," if field_comment: - line += f' // {field_comment}' + line += f" // {field_comment}" generated_packet_code.append(line) return skip -def burger_field_to_type(field, mappings: Mappings, obfuscated_class_name: str, known_variable_types={}) -> tuple[Optional[str], str, Optional[str]]: - ''' +def burger_field_to_type( + field, mappings: Mappings, obfuscated_class_name: str, known_variable_types={} +) -> tuple[Optional[str], str, Optional[str]]: + """ Returns field_type_rs, obfuscated_field_name, field_comment - ''' + """ # match `(x) ? 1 : 0` - match = re.match(r'\((.*)\) \? 1 : 0', field) + match = re.match(r"\((.*)\) \? 1 : 0", field) if match: - return ('bool', match.group(1), None) - match = re.match(r'^\w+\.\w+\(\)$', field) + return ("bool", match.group(1), None) + match = re.match(r"^\w+\.\w+\(\)$", field) if match: - print('field', field) - obfuscated_first = field.split('.')[0] - obfuscated_second = field.split('.')[1].split('(')[0] + print("field", field) + obfuscated_first = field.split(".")[0] + obfuscated_second = field.split(".")[1].split("(")[0] # first = mappings.get_field(obfuscated_class_name, obfuscated_first) if obfuscated_first in known_variable_types: first_type = known_variable_types[obfuscated_first] else: try: first_type = mappings.get_field_type( - obfuscated_class_name, obfuscated_first) - except: - first_type = 'TODO' - first_obfuscated_class_name: Optional[str] = mappings.get_class_from_deobfuscated_name( - first_type) + obfuscated_class_name, obfuscated_first + ) + except Exception: + first_type = "TODO" + first_obfuscated_class_name: Optional[str] = ( + mappings.get_class_from_deobfuscated_name(first_type) + ) if first_obfuscated_class_name: try: second = mappings.get_method( - first_obfuscated_class_name, obfuscated_second, '') - except: + first_obfuscated_class_name, obfuscated_second, "" + ) + except Exception: # if this happens then the field is probably from a super class second = obfuscated_second else: second = obfuscated_second - first_type_short = first_type.split('.')[-1] - if second in {'byteValue'}: + first_type_short = first_type.split(".")[-1] + if second in {"byteValue"}: return (first_type_short, obfuscated_first, None) - return (first_type_short, obfuscated_first, f'TODO: Does {first_type_short}::{second}, may not be implemented') + return ( + first_type_short, + obfuscated_first, + f"TODO: Does {first_type_short}::{second}, may not be implemented", + ) return None, field, None def change_packet_ids(id_map: dict[int, int], direction: str, state: str): - existing_packet_ids, existing_packet_class_names = get_packets( - direction, state) + existing_packet_ids, existing_packet_class_names = get_packets(direction, state) new_packet_ids = [] for packet_id in existing_packet_ids: new_packet_id = id_map.get(packet_id, packet_id) if new_packet_id in new_packet_ids: - raise Exception('Two packets have the same id') + raise Exception("Two packets have the same id") new_packet_ids.append(new_packet_id) set_packets(new_packet_ids, existing_packet_class_names, direction, state) def remove_packet_ids(removing_packet_ids: list[int], direction: str, state: str): - existing_packet_ids, existing_packet_class_names = get_packets( - direction, state) + existing_packet_ids, existing_packet_class_names = get_packets(direction, state) new_packet_ids = [] new_packet_class_names = [] - for packet_id, packet_class_name in zip(existing_packet_ids, existing_packet_class_names): + for packet_id, packet_class_name in zip( + existing_packet_ids, existing_packet_class_names + ): if packet_id in removing_packet_ids: try: os.remove( - f'../azalea-protocol/src/packets/{state}/{packet_class_name}.rs') - except: + f"../azalea-protocol/src/packets/{state}/{packet_class_name}.rs" + ) + except Exception: pass else: new_packet_ids.append(packet_id) @@ -380,18 +476,22 @@ def are_packet_instructions_identical(old_packet, new_packet): return False for old_field, new_field in zip(old_packet, new_packet): - if old_field['operation'] != new_field['operation']: + if old_field["operation"] != new_field["operation"]: return False - if new_field['operation'] == 'write': - if burger_type_to_rust_type(old_field.get('type')) != burger_type_to_rust_type(new_field.get('type')): + if new_field["operation"] == "write": + if burger_type_to_rust_type( + old_field.get("type") + ) != burger_type_to_rust_type(new_field.get("type")): return False else: # comparing is too complicated here since it's possible the type has variables # so we just don't pass - if 'instructions' in old_field and 'instructions' in new_field: - if not are_packet_instructions_identical(old_field['instructions'], new_field['instructions']): + if "instructions" in old_field and "instructions" in new_field: + if not are_packet_instructions_identical( + old_field["instructions"], new_field["instructions"] + ): return False return True diff --git a/codegen/lib/code/registry.py b/codegen/lib/code/registry.py index d1399da5..84d613ba 100644 --- a/codegen/lib/code/registry.py +++ b/codegen/lib/code/registry.py @@ -1,13 +1,11 @@ -from lib.utils import to_snake_case, upper_first_letter, get_dir_location, to_camel_case -from ..mappings import Mappings -from typing import Optional -import re +from lib.utils import get_dir_location, to_camel_case + +REGISTRIES_DIR = get_dir_location("../azalea-registry/src/lib.rs") -REGISTRIES_DIR = get_dir_location('../azalea-registry/src/lib.rs') def generate_registries(registries: dict): - with open(REGISTRIES_DIR, 'r') as f: - code = f.read().split('\n') + with open(REGISTRIES_DIR, "r") as f: + code = f.read().split("\n") existing_registry_enum_names = set() @@ -17,19 +15,20 @@ def generate_registries(registries: dict): # Stone => "minecraft:stone" # }); - registry_name = registry_name.split(':')[1] + registry_name = registry_name.split(":")[1] registry_enum_name = registry_name_to_enum_name(registry_name) existing_registry_enum_names.add(registry_enum_name) registry_code = [] - registry_code.append(f'enum {registry_enum_name} {{') + registry_code.append(f"enum {registry_enum_name} {{") registry_entries = sorted( - registry['entries'].items(), key=lambda x: x[1]['protocol_id']) + registry["entries"].items(), key=lambda x: x[1]["protocol_id"] + ) for variant_name, _variant in registry_entries: - variant_struct_name = to_camel_case(variant_name.split(':')[-1]) + variant_struct_name = to_camel_case(variant_name.split(":")[-1]) registry_code.append(f'\t{variant_struct_name} => "{variant_name}",') - registry_code.append('}') + registry_code.append("}") # when we find a "registry! {" line, find the next line that starts # with "enum " and replace that until we find a line that's "}" @@ -40,28 +39,28 @@ def generate_registries(registries: dict): in_registry_macro = True elif in_registry_macro and line == registry_code[0]: # found it, now delete until we get to "}" - while code[i] != '}': + while code[i] != "}": code.pop(i) - code[i] = '\n'.join(registry_code) + code[i] = "\n".join(registry_code) found = True break if not found: - code.append('registry! {') - code.append('\n'.join(registry_code)) - code.append('}') - code.append('') + code.append("registry! {") + code.append("\n".join(registry_code)) + code.append("}") + code.append("") # delete the unused registries i = 0 while i < len(code): - if code[i] == 'registry! {': + if code[i] == "registry! {": # skip until we get to the enum line - while not code[i].startswith('enum '): + while not code[i].startswith("enum "): i += 1 - enum_name = code[i].split(' ')[1] + enum_name = code[i].split(" ")[1] if enum_name not in existing_registry_enum_names: i -= 1 - while code[i] != '}': + while code[i] != "}": code.pop(i) code.pop(i) # close the registry! block @@ -69,17 +68,18 @@ def generate_registries(registries: dict): else: i += 1 - with open(REGISTRIES_DIR, 'w') as f: - f.write('\n'.join(code)) + with open(REGISTRIES_DIR, "w") as f: + f.write("\n".join(code)) + def registry_name_to_enum_name(registry_name: str) -> str: - registry_name = registry_name.split(':')[-1] + registry_name = registry_name.split(":")[-1] - if registry_name.endswith('_type'): + if registry_name.endswith("_type"): # change _type to _kind because that's Rustier (and because _type # is a reserved keyword) - registry_name = registry_name[:-5] + '_kind' - elif registry_name in {'menu'}: - registry_name += '_kind' + registry_name = registry_name[:-5] + "_kind" + elif registry_name in {"menu"}: + registry_name += "_kind" return to_camel_case(registry_name) diff --git a/codegen/lib/code/shapes.py b/codegen/lib/code/shapes.py index 3a3e6d83..f3a5d513 100644 --- a/codegen/lib/code/shapes.py +++ b/codegen/lib/code/shapes.py @@ -1,46 +1,40 @@ -from lib.utils import get_dir_location, to_camel_case -from ..mappings import Mappings +from lib.utils import get_dir_location -COLLISION_BLOCKS_RS_DIR = get_dir_location( - '../azalea-physics/src/collision/blocks.rs') +COLLISION_BLOCKS_RS_DIR = get_dir_location("../azalea-physics/src/collision/blocks.rs") def generate_block_shapes(pumpkin_block_datas: dict, block_states_report): blocks, shapes = simplify_shapes(pumpkin_block_datas) code = generate_block_shapes_code(blocks, shapes, block_states_report) - with open(COLLISION_BLOCKS_RS_DIR, 'w') as f: + with open(COLLISION_BLOCKS_RS_DIR, "w") as f: f.write(code) def simplify_shapes(blocks: dict) -> tuple[dict, dict]: - ''' + """ Returns new_blocks and new_shapes, where new_blocks is like { grass_block: { collision: [1, 1], outline: [1, 1] } } and new_shapes is like { 1: [ [0, 0, 0, 1, 1, 1] ] } - ''' + """ new_blocks = {} new_shapes = {} all_shapes_ids = {} - for block_data in blocks['blocks']: + for block_data in blocks["blocks"]: new_block_collision_shapes = [] new_block_outline_shapes = [] - for state in block_data['states']: + for state in block_data["states"]: collision_shape = [] - for box_id in state['collision_shapes']: - box = blocks['shapes'][box_id] - collision_shape.append( - tuple(box['min'] + box['max']) - ) + for box_id in state["collision_shapes"]: + box = blocks["shapes"][box_id] + collision_shape.append(tuple(box["min"] + box["max"])) outline_shape = [] - for box_id in state['outline_shapes']: - box = blocks['shapes'][box_id] - outline_shape.append( - tuple(box['min'] + box['max']) - ) + for box_id in state["outline_shapes"]: + box = blocks["shapes"][box_id] + outline_shape.append(tuple(box["min"] + box["max"])) collision_shape = tuple(collision_shape) outline_shape = tuple(outline_shape) @@ -58,27 +52,25 @@ def simplify_shapes(blocks: dict) -> tuple[dict, dict]: all_shapes_ids[outline_shape] = outline_shape_id new_shapes[outline_shape_id] = outline_shape - block_id = block_data['name'] + block_id = block_data["name"] new_block_collision_shapes.append(collision_shape_id) new_block_outline_shapes.append(outline_shape_id) new_blocks[block_id] = { - 'collision': new_block_collision_shapes, - 'outline': new_block_outline_shapes + "collision": new_block_collision_shapes, + "outline": new_block_outline_shapes, } - return new_blocks, new_shapes def generate_block_shapes_code(blocks: dict, shapes: dict, block_states_report): # look at __cache__/generator-mod-*/blockCollisionShapes.json for format of blocks and shapes - generated_shape_code = '' - for (shape_id, shape) in sorted(shapes.items(), key=lambda shape: int(shape[0])): + generated_shape_code = "" + for shape_id, shape in sorted(shapes.items(), key=lambda shape: int(shape[0])): generated_shape_code += generate_code_for_shape(shape_id, shape) - # static COLLISION_SHAPES_MAP: [&LazyLock; 26644] = [&SHAPE0, &SHAPE1, &SHAPE1, ...] empty_shapes = [] full_shapes = [] @@ -88,48 +80,56 @@ def generate_block_shapes_code(blocks: dict, shapes: dict, block_states_report): outline_shapes_map = [] for block_id, shape_datas in blocks.items(): - collision_shapes = shape_datas['collision'] - outline_shapes = shape_datas['outline'] + collision_shapes = shape_datas["collision"] + outline_shapes = shape_datas["outline"] - if isinstance(collision_shapes, int): collision_shapes = [collision_shapes] - if isinstance(outline_shapes, int): outline_shapes = [outline_shapes] + if isinstance(collision_shapes, int): + collision_shapes = [collision_shapes] + if isinstance(outline_shapes, int): + outline_shapes = [outline_shapes] - block_report_data = block_states_report['minecraft:' + block_id] + block_report_data = block_states_report["minecraft:" + block_id] - for possible_state, shape_id in zip(block_report_data['states'], collision_shapes): - block_state_id = possible_state['id'] - if shape_id == 0: empty_shapes.append(block_state_id) - elif shape_id == 1: full_shapes.append(block_state_id) + for possible_state, shape_id in zip( + block_report_data["states"], collision_shapes + ): + block_state_id = possible_state["id"] + if shape_id == 0: + empty_shapes.append(block_state_id) + elif shape_id == 1: + full_shapes.append(block_state_id) while len(collision_shapes_map) <= block_state_id: # default to shape 1 for missing shapes (full block) collision_shapes_map.append(1) collision_shapes_map[block_state_id] = shape_id - for possible_state, shape_id in zip(block_report_data['states'], outline_shapes): - block_state_id = possible_state['id'] + for possible_state, shape_id in zip( + block_report_data["states"], outline_shapes + ): + block_state_id = possible_state["id"] while len(outline_shapes_map) <= block_state_id: # default to shape 1 for missing shapes (full block) outline_shapes_map.append(1) outline_shapes_map[block_state_id] = shape_id - generated_map_code = f'static COLLISION_SHAPES_MAP: [&LazyLock; {len(collision_shapes_map)}] = [' + generated_map_code = f"static COLLISION_SHAPES_MAP: [&LazyLock; {len(collision_shapes_map)}] = [" empty_shape_match_code = convert_ints_to_rust_ranges(empty_shapes) block_shape_match_code = convert_ints_to_rust_ranges(full_shapes) for block_state_id, shape_id in enumerate(collision_shapes_map): - generated_map_code += f'&SHAPE{shape_id},\n' - generated_map_code += '];\n' + generated_map_code += f"&SHAPE{shape_id},\n" + generated_map_code += "];\n" - generated_map_code += f'static OUTLINE_SHAPES_MAP: [&LazyLock; {len(outline_shapes_map)}] = [' + generated_map_code += f"static OUTLINE_SHAPES_MAP: [&LazyLock; {len(outline_shapes_map)}] = [" for block_state_id, shape_id in enumerate(outline_shapes_map): - generated_map_code += f'&SHAPE{shape_id},\n' - generated_map_code += '];\n' + generated_map_code += f"&SHAPE{shape_id},\n" + generated_map_code += "];\n" - if empty_shape_match_code == '': - print('Error: shape 0 was not found') + if empty_shape_match_code == "": + print("Error: shape 0 was not found") - return f''' + return f""" //! 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)] @@ -172,36 +172,35 @@ impl BlockWithShape for BlockState {{ }} {generated_map_code} -''' - - +""" def generate_code_for_shape(shape_id: str, parts: list[list[float]]): def make_arguments(part: list[float]): - return ', '.join(map(lambda n: str(n).rstrip('0'), part)) - code = '' - code += f'static SHAPE{shape_id}: LazyLock = LazyLock::new(|| {{' + return ", ".join(map(lambda n: str(n).rstrip("0"), part)) + + code = "" + code += f"static SHAPE{shape_id}: LazyLock = LazyLock::new(|| {{" steps = [] if parts == (): - steps.append('collision::EMPTY_SHAPE.clone()') + steps.append("collision::EMPTY_SHAPE.clone()") else: - steps.append(f'collision::box_shape({make_arguments(parts[0])})') + steps.append(f"collision::box_shape({make_arguments(parts[0])})") for part in parts[1:]: - steps.append( - f'Shapes::or(s, collision::box_shape({make_arguments(part)}))') + steps.append(f"Shapes::or(s, collision::box_shape({make_arguments(part)}))") if len(steps) == 1: code += steps[0] else: - code += '{\n' + code += "{\n" for step in steps[:-1]: - code += f' let s = {step};\n' - code += f' {steps[-1]}\n' - code += '}\n' - code += '});\n' + code += f" let s = {step};\n" + code += f" {steps[-1]}\n" + code += "}\n" + code += "});\n" return code + def convert_ints_to_rust_ranges(block_state_ids: list[int]) -> str: # convert them into ranges (so like 1|2|3 is 1..=3 instead) block_state_ids_ranges = [] @@ -214,10 +213,18 @@ def convert_ints_to_rust_ranges(block_state_ids: list[int]) -> str: if last_block_state_id is not None: # check if the range is done if block_state_id - 1 != last_block_state_id: - block_state_ids_ranges.append(f'{range_start_block_state_id}..={last_block_state_id}' if range_start_block_state_id != last_block_state_id else str(range_start_block_state_id)) + block_state_ids_ranges.append( + f"{range_start_block_state_id}..={last_block_state_id}" + if range_start_block_state_id != last_block_state_id + else str(range_start_block_state_id) + ) range_start_block_state_id = block_state_id last_block_state_id = block_state_id - block_state_ids_ranges.append(f'{range_start_block_state_id}..={last_block_state_id}' if range_start_block_state_id != last_block_state_id else str(range_start_block_state_id)) - return '|'.join(block_state_ids_ranges) + block_state_ids_ranges.append( + f"{range_start_block_state_id}..={last_block_state_id}" + if range_start_block_state_id != last_block_state_id + else str(range_start_block_state_id) + ) + return "|".join(block_state_ids_ranges) diff --git a/codegen/lib/code/tags.py b/codegen/lib/code/tags.py index 6f6ec9a3..9bacc3d5 100644 --- a/codegen/lib/code/tags.py +++ b/codegen/lib/code/tags.py @@ -1,34 +1,36 @@ from lib.utils import to_snake_case, upper_first_letter, get_dir_location, to_camel_case -REGISTRIES_DIR = get_dir_location('../azalea-registry/src/tags') +REGISTRIES_DIR = get_dir_location("../azalea-registry/src/tags") def generate_tags(registries: dict, file_name: str, struct_name: str): - tags_dir = f'{REGISTRIES_DIR}/{file_name}.rs' + tags_dir = f"{REGISTRIES_DIR}/{file_name}.rs" - generated = f'''// This file was generated by codegen/lib/code/tags.py, don't edit it manually! + generated = f"""// This file was @generated by codegen/lib/code/tags.py, don't edit it manually! use std::{{collections::HashSet, sync::LazyLock}}; use crate::{struct_name}; -''' +""" for tag_name, tag in sorted(registries.items(), key=lambda x: x[0]): - tag_name = tag_name.replace('/', '_') + tag_name = tag_name.replace("/", "_") static_set_name = to_snake_case(tag_name).upper() - generated += f'pub static {static_set_name}: LazyLock> = LazyLock::new(|| HashSet::from_iter(vec![' + generated += f"pub static {static_set_name}: LazyLock> = LazyLock::new(|| HashSet::from_iter(vec![" - queue = tag['values'].copy() + queue = tag["values"].copy() while queue != []: item = queue.pop(0) - namespace, item_name = item.split(':') - if namespace[0] == '#': - queue += registries[item_name]['values'] + namespace, item_name = item.split(":") + if namespace[0] == "#": + queue += registries[item_name]["values"] continue - generated += f'{struct_name}::{upper_first_letter(to_camel_case(item_name))},\n' - generated += ']));\n' + generated += ( + f"{struct_name}::{upper_first_letter(to_camel_case(item_name))},\n" + ) + generated += "]));\n" - with open(tags_dir, 'w') as f: - f.write(generated) \ No newline at end of file + with open(tags_dir, "w") as f: + f.write(generated) diff --git a/codegen/lib/code/utils.py b/codegen/lib/code/utils.py index 1a87b7f6..b8970628 100644 --- a/codegen/lib/code/utils.py +++ b/codegen/lib/code/utils.py @@ -1,175 +1,204 @@ +# utilities specifically for codegen from lib.utils import to_camel_case, to_snake_case, get_dir_location from lib.mappings import Mappings from typing import Optional import os -# utilities specifically for codegen - -def burger_type_to_rust_type(burger_type, field_name: Optional[str] = None, instruction=None, mappings: Optional[Mappings] = None, obfuscated_class_name: Optional[str] = None): +def burger_type_to_rust_type( + burger_type, + field_name: Optional[str] = None, + instruction=None, + mappings: Optional[Mappings] = None, + obfuscated_class_name: Optional[str] = None, +): is_var = False uses = set() # extra code, like enum definitions extra_code = [] should_be_signed = False - if field_name and any(map(lambda w: w in {'x', 'y', 'z', 'xa', 'ya', 'za'}, to_snake_case(field_name).split('_'))): + if field_name and any( + map( + lambda w: w in {"x", "y", "z", "xa", "ya", "za"}, + to_snake_case(field_name).split("_"), + ) + ): # coordinates are signed should_be_signed = True - if burger_type == 'byte': - field_type_rs = 'i8' if should_be_signed else 'u8' - elif burger_type == 'short': - field_type_rs = 'i16' if should_be_signed else 'u16' - elif burger_type == 'int': - field_type_rs = 'i32' if should_be_signed else 'u32' - elif burger_type == 'long': - field_type_rs = 'i64' if should_be_signed else 'u64' - elif burger_type == 'float': - field_type_rs = 'f32' - elif burger_type == 'double': - field_type_rs = 'f64' + if burger_type == "byte": + field_type_rs = "i8" if should_be_signed else "u8" + elif burger_type == "short": + field_type_rs = "i16" if should_be_signed else "u16" + elif burger_type == "int": + field_type_rs = "i32" if should_be_signed else "u32" + elif burger_type == "long": + field_type_rs = "i64" if should_be_signed else "u64" + elif burger_type == "float": + field_type_rs = "f32" + elif burger_type == "double": + field_type_rs = "f64" - elif burger_type == 'varint': + elif burger_type == "varint": is_var = True - field_type_rs = 'i32' if should_be_signed else 'u32' - elif burger_type == 'varlong': + field_type_rs = "i32" if should_be_signed else "u32" + elif burger_type == "varlong": is_var = True - field_type_rs = 'i64' if should_be_signed else 'u64' + field_type_rs = "i64" if should_be_signed else "u64" - elif burger_type == 'boolean': - field_type_rs = 'bool' - elif burger_type == 'string': - field_type_rs = 'String' + elif burger_type == "boolean": + field_type_rs = "bool" + elif burger_type == "string": + field_type_rs = "String" - elif burger_type == 'chatcomponent': - field_type_rs = 'FormattedText' - uses.add('azalea_chat::FormattedText') - elif burger_type == 'identifier': - field_type_rs = 'ResourceLocation' - uses.add('azalea_core::resource_location::ResourceLocation') - elif burger_type == 'uuid': - field_type_rs = 'Uuid' - uses.add('uuid::Uuid') - elif burger_type == 'position': - field_type_rs = 'BlockPos' - uses.add('azalea_core::position::BlockPos') - elif burger_type == 'nbtcompound': - field_type_rs = 'simdnbt::owned::NbtCompound' - elif burger_type == 'itemstack': - field_type_rs = 'Slot' - uses.add('azalea_core::slot::Slot') - elif burger_type == 'metadata': - field_type_rs = 'EntityMetadata' - uses.add('azalea_entity::EntityMetadata') - elif burger_type == 'bitset': + elif burger_type == "chatcomponent": + field_type_rs = "FormattedText" + uses.add("azalea_chat::FormattedText") + elif burger_type == "identifier": + field_type_rs = "ResourceLocation" + uses.add("azalea_core::resource_location::ResourceLocation") + elif burger_type == "uuid": + field_type_rs = "Uuid" + uses.add("uuid::Uuid") + elif burger_type == "position": + field_type_rs = "BlockPos" + uses.add("azalea_core::position::BlockPos") + elif burger_type == "nbtcompound": + field_type_rs = "simdnbt::owned::NbtCompound" + elif burger_type == "itemstack": + field_type_rs = "Slot" + uses.add("azalea_core::slot::Slot") + elif burger_type == "metadata": + field_type_rs = "EntityMetadata" + uses.add("azalea_entity::EntityMetadata") + elif burger_type == "bitset": if instruction: - length = instruction['length'] + length = instruction["length"] field_type_rs = f'todo!("fixed bitset of length {length}")' else: field_type_rs = 'todo!("fixed bitset")' - elif burger_type == 'abstract': - field_type_rs = 'todo!()' - elif burger_type == 'interface': + elif burger_type == "abstract": + field_type_rs = "todo!()" + elif burger_type == "interface": # depends on context - field_type_rs = 'todo!()' - elif burger_type == 'Iterator': - field_type_rs = 'todo!()' - elif burger_type == 'Object': + field_type_rs = "todo!()" + elif burger_type == "Iterator": + field_type_rs = "todo!()" + elif burger_type == "Object": # depends on context - field_type_rs = 'todo!()' - elif burger_type == 'enum': + field_type_rs = "todo!()" + elif burger_type == "enum": if not instruction or not mappings or not obfuscated_class_name: field_type_rs = 'todo!("enum")' else: # generate the whole enum :) print(instruction) - enum_field = instruction['field'] + enum_field = instruction["field"] # enums with a.b() as the field - if '.' in enum_field: + if "." in enum_field: enum_first_part_name = mappings.get_field_type( - obfuscated_class_name, enum_field.split('.')[0]) - enum_first_part_obfuscated_name = mappings.get_class_from_deobfuscated_name( - enum_first_part_name) - print('enum_first_part_obfuscated_name', - enum_first_part_obfuscated_name) - print('enum field', enum_field.split('.')[1].split('(')[0]) + obfuscated_class_name, enum_field.split(".")[0] + ) + enum_first_part_obfuscated_name = ( + mappings.get_class_from_deobfuscated_name(enum_first_part_name) + ) + print( + "enum_first_part_obfuscated_name", enum_first_part_obfuscated_name + ) + print("enum field", enum_field.split(".")[1].split("(")[0]) try: enum_name = mappings.get_method_type( - enum_first_part_obfuscated_name, enum_field.split('.')[1].split('(')[0], '') + enum_first_part_obfuscated_name, + enum_field.split(".")[1].split("(")[0], + "", + ) except KeyError: # sometimes enums are fields instead of methods enum_name = mappings.get_field_type( - enum_first_part_obfuscated_name, enum_field.split('.')[1].split('(')[0]) + enum_first_part_obfuscated_name, + enum_field.split(".")[1].split("(")[0], + ) - print('hm', enum_name) + print("hm", enum_name) else: try: enum_name = mappings.get_field_type( - obfuscated_class_name, enum_field) - except: + obfuscated_class_name, enum_field + ) + except Exception: enum_name = mappings.get_class(obfuscated_class_name) - print(f'failed getting {obfuscated_class_name}.{enum_field} but continuing with {enum_name} anyways') - print('enum_name', enum_name) - enum_obfuscated_name = mappings.get_class_from_deobfuscated_name( - enum_name) - print('enum_obfuscated_name', enum_obfuscated_name) + print( + f"failed getting {obfuscated_class_name}.{enum_field} but continuing with {enum_name} anyways" + ) + print("enum_name", enum_name) + enum_obfuscated_name = mappings.get_class_from_deobfuscated_name(enum_name) + print("enum_obfuscated_name", enum_obfuscated_name) enum_variants = [] for obfuscated_field_name in mappings.fields[enum_obfuscated_name]: field_name = mappings.get_field( - enum_obfuscated_name, obfuscated_field_name) + enum_obfuscated_name, obfuscated_field_name + ) # get the type just to make sure it's actually a variant and not something else field_type = mappings.get_field_type( - enum_obfuscated_name, obfuscated_field_name) + enum_obfuscated_name, obfuscated_field_name + ) if field_type != enum_name: continue enum_variants.append(field_name) - field_type_rs = to_camel_case( - enum_name.split('.')[-1].split('$')[-1]) - extra_code.append('') - extra_code.append(f'#[derive(AzBuf, Clone, Copy, Debug)]') - extra_code.append(f'pub enum {field_type_rs} {{') + field_type_rs = to_camel_case(enum_name.split(".")[-1].split("$")[-1]) + extra_code.append("") + extra_code.append("#[derive(AzBuf, Clone, Copy, Debug)]") + extra_code.append(f"pub enum {field_type_rs} {{") for index, variant in enumerate(enum_variants): - extra_code.append( - f' {to_camel_case(variant.lower())}={index},') - extra_code.append('}') + extra_code.append(f" {to_camel_case(variant.lower())}={index},") + extra_code.append("}") - elif burger_type.endswith('[]'): + elif burger_type.endswith("[]"): field_type_rs, is_var, uses, extra_code = burger_type_to_rust_type( - burger_type[:-2]) - field_type_rs = f'Vec<{field_type_rs}>' + burger_type[:-2] + ) + field_type_rs = f"Vec<{field_type_rs}>" # sometimes burger gives us a slightly incorrect type if mappings and instruction: - if field_type_rs == 'Vec': - field = instruction['field'] - if field.endswith('.copy()'): + if field_type_rs == "Vec": + field = instruction["field"] + if field.endswith(".copy()"): field = field[:-7] try: - array_type = mappings.get_field_type( - obfuscated_class_name, field) + array_type = mappings.get_field_type(obfuscated_class_name, field) except KeyError: - print('Error getting array type', field) + print("Error getting array type", field) return field_type_rs, is_var, uses, extra_code - if array_type == 'net.minecraft.network.FriendlyByteBuf': - field_type_rs = 'UnsizedByteArray' - uses.add('azalea_buf::UnsizedByteArray') + if array_type == "net.minecraft.network.FriendlyByteBuf": + field_type_rs = "UnsizedByteArray" + uses.add("azalea_buf::UnsizedByteArray") else: - print('instruction that we errored on:', instruction) - deobfuscated_class_name = mappings.get_class(obfuscated_class_name) if obfuscated_class_name else None - raise Exception(f'Unknown field type: {burger_type} ({deobfuscated_class_name or obfuscated_class_name})') + print("instruction that we errored on:", instruction) + deobfuscated_class_name = ( + mappings.get_class(obfuscated_class_name) if obfuscated_class_name else None + ) + raise Exception( + f"Unknown field type: {burger_type} ({deobfuscated_class_name or obfuscated_class_name})" + ) return field_type_rs, is_var, uses, extra_code def write_packet_file(state, packet_module_name, code): - with open(get_dir_location(f'../azalea-protocol/src/packets/{state}/{packet_module_name}.rs'), 'w') as f: + with open( + get_dir_location( + f"../azalea-protocol/src/packets/{state}/{packet_module_name}.rs" + ), + "w", + ) as f: f.write(code) def fmt(): - os.system(f'cd {get_dir_location("..")} && cargo fmt') + os.system(f"cd {get_dir_location('..')} && cargo fmt") diff --git a/codegen/lib/code/version.py b/codegen/lib/code/version.py index d4a37232..478c12b2 100644 --- a/codegen/lib/code/version.py +++ b/codegen/lib/code/version.py @@ -2,12 +2,12 @@ from lib.utils import get_dir_location import re import os -README_DIR = get_dir_location('../README.md') -VERSION_REGEX = r'\_Currently supported Minecraft version: `(.*)`.\_' +README_DIR = get_dir_location("../README.md") +VERSION_REGEX = r"\_Currently supported Minecraft version: `(.*)`.\_" def get_version_id() -> str: - with open(README_DIR, 'rb') as f: + with open(README_DIR, "rb") as f: readme_text = f.read().decode() version_line_match = re.search(VERSION_REGEX, readme_text) @@ -15,80 +15,85 @@ def get_version_id() -> str: version_id = version_line_match.group(1) return version_id else: - raise Exception('Could not find version id in README.md') + raise Exception("Could not find version id in README.md") def set_version_id(version_id: str) -> None: - with open(README_DIR, 'rb') as f: + with open(README_DIR, "rb") as f: readme_text = f.read().decode() version_line_match = re.search(VERSION_REGEX, readme_text) if version_line_match: - readme_text = readme_text.replace( - version_line_match.group(1), version_id) + readme_text = readme_text.replace(version_line_match.group(1), version_id) else: - raise Exception('Could not find version id in README.md') + raise Exception("Could not find version id in README.md") - with open(README_DIR, 'wb') as f: + with open(README_DIR, "wb") as f: f.write(readme_text.encode()) - + # update the version in all Cargo.toml files # version = "0.10.3+mc1.21.1" - for root, _, files in os.walk(get_dir_location('..')): + for root, _, files in os.walk(get_dir_location("..")): for file in files: - if file == 'Cargo.toml': - with open(os.path.join(root, file), 'r') as f: + if file == "Cargo.toml": + with open(os.path.join(root, file), "r") as f: cargo_toml = f.read().splitlines() for i, line in enumerate(cargo_toml): - if line.strip().startswith('version = '): + if line.strip().startswith("version = "): replaced = re.sub(r'\+mc[^"]+?"', f'+mc{version_id}"', line) cargo_toml[i] = replaced break else: # didn't have a version line continue - if cargo_toml[-1] != '': + if cargo_toml[-1] != "": # make sure there's always a trailing newline - cargo_toml.append('') - with open(os.path.join(root, file), 'w') as f: - f.write('\n'.join(cargo_toml)) - print('Updated version in README.md and Cargo.toml files') + cargo_toml.append("") + with open(os.path.join(root, file), "w") as f: + f.write("\n".join(cargo_toml)) + print("Updated version in README.md and Cargo.toml files") + def get_protocol_version() -> str: # azalea-protocol/src/packets/mod.rs # pub const PROTOCOL_VERSION: i32 = 758; - with open(get_dir_location('../azalea-protocol/src/packets/mod.rs'), 'r') as f: + with open(get_dir_location("../azalea-protocol/src/packets/mod.rs"), "r") as f: mod_rs = f.read().splitlines() for line in mod_rs: - if line.strip().startswith('pub const PROTOCOL_VERSION'): - return line.strip().split(' ')[-1].strip(';') + if line.strip().startswith("pub const PROTOCOL_VERSION"): + return line.strip().split(" ")[-1].strip(";") raise Exception( - 'Could not find protocol version in azalea-protocol/src/packets/mod.rs') + "Could not find protocol version in azalea-protocol/src/packets/mod.rs" + ) def set_protocol_version(protocol_version: str) -> None: - with open(get_dir_location('../azalea-protocol/src/packets/mod.rs'), 'r') as f: + with open(get_dir_location("../azalea-protocol/src/packets/mod.rs"), "r") as f: mod_rs = f.read().splitlines() for i, line in enumerate(mod_rs): - if line.strip().startswith('pub const PROTOCOL_VERSION:'): - mod_rs[i] = f'pub const PROTOCOL_VERSION: i32 = {protocol_version};' + if line.strip().startswith("pub const PROTOCOL_VERSION:"): + mod_rs[i] = f"pub const PROTOCOL_VERSION: i32 = {protocol_version};" break else: raise Exception( - 'Could not find protocol version in azalea-protocol/src/packets/mod.rs') + "Could not find protocol version in azalea-protocol/src/packets/mod.rs" + ) + + with open(get_dir_location("../azalea-protocol/src/packets/mod.rs"), "w") as f: + f.write("\n".join(mod_rs)) + - with open(get_dir_location('../azalea-protocol/src/packets/mod.rs'), 'w') as f: - f.write('\n'.join(mod_rs)) def set_version_name(version_name: str) -> None: - with open(get_dir_location('../azalea-protocol/src/packets/mod.rs'), 'r') as f: + with open(get_dir_location("../azalea-protocol/src/packets/mod.rs"), "r") as f: mod_rs = f.read().splitlines() for i, line in enumerate(mod_rs): - if line.strip().startswith('pub const VERSION_NAME:'): + if line.strip().startswith("pub const VERSION_NAME:"): mod_rs[i] = f'pub const VERSION_NAME: &str = "{version_name}";' break else: raise Exception( - 'Could not find version name in azalea-protocol/src/packets/mod.rs') + "Could not find version name in azalea-protocol/src/packets/mod.rs" + ) - with open(get_dir_location('../azalea-protocol/src/packets/mod.rs'), 'w') as f: - f.write('\n'.join(mod_rs)) + with open(get_dir_location("../azalea-protocol/src/packets/mod.rs"), "w") as f: + f.write("\n".join(mod_rs)) diff --git a/codegen/lib/download.py b/codegen/lib/download.py index 17b5aa6e..a557b523 100644 --- a/codegen/lib/download.py +++ b/codegen/lib/download.py @@ -6,29 +6,33 @@ import json import os # make sure the cache directory exists -print('Making __cache__') -if not os.path.exists(get_dir_location('__cache__')): - print('Made __cache__ directory.', get_dir_location('__cache__')) - os.mkdir(get_dir_location('__cache__')) +print("Making __cache__") +if not os.path.exists(get_dir_location("__cache__")): + print("Made __cache__ directory.", get_dir_location("__cache__")) + os.mkdir(get_dir_location("__cache__")) def get_burger(): - if not os.path.exists(get_dir_location('__cache__/Burger')): - print('\033[92mDownloading Burger...\033[m') + if not os.path.exists(get_dir_location("__cache__/Burger")): + print("\033[92mDownloading Burger...\033[m") os.system( - f'cd {get_dir_location("__cache__")} && git clone https://github.com/mat-1/Burger && cd Burger && git pull') + f"cd {get_dir_location('__cache__')} && git clone https://github.com/mat-1/Burger && cd Burger && git pull" + ) - print('\033[92mInstalling dependencies...\033[m') - os.system(f'cd {get_dir_location("__cache__")}/Burger && python -m venv venv && venv/bin/pip install six jawa') + print("\033[92mInstalling dependencies...\033[m") + os.system( + f"cd {get_dir_location('__cache__')}/Burger && python -m venv venv && venv/bin/pip install six jawa" + ) def get_pumpkin_extractor(): - if not os.path.exists(get_dir_location('__cache__/pumpkin-extractor')): - print('\033[92mDownloading Pumpkin-MC/Extractor...\033[m') + if not os.path.exists(get_dir_location("__cache__/pumpkin-extractor")): + print("\033[92mDownloading Pumpkin-MC/Extractor...\033[m") os.system( - f'cd {get_dir_location("__cache__")} && git clone https://github.com/Pumpkin-MC/Extractor pumpkin-extractor && cd pumpkin-extractor && git pull') - - GIT_PATCH = '''diff --git a/src/main/kotlin/de/snowii/extractor/extractors/Blocks.kt b/src/main/kotlin/de/snowii/extractor/extractors/Blocks.kt + f"cd {get_dir_location('__cache__')} && git clone https://github.com/Pumpkin-MC/Extractor pumpkin-extractor && cd pumpkin-extractor && git pull" + ) + + GIT_PATCH = """diff --git a/src/main/kotlin/de/snowii/extractor/extractors/Blocks.kt b/src/main/kotlin/de/snowii/extractor/extractors/Blocks.kt index 936cd7b..9876a4b 100644 --- a/src/main/kotlin/de/snowii/extractor/extractors/Blocks.kt +++ b/src/main/kotlin/de/snowii/extractor/extractors/Blocks.kt @@ -51,179 +55,184 @@ index 936cd7b..9876a4b 100644 for (blockEntity in Registries.BLOCK_ENTITY_TYPE) { if (blockEntity.supports(state)) { -''' +""" os.system( - f'cd {get_dir_location("__cache__")}/pumpkin-extractor && git apply - <', command) + print(">", command) for _ in range(10): - p = subprocess.Popen( - command, - stderr=subprocess.PIPE, - shell=True - ) + p = subprocess.Popen(command, stderr=subprocess.PIPE, shell=True) - stderr = b'' + stderr = b"" while True: data = p.stderr.read() - if data == b'': + if data == b"": break - print(data.decode(), end='', flush=True) + print(data.decode(), end="", flush=True) stderr += data regex_match = re.search( - r'ModuleNotFoundError: No module named \'(\w+?)\'', stderr.decode()) + r"ModuleNotFoundError: No module named \'(\w+?)\'", stderr.decode() + ) if not regex_match: out, err = p.communicate() if out: @@ -86,98 +98,99 @@ def run_python_command_and_download_deps(command): print(err) break missing_lib = regex_match.group(1) - print('Missing required lib:', missing_lib) - subprocess.run(f'venv/bin/pip install {missing_lib}', cwd=os.path.dirname(os.path.dirname(__file__))) - print('ok') + print("Missing required lib:", missing_lib) + subprocess.run( + f"venv/bin/pip install {missing_lib}", + cwd=os.path.dirname(os.path.dirname(__file__)), + ) + print("ok") def get_burger_data_for_version(version_id: str): - if not os.path.exists(get_dir_location(f'__cache__/burger-{version_id}.json')): + if not os.path.exists(get_dir_location(f"__cache__/burger-{version_id}.json")): get_burger() get_client_jar(version_id) get_mappings_for_version(version_id) - print('\033[92mRunning Burger...\033[m') + print("\033[92mRunning Burger...\033[m") run_python_command_and_download_deps( - f'cd {get_dir_location("__cache__/Burger")} && '\ - f'venv/bin/python munch.py {get_dir_location("__cache__")}/client-{version_id}.jar '\ - f'--output {get_dir_location("__cache__")}/burger-{version_id}.json '\ - f'--mappings {get_dir_location("__cache__")}/mappings-{version_id}.txt' + f"cd {get_dir_location('__cache__/Burger')} && " + f"venv/bin/python munch.py {get_dir_location('__cache__')}/client-{version_id}.jar " + f"--output {get_dir_location('__cache__')}/burger-{version_id}.json " + f"--mappings {get_dir_location('__cache__')}/mappings-{version_id}.txt" ) - with open(get_dir_location(f'__cache__/burger-{version_id}.json'), 'r') as f: + with open(get_dir_location(f"__cache__/burger-{version_id}.json"), "r") as f: return json.load(f) def get_pumpkin_data(version_id: str, category: str): - assert '/' not in version_id - assert '\\' not in version_id - target_parent_dir = get_dir_location(f'__cache__/pumpkin-{version_id}') - category_dir = f'{target_parent_dir}/{category}.json' + assert "/" not in version_id + assert "\\" not in version_id + target_parent_dir = get_dir_location(f"__cache__/pumpkin-{version_id}") + category_dir = f"{target_parent_dir}/{category}.json" if os.path.exists(category_dir): - with open(category_dir, 'r') as f: + with open(category_dir, "r") as f: return json.load(f) pumpkin_dir = get_pumpkin_extractor() - pumpkin_run_directory = f'{pumpkin_dir}/run' + pumpkin_run_directory = f"{pumpkin_dir}/run" if os.path.exists(pumpkin_run_directory): shutil.rmtree(pumpkin_run_directory) os.makedirs(pumpkin_run_directory, exist_ok=True) - with open(f'{pumpkin_run_directory}/eula.txt', 'w') as f: - f.write('eula=true') - with open(f'{pumpkin_run_directory}/server.properties', 'w') as f: - f.write('server-port=0') + with open(f"{pumpkin_run_directory}/eula.txt", "w") as f: + f.write("eula=true") + with open(f"{pumpkin_run_directory}/server.properties", "w") as f: + f.write("server-port=0") fabric_data = get_fabric_data(version_id)[0] fabric_api_version = get_latest_fabric_api_version() - gradle_properties = f'''# Done to increase the memory available to gradle. + gradle_properties = f"""# Done to increase the memory available to gradle. org.gradle.jvmargs=-Xmx1G org.gradle.parallel=true # Fabric Properties # check these on https://modmuss50.me/fabric.html minecraft_version={version_id} -yarn_mappings={fabric_data['mappings']['version']} -loader_version={fabric_data['loader']['version']} +yarn_mappings={fabric_data["mappings"]["version"]} +loader_version={fabric_data["loader"]["version"]} kotlin_loader_version=1.13.2+kotlin.2.1.20 # Mod Properties mod_version=1.0-SNAPSHOT maven_group=de.snowii archives_base_name=extractor fabric_version={fabric_api_version} -''' - with open(f'{pumpkin_dir}/gradle.properties', 'w') as f: +""" + with open(f"{pumpkin_dir}/gradle.properties", "w") as f: f.write(gradle_properties) # update the minecraft version dependency in src/main/resources/fabric.mod.json - fabric_mod_json_path = f'{pumpkin_dir}/src/main/resources/fabric.mod.json' - with open(fabric_mod_json_path, 'r') as f: + fabric_mod_json_path = f"{pumpkin_dir}/src/main/resources/fabric.mod.json" + with open(fabric_mod_json_path, "r") as f: fabric_mod_json = f.read() - with open(fabric_mod_json_path, 'w') as f: + with open(fabric_mod_json_path, "w") as f: fabric_mod_json = fabric_mod_json.replace( - '"minecraft": "${minecraft_version}"', - f'"minecraft": "*"' + '"minecraft": "${minecraft_version}"', '"minecraft": "*"' ) f.write(fabric_mod_json) - # run ./gradlew runServer until it logs "(pumpkin_extractor) Done" p = subprocess.Popen( - f'cd {pumpkin_dir} && ./gradlew clean && ./gradlew runServer', + f"cd {pumpkin_dir} && ./gradlew clean && ./gradlew runServer", stderr=subprocess.PIPE, stdout=subprocess.PIPE, - shell=True + shell=True, ) while True: data = p.stdout.readline().decode() - print('>' + data, end='', flush=True) - if '[Server thread/INFO] (pumpkin_extractor) Done' in data: - print('Pumpkin extractor done') + print(">" + data, end="", flush=True) + if "[Server thread/INFO] (pumpkin_extractor) Done" in data: + print("Pumpkin extractor done") break - if data == '': + if data == b"": break p.terminate() @@ -186,44 +199,49 @@ fabric_version={fabric_api_version} # delete target_parent_dir if it's empty if os.path.exists(target_parent_dir): os.rmdir(target_parent_dir) - os.rename(f'{pumpkin_dir}/run/pumpkin_extractor_output', target_parent_dir) + os.rename(f"{pumpkin_dir}/run/pumpkin_extractor_output", target_parent_dir) - with open(category_dir, 'r') as f: + with open(category_dir, "r") as f: return json.load(f) def get_file_from_jar(version_id: str, file_dir: str): get_client_jar(version_id) - with ZipFile(get_dir_location(f'__cache__/client-{version_id}.jar')) as z: + with ZipFile(get_dir_location(f"__cache__/client-{version_id}.jar")) as z: with z.open(file_dir) as f: return f.read() def get_en_us_lang(version_id: str): - return json.loads( - get_file_from_jar(version_id, 'assets/minecraft/lang/en_us.json') - ) + return json.loads(get_file_from_jar(version_id, "assets/minecraft/lang/en_us.json")) + # burger packet id extraction is broken since 1.20.5 (always returns -1, so we have to determine packet id ourselves from the mappings). # this is very much not ideal. -if TYPE_CHECKING: from codegen.lib.mappings import Mappings + def get_packet_list(version_id: str): - if version_id != '1.21': + if version_id != "1.21": return [] generate_data_from_server_jar(version_id) - with open(get_dir_location(f'__cache__/generated-{version_id}/reports/packets.json'), 'r') as f: + with open( + get_dir_location(f"__cache__/generated-{version_id}/reports/packets.json"), "r" + ) as f: packets_report = json.load(f) packet_list = [] for state, state_value in packets_report.items(): for direction, direction_value in state_value.items(): for packet_resourcelocation, packet_value in direction_value.items(): - assert packet_resourcelocation.startswith('minecraft:') - packet_resourcelocation = upper_first_letter(to_camel_case(packet_resourcelocation[len('minecraft:'):])) - packet_list.append({ - 'state': state, - 'direction': direction, - 'name': packet_resourcelocation, - 'id': packet_value['protocol_id'] - }) + assert packet_resourcelocation.startswith("minecraft:") + packet_resourcelocation = upper_first_letter( + to_camel_case(packet_resourcelocation[len("minecraft:") :]) + ) + packet_list.append( + { + "state": state, + "direction": direction, + "name": packet_resourcelocation, + "id": packet_value["protocol_id"], + } + ) diff --git a/rustfmt.toml b/rustfmt.toml index 35672d06..a2e8b69f 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,2 +1,4 @@ wrap_comments = true group_imports = "StdExternalCrate" +imports_granularity = "Crate" +format_code_in_doc_comments = true