aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <github@matdoes.dev>2022-04-24 17:37:57 -0500
committermat <github@matdoes.dev>2022-04-24 17:37:57 -0500
commit3e507f0db4020eaf406ba69aae3d4dc1301d29ac (patch)
treeca6c127c9db6dfd14511e98944fc031fe5f1e43a
parent9f576c5600ba9a244bc0d433bb7de174284066a2 (diff)
parentb7641ff308aab7840d2a2253ae50f8ee496b2a97 (diff)
downloadazalea-drasl-3e507f0db4020eaf406ba69aae3d4dc1301d29ac.tar.xz
Merge branch 'main' into auth
-rwxr-xr-x[-rw-r--r--].gitignore1
-rwxr-xr-x[-rw-r--r--].gitpod.yml0
-rwxr-xr-x[-rw-r--r--].vscode/settings.json0
-rwxr-xr-x[-rw-r--r--]Cargo.lock21
-rwxr-xr-x[-rw-r--r--]Cargo.toml1
-rwxr-xr-x[-rw-r--r--]README.md6
-rwxr-xr-x[-rw-r--r--]azalea-auth/Cargo.toml0
-rwxr-xr-x[-rw-r--r--]azalea-auth/src/game_profile.rs0
-rwxr-xr-x[-rw-r--r--]azalea-auth/src/lib.rs0
-rwxr-xr-xazalea-brigadier/Cargo.toml8
-rwxr-xr-xazalea-brigadier/README.md3
-rwxr-xr-xazalea-brigadier/src/arguments/argument_type.rs7
-rwxr-xr-xazalea-brigadier/src/arguments/integer_argument_type.rs54
-rwxr-xr-xazalea-brigadier/src/arguments/mod.rs4
-rwxr-xr-xazalea-brigadier/src/builder/argument_builder.rs137
-rwxr-xr-xazalea-brigadier/src/builder/literal_argument_builder.rs24
-rwxr-xr-xazalea-brigadier/src/builder/mod.rs3
-rwxr-xr-xazalea-brigadier/src/builder/required_argument_builder.rs45
-rwxr-xr-xazalea-brigadier/src/command_dispatcher.rs298
-rwxr-xr-xazalea-brigadier/src/context/command_context.rs80
-rwxr-xr-xazalea-brigadier/src/context/command_context_builder.rs116
-rwxr-xr-xazalea-brigadier/src/context/mod.rs11
-rwxr-xr-xazalea-brigadier/src/context/parsed_argument.rs8
-rwxr-xr-xazalea-brigadier/src/context/parsed_command_node.rs18
-rwxr-xr-xazalea-brigadier/src/context/string_range.rs45
-rwxr-xr-xazalea-brigadier/src/exceptions/builtin_exceptions.rs159
-rwxr-xr-xazalea-brigadier/src/exceptions/command_syntax_exception.rs91
-rwxr-xr-xazalea-brigadier/src/exceptions/mod.rs5
-rwxr-xr-xazalea-brigadier/src/lib.rs10
-rwxr-xr-xazalea-brigadier/src/message.rs14
-rwxr-xr-xazalea-brigadier/src/modifier.rs6
-rwxr-xr-xazalea-brigadier/src/parse_results.rs21
-rwxr-xr-xazalea-brigadier/src/string_reader.rs274
-rwxr-xr-xazalea-brigadier/src/tree/mod.rs259
-rwxr-xr-xazalea-brigadier/tests/arguments/bool_argument_type_test.rs0
-rwxr-xr-xazalea-brigadier/tests/arguments/double_argument_type_test.rs0
-rwxr-xr-xazalea-brigadier/tests/arguments/float_argument_type_test.rs0
-rwxr-xr-xazalea-brigadier/tests/arguments/integer_argument_type_test.rs0
-rwxr-xr-xazalea-brigadier/tests/arguments/long_argument_type_test.rs0
-rwxr-xr-xazalea-brigadier/tests/arguments/string_argument_type_test.rs0
-rwxr-xr-xazalea-brigadier/tests/builder/argument_builder_test.rs75
-rwxr-xr-xazalea-brigadier/tests/builder/literal_argument_builder_test.rs0
-rwxr-xr-xazalea-brigadier/tests/builder/required_argument_builder_test.rs0
-rwxr-xr-xazalea-brigadier/tests/command_dispatcher_test.rs410
-rwxr-xr-xazalea-brigadier/tests/command_dispatcher_usages_test.rs1
-rwxr-xr-xazalea-brigadier/tests/command_suggestions_test.rs1
-rwxr-xr-xazalea-brigadier/tests/context/command_context_test.rs0
-rwxr-xr-xazalea-brigadier/tests/context/parsed_argument_test.rs0
-rwxr-xr-xazalea-brigadier/tests/exceptions/dynamic_command_syntax_exception_type_test.rs0
-rwxr-xr-xazalea-brigadier/tests/exceptions/simple_command_syntax_exception_type_test.rs0
-rwxr-xr-xazalea-brigadier/tests/string_reader_test.rs612
-rwxr-xr-xazalea-brigadier/tests/suggestion/suggestion_test.rs0
-rwxr-xr-xazalea-brigadier/tests/suggestion/suggestions_builder_test.rs0
-rwxr-xr-xazalea-brigadier/tests/suggestion/suggestions_test.rs0
-rwxr-xr-xazalea-brigadier/tests/tree/abstract_command_node_test.rs0
-rwxr-xr-xazalea-brigadier/tests/tree/argument_command_node_test.rs0
-rwxr-xr-xazalea-brigadier/tests/tree/literal_command_node_test.rs0
-rwxr-xr-xazalea-brigadier/tests/tree/root_command_node_test.rs0
-rwxr-xr-x[-rw-r--r--]azalea-chat/Cargo.toml0
-rwxr-xr-xazalea-chat/README.md3
-rwxr-xr-x[-rw-r--r--]azalea-chat/src/base_component.rs0
-rwxr-xr-x[-rw-r--r--]azalea-chat/src/component.rs0
-rwxr-xr-x[-rw-r--r--]azalea-chat/src/events.rs0
-rwxr-xr-x[-rw-r--r--]azalea-chat/src/lib.rs0
-rwxr-xr-x[-rw-r--r--]azalea-chat/src/style.rs0
-rwxr-xr-x[-rw-r--r--]azalea-chat/src/text_component.rs0
-rwxr-xr-x[-rw-r--r--]azalea-chat/src/translatable_component.rs0
-rwxr-xr-x[-rw-r--r--]azalea-chat/tests/integration_test.rs0
-rwxr-xr-x[-rw-r--r--]azalea-client/Cargo.toml0
-rwxr-xr-xazalea-client/README.md3
-rwxr-xr-x[-rw-r--r--]azalea-client/src/connect.rs43
-rwxr-xr-x[-rw-r--r--]azalea-client/src/crypt.rs0
-rwxr-xr-x[-rw-r--r--]azalea-client/src/lib.rs0
-rwxr-xr-x[-rw-r--r--]azalea-client/src/ping.rs2
-rwxr-xr-x[-rw-r--r--]azalea-core/Cargo.toml0
-rwxr-xr-xazalea-core/src/difficulty.rs96
-rwxr-xr-x[-rw-r--r--]azalea-core/src/game_type.rs2
-rwxr-xr-x[-rw-r--r--]azalea-core/src/lib.rs10
-rwxr-xr-x[-rw-r--r--]azalea-core/src/resource_location.rs0
-rwxr-xr-x[-rw-r--r--]azalea-core/src/serializable_uuid.rs0
-rwxr-xr-x[-rw-r--r--]azalea-nbt/Cargo.toml5
-rwxr-xr-x[-rw-r--r--]azalea-nbt/README.md5
-rwxr-xr-x[-rw-r--r--]azalea-nbt/benches/my_benchmark.rs13
-rwxr-xr-x[-rw-r--r--]azalea-nbt/src/decode.rs35
-rwxr-xr-x[-rw-r--r--]azalea-nbt/src/encode.rs231
-rwxr-xr-x[-rw-r--r--]azalea-nbt/src/error.rs11
-rwxr-xr-x[-rw-r--r--]azalea-nbt/src/lib.rs0
-rwxr-xr-x[-rw-r--r--]azalea-nbt/src/tag.rs108
-rwxr-xr-x[-rw-r--r--]azalea-nbt/tests/bigtest.nbtbin507 -> 507 bytes
-rwxr-xr-x[-rw-r--r--]azalea-nbt/tests/complex_player.datbin1379 -> 1379 bytes
-rwxr-xr-x[-rw-r--r--]azalea-nbt/tests/hello_world.nbtbin33 -> 33 bytes
-rwxr-xr-x[-rw-r--r--]azalea-nbt/tests/inttest.nbtbin95 -> 95 bytes
-rwxr-xr-x[-rw-r--r--]azalea-nbt/tests/level.datbin1922 -> 1922 bytes
-rwxr-xr-x[-rw-r--r--]azalea-nbt/tests/simple_player.datbin440 -> 440 bytes
-rwxr-xr-x[-rw-r--r--]azalea-nbt/tests/stringtest.nbtbin251 -> 251 bytes
-rwxr-xr-x[-rw-r--r--]azalea-nbt/tests/tests.rs0
-rwxr-xr-x[-rw-r--r--]azalea-protocol/Cargo.toml4
-rwxr-xr-xazalea-protocol/README.md3
-rwxr-xr-xazalea-protocol/packet-macros/Cargo.toml13
-rwxr-xr-xazalea-protocol/packet-macros/src/lib.rs300
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/connect.rs0
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/lib.rs0
-rw-r--r--azalea-protocol/src/mc_buf.rs478
-rwxr-xr-xazalea-protocol/src/mc_buf/mod.rs231
-rwxr-xr-xazalea-protocol/src/mc_buf/read.rs459
-rwxr-xr-xazalea-protocol/src/mc_buf/write.rs341
-rwxr-xr-xazalea-protocol/src/packets/game/clientbound_change_difficulty_packet.rs8
-rwxr-xr-xazalea-protocol/src/packets/game/clientbound_custom_payload_packet.rs8
-rwxr-xr-xazalea-protocol/src/packets/game/clientbound_declare_commands_packet.rs96
-rw-r--r--azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs9
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/packets/game/clientbound_login_packet.rs107
-rwxr-xr-xazalea-protocol/src/packets/game/clientbound_player_abilities_packet.rs57
-rwxr-xr-xazalea-protocol/src/packets/game/clientbound_set_carried_item_packet.rs7
-rwxr-xr-xazalea-protocol/src/packets/game/clientbound_update_tags_packet.rs105
-rwxr-xr-xazalea-protocol/src/packets/game/clientbound_update_view_distance_packet.rs9
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/packets/game/mod.rs65
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/packets/handshake/client_intention_packet.rs42
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/packets/handshake/mod.rs51
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/packets/login/clientbound_custom_query_packet.rs32
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/packets/login/clientbound_game_profile_packet.rs3
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/packets/login/clientbound_hello_packet.rs2
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/packets/login/clientbound_login_compression_packet.rs3
-rw-r--r--azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs7
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/packets/login/mod.rs86
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/packets/login/serverbound_hello_packet.rs3
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/packets/mod.rs32
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/packets/status/clientbound_status_response_packet.rs6
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/packets/status/mod.rs71
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/packets/status/serverbound_status_request_packet.rs21
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/read.rs0
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/resolver.rs0
-rwxr-xr-x[-rw-r--r--]azalea-protocol/src/write.rs2
-rwxr-xr-x[-rw-r--r--]bot/Cargo.toml0
-rw-r--r--bot/src/main.rs11
134 files changed, 5063 insertions, 1004 deletions
diff --git a/.gitignore b/.gitignore
index ea8c4bf7..4990fff7 100644..100755
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
/target
+/doc
diff --git a/.gitpod.yml b/.gitpod.yml
index f6d0d2af..f6d0d2af 100644..100755
--- a/.gitpod.yml
+++ b/.gitpod.yml
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 3b614348..3b614348 100644..100755
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
diff --git a/Cargo.lock b/Cargo.lock
index c460835d..430c72a6 100644..100755
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -71,6 +71,10 @@ dependencies = [
]
[[package]]
+name = "azalea-brigadier"
+version = "0.1.0"
+
+[[package]]
name = "azalea-chat"
version = "0.1.0"
dependencies = [
@@ -116,11 +120,15 @@ dependencies = [
"async-recursion",
"async-trait",
"azalea-auth",
+ "azalea-brigadier",
"azalea-chat",
"azalea-core",
"azalea-nbt",
"byteorder",
"bytes",
+ "num-derive",
+ "num-traits",
+ "packet-macros",
"serde",
"serde_json",
"thiserror",
@@ -749,6 +757,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
+name = "packet-macros"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -827,9 +844,9 @@ checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]]
name = "proc-macro2"
-version = "1.0.32"
+version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
+checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
diff --git a/Cargo.toml b/Cargo.toml
index d22c326b..7f958207 100644..100755
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,4 +8,5 @@ members = [
"azalea-core",
"azalea-auth",
"azalea-nbt",
+ "azalea-brigadier",
]
diff --git a/README.md b/README.md
index 3ae4b307..ed6ed46f 100644..100755
--- a/README.md
+++ b/README.md
@@ -6,8 +6,8 @@ I named this Azalea because it sounds like a cool word and this is a cool librar
## Goals
-- Bypass most anticheats
-- Only support the latest Minecraft version
- Do everything a vanilla client can do
-- Be fast
- Be easy to use
+- Bypass most/all anticheats
+- Support the latest Minecraft version
+- Be fast
diff --git a/azalea-auth/Cargo.toml b/azalea-auth/Cargo.toml
index cb807776..cb807776 100644..100755
--- a/azalea-auth/Cargo.toml
+++ b/azalea-auth/Cargo.toml
diff --git a/azalea-auth/src/game_profile.rs b/azalea-auth/src/game_profile.rs
index d75f60a4..d75f60a4 100644..100755
--- a/azalea-auth/src/game_profile.rs
+++ b/azalea-auth/src/game_profile.rs
diff --git a/azalea-auth/src/lib.rs b/azalea-auth/src/lib.rs
index afe9eb33..afe9eb33 100644..100755
--- a/azalea-auth/src/lib.rs
+++ b/azalea-auth/src/lib.rs
diff --git a/azalea-brigadier/Cargo.toml b/azalea-brigadier/Cargo.toml
new file mode 100755
index 00000000..a7ebf618
--- /dev/null
+++ b/azalea-brigadier/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+edition = "2021"
+name = "azalea-brigadier"
+version = "0.1.0"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/azalea-brigadier/README.md b/azalea-brigadier/README.md
new file mode 100755
index 00000000..a7318566
--- /dev/null
+++ b/azalea-brigadier/README.md
@@ -0,0 +1,3 @@
+# Azalea Brigadier
+
+A Rust port of Mojang's [Brigadier](https://github.com/Mojang/brigadier) command parsing and dispatching library.
diff --git a/azalea-brigadier/src/arguments/argument_type.rs b/azalea-brigadier/src/arguments/argument_type.rs
new file mode 100755
index 00000000..029e4696
--- /dev/null
+++ b/azalea-brigadier/src/arguments/argument_type.rs
@@ -0,0 +1,7 @@
+use std::{any::Any, rc::Rc};
+
+use crate::{exceptions::CommandSyntaxException, string_reader::StringReader};
+
+pub trait ArgumentType {
+ fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException>;
+}
diff --git a/azalea-brigadier/src/arguments/integer_argument_type.rs b/azalea-brigadier/src/arguments/integer_argument_type.rs
new file mode 100755
index 00000000..336046a7
--- /dev/null
+++ b/azalea-brigadier/src/arguments/integer_argument_type.rs
@@ -0,0 +1,54 @@
+use std::{any::Any, rc::Rc};
+
+use crate::{
+ context::CommandContext,
+ exceptions::{BuiltInExceptions, CommandSyntaxException},
+ string_reader::StringReader,
+};
+
+use super::ArgumentType;
+
+#[derive(Default)]
+struct Integer {
+ pub minimum: Option<i32>,
+ pub maximum: Option<i32>,
+}
+
+impl ArgumentType for Integer {
+ fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> {
+ 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(maximum) = self.maximum {
+ if result > maximum {
+ reader.cursor = start;
+ return Err(BuiltInExceptions::IntegerTooBig {
+ found: result,
+ max: maximum,
+ }
+ .create_with_context(reader));
+ }
+ }
+ Ok(Rc::new(result))
+ }
+}
+
+pub fn integer() -> impl ArgumentType {
+ Integer::default()
+}
+pub fn get_integer<S>(context: &CommandContext<S>, name: &str) -> Option<i32> {
+ context
+ .argument(name)
+ .unwrap()
+ .downcast_ref::<i32>()
+ .copied()
+}
diff --git a/azalea-brigadier/src/arguments/mod.rs b/azalea-brigadier/src/arguments/mod.rs
new file mode 100755
index 00000000..dec39297
--- /dev/null
+++ b/azalea-brigadier/src/arguments/mod.rs
@@ -0,0 +1,4 @@
+mod argument_type;
+pub mod integer_argument_type;
+
+pub use argument_type::ArgumentType;
diff --git a/azalea-brigadier/src/builder/argument_builder.rs b/azalea-brigadier/src/builder/argument_builder.rs
new file mode 100755
index 00000000..d26b2a8a
--- /dev/null
+++ b/azalea-brigadier/src/builder/argument_builder.rs
@@ -0,0 +1,137 @@
+use crate::{context::CommandContext, modifier::RedirectModifier, tree::CommandNode};
+
+use super::{literal_argument_builder::Literal, required_argument_builder::Argument};
+use std::{cell::RefCell, fmt::Debug, rc::Rc};
+
+#[derive(Debug, Clone)]
+pub enum ArgumentBuilderType {
+ Literal(Literal),
+ Argument(Argument),
+}
+
+/// A node that hasn't yet been built.
+pub struct ArgumentBuilder<S> {
+ arguments: CommandNode<S>,
+
+ command: Option<Rc<dyn Fn(&CommandContext<S>) -> i32>>,
+ requirement: Rc<dyn Fn(Rc<S>) -> bool>,
+ target: Option<Rc<RefCell<CommandNode<S>>>>,
+
+ forks: bool,
+ modifier: Option<Rc<RedirectModifier<S>>>,
+}
+
+impl<S> Clone for ArgumentBuilder<S> {
+ fn clone(&self) -> Self {
+ Self {
+ arguments: self.arguments.clone(),
+ command: self.command.clone(),
+ requirement: self.requirement.clone(),
+ target: self.target.clone(),
+ forks: self.forks,
+ modifier: self.modifier.clone(),
+ }
+ }
+}
+
+/// A node that isn't yet built.
+impl<S> ArgumentBuilder<S> {
+ pub fn new(value: ArgumentBuilderType) -> Self {
+ Self {
+ arguments: CommandNode {
+ value,
+ ..Default::default()
+ },
+ command: None,
+ requirement: Rc::new(|_| true),
+ forks: false,
+ modifier: None,
+ target: None,
+ }
+ }
+
+ pub fn then(&mut self, argument: ArgumentBuilder<S>) -> Self {
+ self.then_built(argument.build())
+ }
+
+ pub fn then_built(&mut self, argument: CommandNode<S>) -> Self {
+ self.arguments.add_child(&Rc::new(RefCell::new(argument)));
+ self.clone()
+ }
+
+ pub fn executes<F>(&mut self, f: F) -> Self
+ where
+ F: Fn(&CommandContext<S>) -> i32 + 'static,
+ {
+ self.command = Some(Rc::new(f));
+ self.clone()
+ }
+
+ pub fn requires<F>(&mut self, requirement: F) -> Self
+ where
+ F: Fn(Rc<S>) -> bool + 'static,
+ {
+ self.requirement = Rc::new(requirement);
+ self.clone()
+ }
+
+ pub fn redirect(&mut self, target: Rc<RefCell<CommandNode<S>>>) -> Self {
+ self.forward(target, None, false)
+ }
+
+ pub fn fork(
+ &mut self,
+ target: Rc<RefCell<CommandNode<S>>>,
+ modifier: Rc<RedirectModifier<S>>,
+ ) -> Self {
+ self.forward(target, Some(modifier), true)
+ }
+
+ pub fn forward(
+ &mut self,
+ target: Rc<RefCell<CommandNode<S>>>,
+ modifier: Option<Rc<RedirectModifier<S>>>,
+ fork: bool,
+ ) -> Self {
+ if !self.arguments.children.is_empty() {
+ panic!("Cannot forward a node with children");
+ }
+ self.target = Some(target);
+ self.modifier = modifier;
+ self.forks = fork;
+ self.clone()
+ }
+
+ pub fn build(self) -> CommandNode<S> {
+ let mut result = CommandNode {
+ value: self.arguments.value,
+ command: self.command,
+ requirement: self.requirement,
+ redirect: self.target,
+ modifier: self.modifier,
+ forks: self.forks,
+ arguments: Default::default(),
+ children: Default::default(),
+ literals: Default::default(),
+ };
+
+ for argument in self.arguments.children.values() {
+ result.add_child(argument);
+ }
+
+ result
+ }
+}
+
+impl<S> Debug for ArgumentBuilder<S> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("ArgumentBuilder")
+ .field("arguments", &self.arguments)
+ // .field("command", &self.command)
+ // .field("requirement", &self.requirement)
+ .field("target", &self.target)
+ .field("forks", &self.forks)
+ // .field("modifier", &self.modifier)
+ .finish()
+ }
+}
diff --git a/azalea-brigadier/src/builder/literal_argument_builder.rs b/azalea-brigadier/src/builder/literal_argument_builder.rs
new file mode 100755
index 00000000..6627ffdc
--- /dev/null
+++ b/azalea-brigadier/src/builder/literal_argument_builder.rs
@@ -0,0 +1,24 @@
+use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType};
+
+#[derive(Debug, Clone, Default)]
+pub struct Literal {
+ pub value: String,
+}
+impl Literal {
+ pub fn new(value: &str) -> Self {
+ Self {
+ value: value.to_string(),
+ }
+ }
+}
+
+impl From<Literal> for ArgumentBuilderType {
+ fn from(literal: Literal) -> Self {
+ Self::Literal(literal)
+ }
+}
+
+/// Shortcut for creating a new literal builder node.
+pub fn literal<S>(value: &str) -> ArgumentBuilder<S> {
+ ArgumentBuilder::new(ArgumentBuilderType::Literal(Literal::new(value)))
+}
diff --git a/azalea-brigadier/src/builder/mod.rs b/azalea-brigadier/src/builder/mod.rs
new file mode 100755
index 00000000..26f2f644
--- /dev/null
+++ b/azalea-brigadier/src/builder/mod.rs
@@ -0,0 +1,3 @@
+pub mod argument_builder;
+pub mod literal_argument_builder;
+pub mod required_argument_builder;
diff --git a/azalea-brigadier/src/builder/required_argument_builder.rs b/azalea-brigadier/src/builder/required_argument_builder.rs
new file mode 100755
index 00000000..9d4d9e0a
--- /dev/null
+++ b/azalea-brigadier/src/builder/required_argument_builder.rs
@@ -0,0 +1,45 @@
+use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType};
+use crate::{
+ arguments::ArgumentType, exceptions::CommandSyntaxException, string_reader::StringReader,
+};
+use std::{any::Any, fmt::Debug, rc::Rc};
+
+/// An argument node type. The `T` type parameter is the type of the argument,
+/// which can be anything.
+#[derive(Clone)]
+pub struct Argument {
+ pub name: String,
+ parser: Rc<dyn ArgumentType>,
+}
+impl Argument {
+ pub fn new(name: &str, parser: Rc<dyn ArgumentType>) -> Self {
+ Self {
+ name: name.to_string(),
+ parser,
+ }
+ }
+
+ pub fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> {
+ self.parser.parse(reader)
+ }
+}
+
+impl From<Argument> 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 {
+ f.debug_struct("Argument")
+ .field("name", &self.name)
+ // .field("parser", &self.parser)
+ .finish()
+ }
+}
+
+/// Shortcut for creating a new argument builder node.
+pub fn argument<S>(name: &str, parser: impl ArgumentType + 'static) -> ArgumentBuilder<S> {
+ ArgumentBuilder::new(Argument::new(name, Rc::new(parser)).into())
+}
diff --git a/azalea-brigadier/src/command_dispatcher.rs b/azalea-brigadier/src/command_dispatcher.rs
new file mode 100755
index 00000000..eab42dd8
--- /dev/null
+++ b/azalea-brigadier/src/command_dispatcher.rs
@@ -0,0 +1,298 @@
+use crate::{
+ builder::argument_builder::ArgumentBuilder,
+ context::{CommandContext, CommandContextBuilder},
+ exceptions::{BuiltInExceptions, CommandSyntaxException},
+ parse_results::ParseResults,
+ string_reader::StringReader,
+ tree::CommandNode,
+};
+use std::{cell::RefCell, cmp::Ordering, collections::HashMap, marker::PhantomData, mem, rc::Rc};
+
+#[derive(Default)]
+pub struct CommandDispatcher<S> {
+ pub root: Rc<RefCell<CommandNode<S>>>,
+ _marker: PhantomData<S>,
+}
+
+impl<S> CommandDispatcher<S> {
+ pub fn new() -> Self {
+ Self {
+ root: Rc::new(RefCell::new(CommandNode::default())),
+ _marker: PhantomData,
+ }
+ }
+
+ pub fn register(&mut self, node: ArgumentBuilder<S>) -> Rc<RefCell<CommandNode<S>>> {
+ let build = Rc::new(RefCell::new(node.build()));
+ self.root.borrow_mut().add_child(&build);
+ build
+ }
+
+ pub fn parse(&self, command: StringReader, source: Rc<S>) -> ParseResults<S> {
+ let context = CommandContextBuilder::new(
+ Rc::new(self.clone()),
+ source,
+ self.root.clone(),
+ command.cursor(),
+ );
+ self.parse_nodes(&self.root, &command, context).unwrap()
+ }
+
+ fn parse_nodes(
+ &self,
+ node: &Rc<RefCell<CommandNode<S>>>,
+ original_reader: &StringReader,
+ context_so_far: CommandContextBuilder<S>,
+ ) -> Result<ParseResults<S>, CommandSyntaxException> {
+ let source = context_so_far.source.clone();
+ let mut errors = HashMap::<Rc<CommandNode<S>>, CommandSyntaxException>::new();
+ let mut potentials: Vec<ParseResults<S>> = vec![];
+ let cursor = original_reader.cursor();
+
+ for child in node
+ .borrow()
+ .get_relevant_nodes(&mut original_reader.clone())
+ {
+ if !child.borrow().can_use(source.clone()) {
+ continue;
+ }
+ let mut context = context_so_far.clone();
+ let mut reader = original_reader.clone();
+
+ let parse_with_context_result =
+ child.borrow().parse_with_context(&mut reader, &mut context);
+ if let Err(ex) = parse_with_context_result {
+ errors.insert(
+ Rc::new((*child.borrow()).clone()),
+ BuiltInExceptions::DispatcherParseException {
+ message: ex.message(),
+ }
+ .create_with_context(&reader),
+ );
+ reader.cursor = cursor;
+ continue;
+ }
+ if reader.can_read() && reader.peek() != ' ' {
+ errors.insert(
+ Rc::new((*child.borrow()).clone()),
+ BuiltInExceptions::DispatcherExpectedArgumentSeparator
+ .create_with_context(&reader),
+ );
+ reader.cursor = cursor;
+ continue;
+ }
+
+ context.with_command(&child.borrow().command);
+ if reader.can_read_length(if child.borrow().redirect.is_none() {
+ 2
+ } else {
+ 1
+ }) {
+ reader.skip();
+ if let Some(redirect) = &child.borrow().redirect {
+ let child_context = CommandContextBuilder::new(
+ Rc::new(self.clone()),
+ source,
+ redirect.clone(),
+ reader.cursor,
+ );
+ let parse = self
+ .parse_nodes(redirect, &reader, child_context)
+ .expect("Parsing nodes failed");
+ context.with_child(Rc::new(parse.context));
+ return Ok(ParseResults {
+ context,
+ reader: parse.reader,
+ exceptions: parse.exceptions,
+ });
+ } else {
+ let parse = self
+ .parse_nodes(&child, &reader, context)
+ .expect("Parsing nodes failed");
+ potentials.push(parse);
+ }
+ } else {
+ potentials.push(ParseResults {
+ context,
+ reader,
+ exceptions: HashMap::new(),
+ });
+ }
+ }
+
+ if !potentials.is_empty() {
+ if potentials.len() > 1 {
+ potentials.sort_by(|a, b| {
+ if !a.reader.can_read() && b.reader.can_read() {
+ return Ordering::Less;
+ };
+ if a.reader.can_read() && !b.reader.can_read() {
+ return Ordering::Greater;
+ };
+ if a.exceptions.is_empty() && !b.exceptions.is_empty() {
+ return Ordering::Less;
+ };
+ if !a.exceptions.is_empty() && b.exceptions.is_empty() {
+ return Ordering::Greater;
+ };
+ Ordering::Equal
+ })
+ }
+ let best_potential = potentials.into_iter().next().unwrap();
+ return Ok(best_potential);
+ }
+
+ Ok(ParseResults {
+ context: context_so_far,
+ reader: original_reader.clone(),
+ exceptions: errors,
+ })
+ }
+
+ pub fn execute(
+ &self,
+ input: StringReader,
+ source: Rc<S>,
+ ) -> Result<i32, CommandSyntaxException> {
+ let parse = self.parse(input, source);
+ Self::execute_parsed(parse)
+ }
+
+ pub fn add_paths(
+ &self,
+ node: Rc<RefCell<CommandNode<S>>>,
+ result: &mut Vec<Vec<Rc<RefCell<CommandNode<S>>>>>,
+ parents: Vec<Rc<RefCell<CommandNode<S>>>>,
+ ) {
+ let mut current = parents;
+ current.push(node.clone());
+ result.push(current.clone());
+
+ for child in node.borrow().children.values() {
+ self.add_paths(child.clone(), result, current.clone());
+ }
+ }
+
+ pub fn get_path(&self, target: CommandNode<S>) -> Vec<String> {
+ let rc_target = Rc::new(RefCell::new(target));
+ let mut nodes: Vec<Vec<Rc<RefCell<CommandNode<S>>>>> = Vec::new();
+ self.add_paths(self.root.clone(), &mut nodes, vec![]);
+
+ for list in nodes {
+ if *list.last().expect("Nothing in list").borrow() == *rc_target.borrow() {
+ let mut result: Vec<String> = Vec::with_capacity(list.len());
+ for node in list {
+ if node != self.root {
+ result.push(node.borrow().name().to_string());
+ }
+ }
+ return result;
+ }
+ }
+ vec![]
+ }
+
+ pub fn find_node(&self, path: &[&str]) -> Option<Rc<RefCell<CommandNode<S>>>> {
+ let mut node = self.root.clone();
+ for name in path {
+ if let Some(child) = node.clone().borrow().child(name) {
+ node = child
+ } else {
+ return None;
+ }
+ }
+ Some(node)
+ }
+
+ /// Executes a given pre-parsed command.
+ pub fn execute_parsed(parse: ParseResults<S>) -> Result<i32, CommandSyntaxException> {
+ 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)
+ );
+ }
+ let mut result = 0i32;
+ let mut successful_forks = 0;
+ let mut forked = false;
+ let mut found_command = false;
+ let command = parse.reader.string();
+ let original = parse.context.build(command);
+ let mut contexts = vec![original];
+ let mut next: Vec<CommandContext<S>> = vec![];
+
+ while !contexts.is_empty() {
+ for context in contexts.iter() {
+ let child = &context.child;
+ if let Some(child) = child {
+ println!("aaaaaaa {:?}", child);
+ forked |= child.forks;
+ if child.has_nodes() {
+ found_command = true;
+ let modifier = &context.modifier;
+ if let Some(modifier) = modifier {
+ let results = modifier(context);
+ if let Ok(results) = results {
+ if !results.is_empty() {
+ next.extend(results.iter().map(|s| child.copy_for(s.clone())));
+ }
+ } else {
+ // 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 })
+ }
+}
+
+impl<S> Clone for CommandDispatcher<S> {
+ fn clone(&self) -> Self {
+ Self {
+ root: self.root.clone(),
+ _marker: PhantomData,
+ }
+ }
+}
diff --git a/azalea-brigadier/src/context/command_context.rs b/azalea-brigadier/src/context/command_context.rs
new file mode 100755
index 00000000..1834a73d
--- /dev/null
+++ b/azalea-brigadier/src/context/command_context.rs
@@ -0,0 +1,80 @@
+use super::{parsed_command_node::ParsedCommandNode, string_range::StringRange, ParsedArgument};
+use crate::{modifier::RedirectModifier, tree::CommandNode};
+use std::{any::Any, cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
+
+/// A built `CommandContextBuilder`.
+pub struct CommandContext<S> {
+ pub source: Rc<S>,
+ pub input: String,
+ pub arguments: HashMap<String, ParsedArgument>,
+ pub command: Option<Rc<dyn Fn(&CommandContext<S>) -> i32>>,
+ pub root_node: Rc<RefCell<CommandNode<S>>>,
+ pub nodes: Vec<ParsedCommandNode<S>>,
+ pub range: StringRange,
+ pub child: Option<Rc<CommandContext<S>>>,
+ pub modifier: Option<Rc<RedirectModifier<S>>>,
+ pub forks: bool,
+}
+
+impl<S> Clone for CommandContext<S> {
+ fn clone(&self) -> Self {
+ Self {
+ source: self.source.clone(),
+ input: self.input.clone(),
+ arguments: self.arguments.clone(),
+ command: self.command.clone(),
+ root_node: self.root_node.clone(),
+ nodes: self.nodes.clone(),
+ range: self.range.clone(),
+ child: self.child.clone(),
+ modifier: self.modifier.clone(),
+ forks: self.forks,
+ }
+ }
+}
+
+impl<S> Debug for CommandContext<S> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("CommandContext")
+ // .field("source", &self.source)
+ .field("input", &self.input)
+ // .field("arguments", &self.arguments)
+ // .field("command", &self.command)
+ // .field("root_node", &self.root_node)
+ // .field("nodes", &self.nodes)
+ .field("range", &self.range)
+ .field("child", &self.child)
+ // .field("modifier", &self.modifier)
+ .field("forks", &self.forks)
+ .finish()
+ }
+}
+
+impl<S> CommandContext<S> {
+ pub fn copy_for(&self, source: Rc<S>) -> Self {
+ if Rc::ptr_eq(&source, &self.source) {
+ return self.clone();
+ }
+ CommandContext {
+ source,
+ input: self.input.clone(),
+ arguments: self.arguments.clone(),
+ command: self.command.clone(),
+ root_node: self.root_node.clone(),
+ nodes: self.nodes.clone(),
+ range: self.range.clone(),
+ child: self.child.clone(),
+ modifier: self.modifier.clone(),
+ forks: self.forks,
+ }
+ }
+
+ pub fn has_nodes(&self) -> bool {
+ !self.nodes.is_empty()
+ }
+
+ pub fn argument(&self, name: &str) -> Option<Rc<dyn Any>> {
+ let argument = self.arguments.get(name);
+ argument.map(|a| a.result.clone())
+ }
+}
diff --git a/azalea-brigadier/src/context/command_context_builder.rs b/azalea-brigadier/src/context/command_context_builder.rs
new file mode 100755
index 00000000..f192f6b7
--- /dev/null
+++ b/azalea-brigadier/src/context/command_context_builder.rs
@@ -0,0 +1,116 @@
+use super::{
+ command_context::CommandContext, parsed_command_node::ParsedCommandNode,
+ string_range::StringRange, ParsedArgument,
+};
+use crate::{command_dispatcher::CommandDispatcher, modifier::RedirectModifier, tree::CommandNode};
+use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
+
+pub struct CommandContextBuilder<S> {
+ pub arguments: HashMap<String, ParsedArgument>,
+ pub root: Rc<RefCell<CommandNode<S>>>,
+ pub nodes: Vec<ParsedCommandNode<S>>,
+ pub dispatcher: Rc<CommandDispatcher<S>>,
+ pub source: Rc<S>,
+ pub command: Option<Rc<dyn Fn(&CommandContext<S>) -> i32>>,
+ pub child: Option<Rc<CommandContextBuilder<S>>>,
+ pub range: StringRange,
+ pub modifier: Option<Rc<RedirectModifier<S>>>,
+ pub forks: bool,
+}
+
+impl<S> Clone for CommandContextBuilder<S> {
+ fn clone(&self) -> Self {
+ Self {
+ arguments: self.arguments.clone(),
+ root: self.root.clone(),
+ nodes: self.nodes.clone(),
+ dispatcher: self.dispatcher.clone(),
+ source: self.source.clone(),
+ command: self.command.clone(),
+ child: self.child.clone(),
+ range: self.range.clone(),
+ modifier: self.modifier.clone(),
+ forks: self.forks,
+ }
+ }
+}
+
+impl<S> CommandContextBuilder<S> {
+ pub fn new(
+ dispatcher: Rc<CommandDispatcher<S>>,
+ source: Rc<S>,
+ root_node: Rc<RefCell<CommandNode<S>>>,
+ start: usize,
+ ) -> Self {
+ Self {
+ arguments: HashMap::new(),
+ root: root_node,
+ source,
+ range: StringRange::at(start),
+ command: None,
+ dispatcher,
+ nodes: vec![],
+ child: None,
+ modifier: None,
+ forks: false,
+ }
+ }
+
+ pub fn with_command(
+ &mut self,
+ command: &Option<Rc<dyn Fn(&CommandContext<S>) -> i32>>,
+ ) -> &Self {
+ self.command = command.clone();
+ self
+ }
+ pub fn with_child(&mut self, child: Rc<CommandContextBuilder<S>>) -> &Self {
+ self.child = Some(child);
+ self
+ }
+ pub fn with_argument(&mut self, name: &str, argument: ParsedArgument) -> &Self {
+ self.arguments.insert(name.to_string(), argument);
+ self
+ }
+ pub fn with_node(&mut self, node: Rc<RefCell<CommandNode<S>>>, range: StringRange) -> &Self {
+ self.nodes.push(ParsedCommandNode {
+ node: node.clone(),
+ range: range.clone(),
+ });
+ self.range = StringRange::encompassing(&self.range, &range);
+ self.modifier = node.borrow().modifier.clone();
+ self.forks = node.borrow().forks;
+ self
+ }
+
+ pub fn build(&self, input: &str) -> CommandContext<S> {
+ CommandContext {
+ arguments: self.arguments.clone(),
+ root_node: self.root.clone(),
+ nodes: self.nodes.clone(),
+ source: self.source.clone(),
+ command: self.command.clone(),
+ child: self.child.clone().map(|c| Rc::new(c.build(input))),
+ range: self.range.clone(),
+ forks: self.forks,
+ modifier: self.modifier.clone(),
+ input: input.to_string(),
+ }
+ }
+}
+
+impl<S> Debug for CommandContextBuilder<S> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("CommandContextBuilder")
+ // .field("arguments", &self.arguments)
+ .field("root", &self.root)
+ // .field("nodes", &self.nodes)
+ // .field("dispatcher", &self.dispatcher)
+ // .field("source", &self.source)
+ // .field("command", &self.command)
+ .field("child", &self.child)
+ .field("range", &self.range)
+ // .field("modifier", &self.modifier)
+ .field("forks", &self.forks)
+ .finish()
+ }
+}
diff --git a/azalea-brigadier/src/context/mod.rs b/azalea-brigadier/src/context/mod.rs
new file mode 100755
index 00000000..d535602a
--- /dev/null
+++ b/azalea-brigadier/src/context/mod.rs
@@ -0,0 +1,11 @@
+mod command_context;
+mod command_context_builder;
+mod parsed_argument;
+mod parsed_command_node;
+mod string_range;
+
+pub use command_context::CommandContext;
+pub use command_context_builder::CommandContextBuilder;
+pub use parsed_argument::ParsedArgument;
+pub use parsed_command_node::ParsedCommandNode;
+pub use string_range::StringRange;
diff --git a/azalea-brigadier/src/context/parsed_argument.rs b/azalea-brigadier/src/context/parsed_argument.rs
new file mode 100755
index 00000000..3302b1be
--- /dev/null
+++ b/azalea-brigadier/src/context/parsed_argument.rs
@@ -0,0 +1,8 @@
+use super::string_range::StringRange;
+use std::{any::Any, rc::Rc};
+
+#[derive(Clone)]
+pub struct ParsedArgument {
+ pub range: StringRange,
+ pub result: Rc<dyn Any>,
+}
diff --git a/azalea-brigadier/src/context/parsed_command_node.rs b/azalea-brigadier/src/context/parsed_command_node.rs
new file mode 100755
index 00000000..ed49928d
--- /dev/null
+++ b/azalea-brigadier/src/context/parsed_command_node.rs
@@ -0,0 +1,18 @@
+use super::string_range::StringRange;
+use crate::tree::CommandNode;
+use std::{cell::RefCell, rc::Rc};
+
+#[derive(Debug)]
+pub struct ParsedCommandNode<S> {
+ pub node: Rc<RefCell<CommandNode<S>>>,
+ pub range: StringRange,
+}
+
+impl<S> Clone for ParsedCommandNode<S> {
+ fn clone(&self) -> Self {
+ Self {
+ node: self.node.clone(),
+ range: self.range.clone(),
+ }
+ }
+}
diff --git a/azalea-brigadier/src/context/string_range.rs b/azalea-brigadier/src/context/string_range.rs
new file mode 100755
index 00000000..8ca88624
--- /dev/null
+++ b/azalea-brigadier/src/context/string_range.rs
@@ -0,0 +1,45 @@
+use std::cmp;
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
+pub struct StringRange {
+ start: usize,
+ end: usize,
+}
+
+impl StringRange {
+ pub fn new(start: usize, end: usize) -> Self {
+ Self { start, end }
+ }
+
+ pub fn at(pos: usize) -> Self {
+ Self::new(pos, pos)
+ }
+
+ pub fn between(start: usize, end: usize) -> Self {
+ Self::new(start, end)
+ }
+
+ pub fn encompassing(a: &Self, b: &Self) -> Self {
+ Self::new(cmp::min(a.start, b.start), cmp::max(a.end, b.end))
+ }
+
+ pub fn start(&self) -> usize {
+ self.start
+ }
+
+ pub fn end(&self) -> usize {
+ self.end
+ }
+
+ pub fn get<'a>(&self, reader: &'a str) -> &'a str {
+ &reader[self.start..self.end]
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.start == self.end
+ }
+
+ pub fn length(&self) -> usize {
+ self.end - self.start
+ }
+}
diff --git a/azalea-brigadier/src/exceptions/builtin_exceptions.rs b/azalea-brigadier/src/exceptions/builtin_exceptions.rs
new file mode 100755
index 00000000..09951a03
--- /dev/null
+++ b/azalea-brigadier/src/exceptions/builtin_exceptions.rs
@@ -0,0 +1,159 @@
+use std::fmt;
+
+use crate::{message::Message, string_reader::StringReader};
+
+use super::command_syntax_exception::CommandSyntaxException;
+
+#[derive(Clone, PartialEq)]
+pub enum BuiltInExceptions {
+ DoubleTooSmall { found: f64, min: f64 },
+ DoubleTooBig { found: f64, max: f64 },
+
+ FloatTooSmall { found: f32, min: f32 },
+ FloatTooBig { found: f32, max: f32 },
+
+ IntegerTooSmall { found: i32, min: i32 },
+ IntegerTooBig { found: i32, max: i32 },
+
+ LongTooSmall { found: i64, min: i64 },
+ LongTooBig { found: i64, max: i64 },
+
+ LiteralIncorrect { expected: String },
+
+ ReaderExpectedStartOfQuote,
+ ReaderExpectedEndOfQuote,
+ ReaderInvalidEscape { character: char },
+ ReaderInvalidBool { value: String },
+ ReaderInvalidInt { value: String },
+ ReaderExpectedInt,
+ ReaderInvalidLong { value: String },
+ ReaderExpectedLong,
+ ReaderInvalidDouble { value: String },
+ ReaderExpectedDouble,
+ ReaderInvalidFloat { value: String },
+ ReaderExpectedFloat,
+ ReaderExpectedBool,
+ ReaderExpectedSymbol { symbol: char },
+
+ DispatcherUnknownCommand,
+ DispatcherUnknownArgument,
+ DispatcherExpectedArgumentSeparator,
+ DispatcherParseException { message: String },
+}
+
+impl fmt::Debug for BuiltInExceptions {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ BuiltInExceptions::DoubleTooSmall { found, min } => {
+ write!(f, "Double must not be less than {}, found {}", min, found)
+ }
+ BuiltInExceptions::DoubleTooBig { found, max } => {
+ write!(f, "Double must not be more than {}, found {}", max, found)
+ }
+
+ BuiltInExceptions::FloatTooSmall { found, min } => {
+ write!(f, "Float must not be less than {}, found {}", min, found)
+ }
+ BuiltInExceptions::FloatTooBig { found, max } => {
+ write!(f, "Float must not be more than {}, found {}", max, found)
+ }
+
+ BuiltInExceptions::IntegerTooSmall { found, min } => {
+ write!(f, "Integer must not be less than {}, found {}", min, found)
+ }
+ BuiltInExceptions::IntegerTooBig { found, max } => {
+ write!(f, "Integer must not be more than {}, found {}", max, found)
+ }
+
+ BuiltInExceptions::LongTooSmall { found, min } => {
+ write!(f, "Long must not be less than {}, found {}", min, found)
+ }
+ BuiltInExceptions::LongTooBig { found, max } => {
+ write!(f, "Long must not be more than {}, found {}", max, found)
+ }
+
+ BuiltInExceptions::LiteralIncorrect { expected } => {
+ write!(f, "Expected literal {}", expected)
+ }
+
+ BuiltInExceptions::ReaderExpectedStartOfQuote => {
+ write!(f, "Expected quote to start a string")
+ }
+ BuiltInExceptions::ReaderExpectedEndOfQuote => {
+ write!(f, "Unclosed quoted string")
+ }
+ BuiltInExceptions::ReaderInvalidEscape { character } => {
+ write!(
+ f,
+ "Invalid escape sequence '{}' in quoted string",
+ character
+ )
+ }
+ BuiltInExceptions::ReaderInvalidBool { value } => {
+ write!(
+ f,
+ "Invalid bool, expected true or false but found '{}'",
+ value
+ )
+ }
+ BuiltInExceptions::ReaderInvalidInt { value } => {
+ write!(f, "Invalid Integer '{}'", value)
+ }
+ BuiltInExceptions::ReaderExpectedInt => {
+ write!(f, "Expected Integer")
+ }
+ BuiltInExceptions::ReaderInvalidLong { value } => {
+ write!(f, "Invalid long '{}'", value)
+ }
+ BuiltInExceptions::ReaderExpectedLong => {
+ write!(f, "Expected long")
+ }
+ BuiltInExceptions::ReaderInvalidDouble { value } => {
+ write!(f, "Invalid double '{}'", value)
+ }
+ BuiltInExceptions::ReaderExpectedDouble => {
+ write!(f, "Expected double")
+ }
+ BuiltInExceptions::ReaderInvalidFloat { value } => {
+ write!(f, "Invalid Float '{}'", value)
+ }
+ BuiltInExceptions::ReaderExpectedFloat => {
+ write!(f, "Expected Float")
+ }
+ BuiltInExceptions::ReaderExpectedBool => {
+ write!(f, "Expected bool")
+ }
+ BuiltInExceptions::ReaderExpectedSymbol { symbol } => {
+ write!(f, "Expected '{}'", symbol)
+ }
+
+ BuiltInExceptions::DispatcherUnknownCommand => {
+ write!(f, "Unknown command")
+ }
+ BuiltInExceptions::DispatcherUnknownArgument => {
+ write!(f, "Incorrect argument for command")
+ }
+ BuiltInExceptions::DispatcherExpectedArgumentSeparator => {
+ write!(
+ f,
+ "Expected whitespace to end one argument, but found trailing data"
+ )
+ }
+ BuiltInExceptions::DispatcherParseException { message } => {
+ write!(f, "Could not parse command: {}", message)
+ }
+ }
+ }
+}
+
+impl BuiltInExceptions {
+ pub fn create(self) -> CommandSyntaxException {
+ let message = Message::from(format!("{:?}", self));
+ CommandSyntaxException::create(self, message)
+ }
+
+ pub fn create_with_context(self, reader: &StringReader) -> CommandSyntaxException {
+ let message = Message::from(format!("{:?}", self));
+ CommandSyntaxException::new(self, message, reader.string(), reader.cursor())
+ }
+}
diff --git a/azalea-brigadier/src/exceptions/command_syntax_exception.rs b/azalea-brigadier/src/exceptions/command_syntax_exception.rs
new file mode 100755
index 00000000..4bfe9cda
--- /dev/null
+++ b/azalea-brigadier/src/exceptions/command_syntax_exception.rs
@@ -0,0 +1,91 @@
+use std::{cmp, fmt};
+
+use super::builtin_exceptions::BuiltInExceptions;
+use crate::message::Message;
+
+#[derive(Clone, PartialEq)]
+pub struct CommandSyntaxException {
+ pub type_: BuiltInExceptions,
+ message: Message,
+ input: Option<String>,
+ cursor: Option<usize>,
+}
+
+const CONTEXT_AMOUNT: usize = 10;
+
+impl CommandSyntaxException {
+ pub fn new(type_: BuiltInExceptions, message: Message, input: &str, cursor: usize) -> Self {
+ Self {
+ type_,
+ message,
+ input: Some(input.to_string()),
+ cursor: Some(cursor),
+ }
+ }
+
+ pub fn create(type_: BuiltInExceptions, message: Message) -> Self {
+ Self {
+ type_,
+ message,
+ input: None,
+ cursor: None,
+ }
+ }
+
+ pub fn message(&self) -> String {
+ let mut message = self.message.string();
+ let context = self.context();
+ if let Some(context) = context {
+ message.push_str(&format!(
+ " at position {}: {}",
+ self.cursor.unwrap_or(usize::MAX),
+ context
+ ));
+ }
+ message
+ }
+
+ pub fn raw_message(&self) -> &Message {
+ &self.message
+ }
+
+ pub fn context(&self) -> Option<String> {
+ if let Some(input) = &self.input {
+ if let Some(cursor) = self.cursor {
+ let mut builder = String::new();
+ let cursor = cmp::min(input.len(), cursor);
+
+ if cursor > CONTEXT_AMOUNT {
+ builder.push_str("...");
+ }
+
+ builder.push_str(
+ &input
+ [(cmp::max(0, cursor as isize - CONTEXT_AMOUNT as isize) as usize)..cursor],
+ );
+ builder.push_str("<--[HERE]");
+
+ return Some(builder);
+ }
+ }
+ None
+ }
+
+ pub fn get_type(&self) -> &BuiltInExceptions {
+ &self.type_
+ }
+
+ pub fn input(&self) -> &Option<String> {
+ &self.input
+ }
+
+ pub fn cursor(&self) -> Option<usize> {
+ self.cursor
+ }
+}
+
+impl fmt::Debug for CommandSyntaxException {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.message())
+ }
+}
diff --git a/azalea-brigadier/src/exceptions/mod.rs b/azalea-brigadier/src/exceptions/mod.rs
new file mode 100755
index 00000000..6b9c8d62
--- /dev/null
+++ b/azalea-brigadier/src/exceptions/mod.rs
@@ -0,0 +1,5 @@
+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
new file mode 100755
index 00000000..a294eb19
--- /dev/null
+++ b/azalea-brigadier/src/lib.rs
@@ -0,0 +1,10 @@
+pub mod arguments;
+pub mod builder;
+pub mod command_dispatcher;
+pub mod context;
+pub mod exceptions;
+pub mod message;
+pub mod modifier;
+pub mod parse_results;
+pub mod string_reader;
+pub mod tree;
diff --git a/azalea-brigadier/src/message.rs b/azalea-brigadier/src/message.rs
new file mode 100755
index 00000000..75e07d4e
--- /dev/null
+++ b/azalea-brigadier/src/message.rs
@@ -0,0 +1,14 @@
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct Message(String);
+
+impl Message {
+ pub fn string(&self) -> String {
+ self.0.to_string()
+ }
+}
+
+impl From<String> for Message {
+ fn from(s: String) -> Self {
+ Self(s)
+ }
+}
diff --git a/azalea-brigadier/src/modifier.rs b/azalea-brigadier/src/modifier.rs
new file mode 100755
index 00000000..d40d59f9
--- /dev/null
+++ b/azalea-brigadier/src/modifier.rs
@@ -0,0 +1,6 @@
+use std::rc::Rc;
+
+use crate::{context::CommandContext, exceptions::CommandSyntaxException};
+
+pub type RedirectModifier<S> =
+ dyn Fn(&CommandContext<S>) -> Result<Vec<Rc<S>>, CommandSyntaxException>;
diff --git a/azalea-brigadier/src/parse_results.rs b/azalea-brigadier/src/parse_results.rs
new file mode 100755
index 00000000..3698ae82
--- /dev/null
+++ b/azalea-brigadier/src/parse_results.rs
@@ -0,0 +1,21 @@
+use crate::{
+ context::CommandContextBuilder, exceptions::CommandSyntaxException,
+ string_reader::StringReader, tree::CommandNode,
+};
+use std::{collections::HashMap, fmt::Debug, rc::Rc};
+
+pub struct ParseResults<S> {
+ pub context: CommandContextBuilder<S>,
+ pub reader: StringReader,
+ pub exceptions: HashMap<Rc<CommandNode<S>>, CommandSyntaxException>,
+}
+
+impl<S> Debug for ParseResults<S> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("ParseResults")
+ .field("context", &self.context)
+ // .field("reader", &self.reader)
+ .field("exceptions", &self.exceptions)
+ .finish()
+ }
+}
diff --git a/azalea-brigadier/src/string_reader.rs b/azalea-brigadier/src/string_reader.rs
new file mode 100755
index 00000000..9eb09b57
--- /dev/null
+++ b/azalea-brigadier/src/string_reader.rs
@@ -0,0 +1,274 @@
+use crate::exceptions::{BuiltInExceptions, CommandSyntaxException};
+use std::str::FromStr;
+
+#[derive(Clone)]
+pub struct StringReader {
+ string: String,
+ pub cursor: usize,
+}
+
+const SYNTAX_ESCAPE: char = '\\';
+const SYNTAX_DOUBLE_QUOTE: char = '"';
+const SYNTAX_SINGLE_QUOTE: char = '\'';
+
+impl From<String> for StringReader {
+ fn from(string: String) -> Self {
+ Self { string, cursor: 0 }
+ }
+}
+impl From<&str> for StringReader {
+ fn from(string: &str) -> Self {
+ Self {
+ string: string.to_string(),
+ cursor: 0,
+ }
+ }
+}
+
+impl StringReader {
+ pub fn string(&self) -> &str {
+ &self.string
+ }
+
+ pub fn remaining_length(&self) -> usize {
+ self.string.len() - self.cursor
+ }
+
+ pub fn total_length(&self) -> usize {
+ self.string.len()
+ }
+
+ pub fn get_read(&self) -> &str {
+ &self.string[..self.cursor]
+ }
+
+ pub fn remaining(&self) -> &str {
+ &self.string[self.cursor..]
+ }
+
+ pub fn can_read_length(&self, length: usize) -> bool {
+ self.cursor + length <= self.string.len()
+ }
+
+ pub fn can_read(&self) -> bool {
+ self.can_read_length(1)
+ }
+
+ pub fn peek(&self) -> char {
+ self.string.chars().nth(self.cursor).unwrap()
+ }
+
+ pub fn peek_offset(&self, offset: usize) -> char {
+ self.string.chars().nth(self.cursor + offset).unwrap()
+ }
+
+ pub fn cursor(&self) -> usize {
+ self.cursor
+ }
+
+ pub fn read(&mut self) -> char {
+ let c = self.peek();
+ self.cursor += 1;
+ c
+ }
+
+ pub fn skip(&mut self) {
+ self.cursor += 1;
+ }
+
+ pub fn is_allowed_number(c: char) -> bool {
+ ('0'..='9').contains(&c) || c == '.' || c == '-'
+ }
+
+ pub fn is_quoted_string_start(c: char) -> bool {
+ c == SYNTAX_DOUBLE_QUOTE || c == SYNTAX_SINGLE_QUOTE
+ }
+
+ pub fn skip_whitespace(&mut self) {
+ while self.can_read() && self.peek().is_whitespace() {
+ self.skip();
+ }
+ }
+
+ pub fn read_int(&mut self) -> Result<i32, CommandSyntaxException> {
+ 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));
+ }
+ let result = i32::from_str(number);
+ if result.is_err() {
+ self.cursor = start;
+ return Err(BuiltInExceptions::ReaderInvalidInt {
+ value: number.to_string(),
+ }
+ .create_with_context(self));
+ }
+
+ Ok(result.unwrap())
+ }
+
+ pub fn read_long(&mut self) -> Result<i64, CommandSyntaxException> {
+ 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));
+ }
+ let result = i64::from_str(number);
+ if result.is_err() {
+ self.cursor = start;
+ return Err(BuiltInExceptions::ReaderInvalidLong {
+ value: number.to_string(),
+ }
+ .create_with_context(self));
+ }
+
+ Ok(result.unwrap())
+ }
+
+ pub fn read_double(&mut self) -> Result<f64, CommandSyntaxException> {
+ 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));
+ }
+ let result = f64::from_str(number);
+ if result.is_err() {
+ self.cursor = start;
+ return Err(BuiltInExceptions::ReaderInvalidDouble {
+ value: number.to_string(),
+ }
+ .create_with_context(self));
+ }
+
+ Ok(result.unwrap())
+ }
+
+ pub fn read_float(&mut self) -> Result<f32, CommandSyntaxException> {
+ 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));
+ }
+ let result = f32::from_str(number);
+ if result.is_err() {
+ self.cursor = start;
+ return Err(BuiltInExceptions::ReaderInvalidFloat {
+ value: number.to_string(),
+ }
+ .create_with_context(self));
+ }
+
+ Ok(result.unwrap())
+ }
+
+ pub fn is_allowed_in_unquoted_string(c: char) -> bool {
+ ('0'..='9').contains(&c)
+ || ('A'..='Z').contains(&c)
+ || ('a'..='z').contains(&c)
+ || c == '_'
+ || c == '-'
+ || c == '.'
+ || c == '+'
+ }
+
+ pub fn read_unquoted_string(&mut self) -> &str {
+ let start = self.cursor;
+ while self.can_read() && StringReader::is_allowed_in_unquoted_string(self.peek()) {
+ self.skip();
+ }
+ &self.string[start..self.cursor]
+ }
+
+ pub fn read_quoted_string(&mut self) -> Result<String, CommandSyntaxException> {
+ 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));
+ }
+ self.skip();
+ self.read_string_until(next)
+ }
+
+ pub fn read_string_until(
+ &mut self,
+ terminator: char,
+ ) -> Result<String, CommandSyntaxException> {
+ let mut result = String::new();
+ let mut escaped = false;
+ while self.can_read() {
+ let c = self.read();
+ if escaped {
+ if c == terminator || c == SYNTAX_ESCAPE {
+ result.push(c);
+ escaped = false;
+ } else {
+ self.cursor -= 1;
+ return Err(BuiltInExceptions::ReaderInvalidEscape { character: c }
+ .create_with_context(self));
+ }
+ } else if c == SYNTAX_ESCAPE {
+ escaped = true;
+ } else if c == terminator {
+ return Ok(result);
+ } else {
+ result.push(c);
+ }
+ }
+
+ Err(BuiltInExceptions::ReaderExpectedEndOfQuote.create_with_context(self))
+ }
+
+ pub fn read_string(&mut self) -> Result<String, CommandSyntaxException> {
+ if !self.can_read() {
+ return Ok(String::new());
+ }
+ let next = self.peek();
+ if StringReader::is_quoted_string_start(next) {
+ self.skip();
+ return self.read_string_until(next);
+ }
+ Ok(self.read_unquoted_string().to_string())
+ }
+
+ pub fn read_boolean(&mut self) -> Result<bool, CommandSyntaxException> {
+ let start = self.cursor;
+ let value = self.read_string()?;
+ if value.is_empty() {
+ return Err(BuiltInExceptions::ReaderExpectedBool.create_with_context(self));
+ }
+
+ if value == "true" {
+ Ok(true)
+ } else if value == "false" {
+ Ok(false)
+ } else {
+ self.cursor = start;
+ Err(BuiltInExceptions::ReaderInvalidBool { value }.create_with_context(self))
+ }
+ }
+
+ pub fn expect(&mut self, c: char) -> Result<(), CommandSyntaxException> {
+ if !self.can_read() || self.peek() != c {
+ return Err(
+ BuiltInExceptions::ReaderExpectedSymbol { symbol: c }.create_with_context(self)
+ );
+ }
+ self.skip();
+ Ok(())
+ }
+}
diff --git a/azalea-brigadier/src/tree/mod.rs b/azalea-brigadier/src/tree/mod.rs
new file mode 100755
index 00000000..b6181c73
--- /dev/null
+++ b/azalea-brigadier/src/tree/mod.rs
@@ -0,0 +1,259 @@
+use crate::{
+ builder::{
+ argument_builder::ArgumentBuilderType, literal_argument_builder::Literal,
+ required_argument_builder::Argument,
+ },
+ context::{CommandContext, CommandContextBuilder, ParsedArgument, StringRange},
+ exceptions::{BuiltInExceptions, CommandSyntaxException},
+ modifier::RedirectModifier,
+ string_reader::StringReader,
+};
+use std::{cell::RefCell, collections::HashMap, fmt::Debug, hash::Hash, ptr, rc::Rc};
+
+/// An ArgumentBuilder that has been built.
+#[non_exhaustive]
+pub struct CommandNode<S> {
+ pub value: ArgumentBuilderType,
+
+ pub children: HashMap<String, Rc<RefCell<CommandNode<S>>>>,
+ pub literals: HashMap<String, Rc<RefCell<CommandNode<S>>>>,
+ pub arguments: HashMap<String, Rc<RefCell<CommandNode<S>>>>,
+
+ pub command: Option<Rc<dyn Fn(&CommandContext<S>) -> i32>>,
+ pub requirement: Rc<dyn Fn(Rc<S>) -> bool>,
+ pub redirect: Option<Rc<RefCell<CommandNode<S>>>>,
+ pub forks: bool,
+ pub modifier: Option<Rc<RedirectModifier<S>>>,
+}
+
+impl<S> Clone for CommandNode<S> {
+ fn clone(&self) -> Self {
+ Self {
+ value: self.value.clone(),
+ children: self.children.clone(),
+ literals: self.literals.clone(),
+ arguments: self.arguments.clone(),
+ command: self.command.clone(),
+ requirement: self.requirement.clone(),
+ redirect: self.redirect.clone(),
+ forks: self.forks,
+ modifier: self.modifier.clone(),
+ }
+ }
+}
+
+impl<S> CommandNode<S> {
+ /// Gets the literal, or panics. You should use match if you're not certain about the type.
+ pub fn literal(&self) -> &Literal {
+ match self.value {
+ ArgumentBuilderType::Literal(ref literal) => literal,
+ _ => panic!("CommandNode::literal() called on non-literal node"),
+ }
+ }
+ /// Gets the argument, or panics. You should use match if you're not certain about the type.
+ pub fn argument(&self) -> &Argument {
+ match self.value {
+ ArgumentBuilderType::Argument(ref argument) => argument,
+ _ => panic!("CommandNode::argument() called on non-argument node"),
+ }
+ }
+
+ pub fn get_relevant_nodes(&self, input: &mut StringReader) -> Vec<Rc<RefCell<CommandNode<S>>>> {
+ let literals = &self.literals;
+
+ if !literals.is_empty() {
+ let cursor = input.cursor();
+ while input.can_read() && input.peek() != ' ' {
+ input.skip();
+ }
+ let text: String = input
+ .string()
+ .chars()
+ .skip(cursor)
+ .take(input.cursor() - cursor)
+ .collect();
+ input.cursor = cursor;
+ let literal = literals.get(&text);
+ if let Some(literal) = literal {
+ return vec![literal.clone()];
+ } else {
+ return self.arguments.values().cloned().collect();
+ }
+ } else {
+ self.arguments.values().cloned().collect()
+ }
+ }
+
+ pub fn can_use(&self, source: Rc<S>) -> bool {
+ (self.requirement)(source)
+ }
+
+ pub fn add_child(&mut self, node: &Rc<RefCell<CommandNode<S>>>) {
+ let child = self.children.get(node.borrow().name());
+ if let Some(child) = child {
+ // We've found something to merge onto
+ if let Some(command) = &node.borrow().command {
+ child.borrow_mut().command = Some(command.clone());
+ }
+ for grandchild in node.borrow().children.values() {
+ child.borrow_mut().add_child(grandchild);
+ }
+ } else {
+ self.children
+ .insert(node.borrow().name().to_string(), node.clone());
+ match &node.borrow().value {
+ ArgumentBuilderType::Literal(literal) => {
+ self.literals.insert(literal.value.clone(), node.clone());
+ }
+ ArgumentBuilderType::Argument(argument) => {
+ self.arguments.insert(argument.name.clone(), node.clone());
+ }
+ }
+ }
+ }
+
+ pub fn name(&self) -> &str {
+ match &self.value {
+ ArgumentBuilderType::Argument(argument) => &argument.name,
+ ArgumentBuilderType::Literal(literal) => &literal.value,
+ }
+ }
+
+ pub fn child(&self, name: &str) -> Option<Rc<RefCell<CommandNode<S>>>> {
+ self.children.get(name).cloned()
+ }
+
+ pub fn parse_with_context(
+ &self,
+ reader: &mut StringReader,
+ context_builder: &mut CommandContextBuilder<S>,
+ ) -> Result<(), CommandSyntaxException> {
+ match self.value {
+ ArgumentBuilderType::Argument(ref argument) => {
+ let start = reader.cursor();
+ let result = argument.parse(reader)?;
+ let parsed = ParsedArgument {
+ range: StringRange::between(start, reader.cursor()),
+ result,
+ };
+
+ context_builder.with_argument(&argument.name, parsed.clone());
+ context_builder.with_node(Rc::new(RefCell::new(self.clone())), parsed.range);
+
+ Ok(())
+ }
+ ArgumentBuilderType::Literal(ref literal) => {
+ let start = reader.cursor();
+ let end = self.parse(reader);
+
+ if let Some(end) = end {
+ context_builder.with_node(
+ Rc::new(RefCell::new(self.clone())),
+ StringRange::between(start, end),
+ );
+ return Ok(());
+ }
+
+ Err(BuiltInExceptions::LiteralIncorrect {
+ expected: literal.value.clone(),
+ }
+ .create_with_context(reader))
+ }
+ }
+ }
+
+ fn parse(&self, reader: &mut StringReader) -> Option<usize> {
+ match self.value {
+ ArgumentBuilderType::Argument(_) => {
+ panic!("Can't parse argument.")
+ }
+ ArgumentBuilderType::Literal(ref literal) => {
+ let start = reader.cursor();
+ if reader.can_read_length(literal.value.len()) {
+ let end = start + literal.value.len();
+ if reader
+ .string()
+ .get(start..end)
+ .expect("Couldn't slice reader correctly?")
+ == literal.value
+ {
+ reader.cursor = end;
+ if !reader.can_read() || reader.peek() == ' ' {
+ return Some(end);
+ } else {
+ reader.cursor = start;
+ }
+ }
+ }
+ }
+ }
+ None
+ }
+}
+
+impl<S> Debug for CommandNode<S> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("CommandNode")
+ .field("value", &self.value)
+ .field("children", &self.children)
+ .field("command", &self.command.is_some())
+ // .field("requirement", &self.requirement)
+ .field("redirect", &self.redirect)
+ .field("forks", &self.forks)
+ // .field("modifier", &self.modifier)
+ .finish()
+ }
+}
+
+impl<S> Default for CommandNode<S> {
+ fn default() -> Self {
+ Self {
+ value: ArgumentBuilderType::Literal(Literal::default()),
+
+ children: HashMap::new(),
+ literals: HashMap::new(),
+ arguments: HashMap::new(),
+
+ command: None,
+ requirement: Rc::new(|_| true),
+ redirect: None,
+ forks: false,
+ modifier: None,
+ }
+ }
+}
+
+impl<S> Hash for CommandNode<S> {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ // hash the children
+ for (k, v) in &self.children {
+ k.hash(state);
+ v.borrow().hash(state);
+ }
+ // i hope this works because if doesn't then that'll be a problem
+ ptr::hash(&self.command, state);
+ }
+}
+
+impl<S> PartialEq for CommandNode<S> {
+ fn eq(&self, other: &Self) -> bool {
+ if self.children != other.children {
+ return false;
+ }
+ if let Some(selfexecutes) = &self.command {
+ // idk how to do this better since we can't compare `dyn Fn`s
+ if let Some(otherexecutes) = &other.command {
+ #[allow(clippy::vtable_address_comparisons)]
+ if !Rc::ptr_eq(selfexecutes, otherexecutes) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ } else if other.command.is_some() {
+ return false;
+ }
+ true
+ }
+}
+impl<S> Eq for CommandNode<S> {}
diff --git a/azalea-brigadier/tests/arguments/bool_argument_type_test.rs b/azalea-brigadier/tests/arguments/bool_argument_type_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/arguments/bool_argument_type_test.rs
diff --git a/azalea-brigadier/tests/arguments/double_argument_type_test.rs b/azalea-brigadier/tests/arguments/double_argument_type_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/arguments/double_argument_type_test.rs
diff --git a/azalea-brigadier/tests/arguments/float_argument_type_test.rs b/azalea-brigadier/tests/arguments/float_argument_type_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/arguments/float_argument_type_test.rs
diff --git a/azalea-brigadier/tests/arguments/integer_argument_type_test.rs b/azalea-brigadier/tests/arguments/integer_argument_type_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/arguments/integer_argument_type_test.rs
diff --git a/azalea-brigadier/tests/arguments/long_argument_type_test.rs b/azalea-brigadier/tests/arguments/long_argument_type_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/arguments/long_argument_type_test.rs
diff --git a/azalea-brigadier/tests/arguments/string_argument_type_test.rs b/azalea-brigadier/tests/arguments/string_argument_type_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/arguments/string_argument_type_test.rs
diff --git a/azalea-brigadier/tests/builder/argument_builder_test.rs b/azalea-brigadier/tests/builder/argument_builder_test.rs
new file mode 100755
index 00000000..e570c988
--- /dev/null
+++ b/azalea-brigadier/tests/builder/argument_builder_test.rs
@@ -0,0 +1,75 @@
+use std::rc::Rc;
+
+use crate::{
+ arguments::integer_argument_type::integer,
+ builder::{literal_argument_builder::literal, required_argument_builder::argument},
+};
+
+use super::ArgumentBuilder;
+
+// public class ArgumentBuilderTest {
+// private TestableArgumentBuilder<Object> builder;
+
+// @Before
+// public void setUp() throws Exception {
+// builder = new TestableArgumentBuilder<>();
+// }
+
+// @Test
+// public void testArguments() throws Exception {
+// final RequiredArgumentBuilder<Object, ?> argument = argument("bar", integer());
+
+// builder.then(argument);
+
+// assertThat(builder.getArguments(), hasSize(1));
+// assertThat(builder.getArguments(), hasItem((CommandNode<Object>) argument.build()));
+// }
+
+#[test]
+fn test_arguments() {
+ let mut builder: ArgumentBuilder<()> = literal("foo");
+
+ let argument: ArgumentBuilder<()> = argument("bar", integer());
+ builder.then(argument.clone());
+ assert_eq!(builder.arguments.children.len(), 1);
+ let built_argument = Rc::new(argument.build());
+ assert!(builder
+ .arguments
+ .children
+ .values()
+ .any(|e| *e.borrow() == *built_argument));
+}
+
+// @Test
+// public void testRedirect() throws Exception {
+// final CommandNode<Object> target = mock(CommandNode.class);
+// builder.redirect(target);
+// assertThat(builder.getRedirect(), is(target));
+// }
+
+// @Test(expected = IllegalStateException.class)
+// public void testRedirect_withChild() throws Exception {
+// final CommandNode<Object> target = mock(CommandNode.class);
+// builder.then(literal("foo"));
+// builder.redirect(target);
+// }
+
+// @Test(expected = IllegalStateException.class)
+// public void testThen_withRedirect() throws Exception {
+// final CommandNode<Object> target = mock(CommandNode.class);
+// builder.redirect(target);
+// builder.then(literal("foo"));
+// }
+
+// private static class TestableArgumentBuilder<S> extends ArgumentBuilder<S, TestableArgumentBuilder<S>> {
+// @Override
+// protected TestableArgumentBuilder<S> getThis() {
+// return this;
+// }
+
+// @Override
+// public CommandNode<S> build() {
+// return null;
+// }
+// }
+// }
diff --git a/azalea-brigadier/tests/builder/literal_argument_builder_test.rs b/azalea-brigadier/tests/builder/literal_argument_builder_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/builder/literal_argument_builder_test.rs
diff --git a/azalea-brigadier/tests/builder/required_argument_builder_test.rs b/azalea-brigadier/tests/builder/required_argument_builder_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/builder/required_argument_builder_test.rs
diff --git a/azalea-brigadier/tests/command_dispatcher_test.rs b/azalea-brigadier/tests/command_dispatcher_test.rs
new file mode 100755
index 00000000..cb33ac73
--- /dev/null
+++ b/azalea-brigadier/tests/command_dispatcher_test.rs
@@ -0,0 +1,410 @@
+use std::rc::Rc;
+
+use azalea_brigadier::{
+ arguments::integer_argument_type::integer,
+ builder::{literal_argument_builder::literal, required_argument_builder::argument},
+ command_dispatcher::CommandDispatcher,
+ context::CommandContext,
+ exceptions::{BuiltInExceptions, CommandSyntaxException},
+ string_reader::StringReader,
+};
+
+#[derive(Debug, PartialEq)]
+struct CommandSource {}
+
+fn input_with_offset(input: &str, offset: usize) -> StringReader {
+ let mut result: StringReader = input.into();
+ result.cursor = offset;
+ result
+}
+
+#[test]
+fn create_and_execute_command() {
+ let mut subject = CommandDispatcher::new();
+ subject.register(literal("foo").executes(|_| 42));
+
+ assert_eq!(
+ subject
+ .execute("foo".into(), Rc::new(CommandSource {}))
+ .unwrap(),
+ 42
+ );
+}
+
+#[test]
+fn create_and_execute_offset_command() {
+ let mut subject = CommandDispatcher::new();
+ subject.register(literal("foo").executes(|_| 42));
+
+ assert_eq!(
+ subject
+ .execute(input_with_offset("/foo", 1), Rc::new(CommandSource {}))
+ .unwrap(),
+ 42
+ );
+}
+
+#[test]
+fn create_and_merge_commands() {
+ let mut subject = CommandDispatcher::new();
+ subject.register(literal("base").then(literal("foo").executes(|_| 42)));
+ subject.register(literal("base").then(literal("bar").executes(|_| 42)));
+
+ assert_eq!(
+ subject
+ .execute("base foo".into(), Rc::new(CommandSource {}))
+ .unwrap(),
+ 42
+ );
+ assert_eq!(
+ subject
+ .execute("base bar".into(), Rc::new(CommandSource {}))
+ .unwrap(),
+ 42
+ );
+}
+
+#[test]
+fn execute_unknown_command() {
+ let mut subject = CommandDispatcher::new();
+ subject.register(literal("bar"));
+ subject.register(literal("baz"));
+
+ let execute_result = subject.execute("foo".into(), Rc::new(CommandSource {}));
+
+ let err = execute_result.err().unwrap();
+ match err.type_ {
+ BuiltInExceptions::DispatcherUnknownCommand => {}
+ _ => panic!("Unexpected error"),
+ }
+ assert_eq!(err.cursor().unwrap(), 0);
+}
+
+#[test]
+fn execute_impermissible_command() {
+ let mut subject = CommandDispatcher::new();
+ subject.register(literal("foo").requires(|_| false));
+
+ let execute_result = subject.execute("foo".into(), Rc::new(CommandSource {}));
+
+ let err = execute_result.err().unwrap();
+ match err.type_ {
+ BuiltInExceptions::DispatcherUnknownCommand => {}
+ _ => panic!("Unexpected error"),
+ }
+ assert_eq!(err.cursor().unwrap(), 0);
+}
+
+#[test]
+fn execute_empty_command() {
+ let mut subject = CommandDispatcher::new();
+ subject.register(literal(""));
+
+ let execute_result = subject.execute("".into(), Rc::new(CommandSource {}));
+
+ let err = execute_result.err().unwrap();
+ match err.type_ {
+ BuiltInExceptions::DispatcherUnknownCommand => {}
+ _ => panic!("Unexpected error"),
+ }
+ assert_eq!(err.cursor().unwrap(), 0);
+}
+
+#[test]
+fn execute_unknown_subcommand() {
+ let mut subject = CommandDispatcher::new();
+ subject.register(literal("foo").executes(|_| 42));
+
+ let execute_result = subject.execute("foo bar".into(), Rc::new(CommandSource {}));
+
+ let err = execute_result.err().unwrap();
+ match err.type_ {
+ BuiltInExceptions::DispatcherUnknownArgument => {}
+ _ => panic!("Unexpected error"),
+ }
+ assert_eq!(err.cursor().unwrap(), 4);
+}
+
+#[test]
+fn execute_incorrect_literal() {
+ let mut subject = CommandDispatcher::new();
+ subject.register(literal("foo").executes(|_| 42).then(literal("bar")));
+
+ let execute_result = subject.execute("foo baz".into(), Rc::new(CommandSource {}));
+
+ let err = execute_result.err().unwrap();
+ match err.type_ {
+ BuiltInExceptions::DispatcherUnknownArgument => {}
+ _ => panic!("Unexpected error"),
+ }
+ assert_eq!(err.cursor().unwrap(), 4);
+}
+
+#[test]
+fn execute_ambiguous_incorrect_argument() {
+ let mut subject = CommandDispatcher::new();
+ subject.register(
+ literal("foo")
+ .executes(|_| 42)
+ .then(literal("bar"))
+ .then(literal("baz")),
+ );
+
+ let execute_result = subject.execute("foo unknown".into(), Rc::new(CommandSource {}));
+
+ let err = execute_result.err().unwrap();
+ match err.type_ {
+ BuiltInExceptions::DispatcherUnknownArgument => {}
+ _ => panic!("Unexpected error"),
+ }
+ assert_eq!(err.cursor().unwrap(), 4);
+}
+
+#[test]
+fn execute_subcommand() {
+ let mut subject = CommandDispatcher::new();
+
+ subject.register(
+ literal("foo")
+ .then(literal("a"))
+ .then(literal("=").executes(|_| 100))
+ .then(literal("c"))
+ .executes(|_| 42),
+ );
+
+ assert_eq!(
+ subject
+ .execute("foo =".into(), Rc::new(CommandSource {}))
+ .unwrap(),
+ 100
+ );
+}
+
+#[test]
+fn parse_incomplete_literal() {
+ let mut subject = CommandDispatcher::new();
+ subject.register(literal("foo").then(literal("bar").executes(|_| 42)));
+
+ let parse = subject.parse("foo ".into(), Rc::new(CommandSource {}));
+ assert_eq!(parse.reader.remaining(), " ");
+ assert_eq!(parse.context.nodes.len(), 1);
+}
+
+#[test]
+fn parse_incomplete_argument() {
+ let mut subject = CommandDispatcher::new();
+ subject.register(literal("foo").then(argument("bar", integer()).executes(|_| 42)));
+
+ let parse = subject.parse("foo ".into(), Rc::new(CommandSource {}));
+ assert_eq!(parse.reader.remaining(), " ");
+ assert_eq!(parse.context.nodes.len(), 1);
+}
+
+#[test]
+fn execute_ambiguious_parent_subcommand() {
+ let mut subject = CommandDispatcher::new();
+
+ subject.register(
+ literal("test")
+ .then(argument("incorrect", integer()).executes(|_| 42))
+ .then(argument("right", integer()).then(argument("sub", integer()).executes(|_| 100))),
+ );
+
+ assert_eq!(
+ subject
+ .execute("test 1 2".into(), Rc::new(CommandSource {}))
+ .unwrap(),
+ 100
+ );
+}
+
+#[test]
+fn execute_ambiguious_parent_subcommand_via_redirect() {
+ let mut subject = CommandDispatcher::new();
+
+ let real = subject.register(
+ literal("test")
+ .then(argument("incorrect", integer()).executes(|_| 42))
+ .then(argument("right", integer()).then(argument("sub", integer()).executes(|_| 100))),
+ );
+
+ subject.register(literal("redirect").redirect(real));
+
+ assert_eq!(
+ subject
+ .execute("redirect 1 2".into(), Rc::new(CommandSource {}))
+ .unwrap(),
+ 100
+ );
+}
+
+#[test]
+fn execute_redirected_multiple_times() {
+ let mut subject = CommandDispatcher::new();
+
+ let concrete_node = subject.register(literal("actual").executes(|_| 42));
+ let root = subject.root.clone();
+ let redirect_node = subject.register(literal("redirected").redirect(root.clone()));
+
+ let input = "redirected redirected actual";
+
+ let parse = subject.parse(input.into(), Rc::new(CommandSource {}));
+ assert_eq!(parse.context.range.get(input), "redirected");
+ assert_eq!(parse.context.nodes.len(), 1);
+ assert_eq!(parse.context.root, root);
+ assert_eq!(parse.context.nodes[0].range, parse.context.range);
+ assert_eq!(parse.context.nodes[0].node, redirect_node);
+
+ let child1 = parse.context.child.clone();
+ assert!(child1.is_some());
+ assert_eq!(child1.clone().unwrap().range.get(input), "redirected");
+ assert_eq!(child1.clone().unwrap().nodes.len(), 1);
+ assert_eq!(child1.clone().unwrap().root, root);
+ assert_eq!(
+ child1.clone().unwrap().nodes[0].range,
+ child1.clone().unwrap().range
+ );
+ assert_eq!(child1.clone().unwrap().nodes[0].node, redirect_node);
+
+ let child2 = child1.unwrap().child.clone();
+ assert!(child2.is_some());
+ assert_eq!(child2.clone().unwrap().range.get(input), "actual");
+ assert_eq!(child2.clone().unwrap().nodes.len(), 1);
+ assert_eq!(child2.clone().unwrap().root, root);
+ assert_eq!(
+ child2.clone().unwrap().nodes[0].range,
+ child2.clone().unwrap().range
+ );
+ assert_eq!(child2.clone().unwrap().nodes[0].node, concrete_node);
+
+ assert_eq!(CommandDispatcher::execute_parsed(parse).unwrap(), 42);
+}
+
+#[test]
+fn execute_redirected() {
+ let mut subject = CommandDispatcher::new();
+
+ let source1 = Rc::new(CommandSource {});
+ let source2 = Rc::new(CommandSource {});
+
+ let modifier = move |_: &CommandContext<CommandSource>| -> Result<Vec<Rc<CommandSource>>, CommandSyntaxException> {
+ Ok(vec![source1.clone(), source2.clone()])
+ };
+
+ let concrete_node = subject.register(literal("actual").executes(|_| 42));
+ let redirect_node =
+ subject.register(literal("redirected").fork(subject.root.clone(), Rc::new(modifier)));
+
+ let input = "redirected actual";
+ let parse = subject.parse(input.into(), Rc::new(CommandSource {}));
+ assert_eq!(parse.context.range.get(input), "redirected");
+ assert_eq!(parse.context.nodes.len(), 1);
+ assert_eq!(parse.context.root, subject.root);
+ assert_eq!(parse.context.nodes[0].range, parse.context.range);
+ assert_eq!(parse.context.nodes[0].node, redirect_node);
+
+ let parent = parse.context.child.clone();
+ assert!(parent.is_some());
+ let parent = parent.unwrap();
+ assert_eq!(parent.range.get(input), "actual");
+ assert_eq!(parent.nodes.len(), 1);
+ assert_eq!(parse.context.root, subject.root);
+ assert_eq!(parent.nodes[0].range, parent.range);
+ assert_eq!(parent.nodes[0].node, concrete_node);
+ assert_eq!(parent.source, Rc::new(CommandSource {}));
+
+ assert_eq!(CommandDispatcher::execute_parsed(parse).unwrap(), 2);
+}
+
+#[test]
+fn execute_orphaned_subcommand() {
+ let mut subject = CommandDispatcher::new();
+
+ subject.register(
+ literal("foo")
+ .then(argument("bar", integer()))
+ .executes(|_| 42),
+ );
+
+ let result = subject.execute("foo 5".into(), Rc::new(CommandSource {}));
+ assert!(result.is_err());
+ let result = result.unwrap_err();
+ assert_eq!(
+ *result.get_type(),
+ BuiltInExceptions::DispatcherUnknownCommand
+ );
+ assert_eq!(result.cursor(), Some(5));
+}
+
+#[test]
+fn execute_invalid_other() {
+ let mut subject = CommandDispatcher::new();
+
+ subject.register(literal("w").executes(|_| panic!("This should not run")));
+ subject.register(literal("world").executes(|_| 42));
+
+ assert_eq!(
+ subject
+ .execute("world".into(), Rc::new(CommandSource {}))
+ .unwrap(),
+ 42
+ );
+}
+
+#[test]
+fn parse_no_space_separator() {
+ let mut subject = CommandDispatcher::new();
+
+ subject.register(
+ literal("foo")
+ .then(argument("bar", integer()))
+ .executes(|_| 42),
+ );
+
+ let result = subject.execute("foo$".into(), Rc::new(CommandSource {}));
+ assert!(result.is_err());
+ let result = result.unwrap_err();
+ assert_eq!(
+ *result.get_type(),
+ BuiltInExceptions::DispatcherUnknownCommand
+ );
+ assert_eq!(result.cursor(), Some(0));
+}
+
+#[test]
+fn execute_invalid_subcommand() {
+ let mut subject = CommandDispatcher::new();
+
+ subject.register(
+ literal("foo")
+ .then(argument("bar", integer()))
+ .executes(|_| 42),
+ );
+
+ let result = subject.execute("foo bar".into(), Rc::new(CommandSource {}));
+ 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.cursor(), Some(4));
+}
+
+#[test]
+fn get_path() {
+ let mut subject = CommandDispatcher::<()>::new();
+
+ let bar = literal("bar").build();
+ subject.register(literal("foo").then_built(bar.clone()));
+
+ assert_eq!(
+ subject.get_path(bar),
+ vec!["foo".to_string(), "bar".to_string()]
+ );
+}
+
+#[test]
+fn find_node_doesnt_exist() {
+ let subject = CommandDispatcher::<()>::new();
+
+ assert_eq!(subject.find_node(&vec!["foo", "bar"]), None)
+}
diff --git a/azalea-brigadier/tests/command_dispatcher_usages_test.rs b/azalea-brigadier/tests/command_dispatcher_usages_test.rs
new file mode 100755
index 00000000..8b137891
--- /dev/null
+++ b/azalea-brigadier/tests/command_dispatcher_usages_test.rs
@@ -0,0 +1 @@
+
diff --git a/azalea-brigadier/tests/command_suggestions_test.rs b/azalea-brigadier/tests/command_suggestions_test.rs
new file mode 100755
index 00000000..8b137891
--- /dev/null
+++ b/azalea-brigadier/tests/command_suggestions_test.rs
@@ -0,0 +1 @@
+
diff --git a/azalea-brigadier/tests/context/command_context_test.rs b/azalea-brigadier/tests/context/command_context_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/context/command_context_test.rs
diff --git a/azalea-brigadier/tests/context/parsed_argument_test.rs b/azalea-brigadier/tests/context/parsed_argument_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/context/parsed_argument_test.rs
diff --git a/azalea-brigadier/tests/exceptions/dynamic_command_syntax_exception_type_test.rs b/azalea-brigadier/tests/exceptions/dynamic_command_syntax_exception_type_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/exceptions/dynamic_command_syntax_exception_type_test.rs
diff --git a/azalea-brigadier/tests/exceptions/simple_command_syntax_exception_type_test.rs b/azalea-brigadier/tests/exceptions/simple_command_syntax_exception_type_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/exceptions/simple_command_syntax_exception_type_test.rs
diff --git a/azalea-brigadier/tests/string_reader_test.rs b/azalea-brigadier/tests/string_reader_test.rs
new file mode 100755
index 00000000..5008eff8
--- /dev/null
+++ b/azalea-brigadier/tests/string_reader_test.rs
@@ -0,0 +1,612 @@
+use azalea_brigadier::{exceptions::BuiltInExceptions, string_reader::StringReader};
+
+#[test]
+fn can_read() {
+ let mut reader = StringReader::from("abc".to_string());
+ assert_eq!(reader.can_read(), true);
+ reader.skip(); // 'a'
+ assert_eq!(reader.can_read(), true);
+ reader.skip(); // 'b'
+ assert_eq!(reader.can_read(), true);
+ reader.skip(); // 'c'
+ assert_eq!(reader.can_read(), false);
+}
+
+#[test]
+fn get_remaining_length() {
+ let mut reader = StringReader::from("abc".to_string());
+ assert_eq!(reader.remaining_length(), 3);
+ reader.cursor = 1;
+ assert_eq!(reader.remaining_length(), 2);
+ reader.cursor = 2;
+ assert_eq!(reader.remaining_length(), 1);
+ reader.cursor = 3;
+ assert_eq!(reader.remaining_length(), 0);
+}
+
+#[test]
+fn can_read_length() {
+ let reader = StringReader::from("abc".to_string());
+ assert_eq!(reader.can_read_length(1), true);
+ assert_eq!(reader.can_read_length(2), true);
+ assert_eq!(reader.can_read_length(3), true);
+ assert_eq!(reader.can_read_length(4), false);
+ assert_eq!(reader.can_read_length(5), false);
+}
+
+#[test]
+fn peek() {
+ let mut reader = StringReader::from("abc".to_string());
+ assert_eq!(reader.peek(), 'a');
+ assert_eq!(reader.cursor(), 0);
+ reader.cursor = 2;
+ assert_eq!(reader.peek(), 'c');
+ assert_eq!(reader.cursor(), 2);
+}
+
+#[test]
+fn peek_length() {
+ let mut reader = StringReader::from("abc".to_string());
+ assert_eq!(reader.peek_offset(0), 'a');
+ assert_eq!(reader.peek_offset(2), 'c');
+ assert_eq!(reader.cursor(), 0);
+ reader.cursor = 1;
+ assert_eq!(reader.peek_offset(1), 'c');
+ assert_eq!(reader.cursor(), 1);
+}
+
+#[test]
+fn read() {
+ let mut reader = StringReader::from("abc".to_string());
+ assert_eq!(reader.read(), 'a');
+ assert_eq!(reader.read(), 'b');
+ assert_eq!(reader.read(), 'c');
+ assert_eq!(reader.cursor(), 3);
+}
+
+#[test]
+fn skip() {
+ let mut reader = StringReader::from("abc".to_string());
+ reader.skip();
+ assert_eq!(reader.cursor(), 1);
+}
+
+#[test]
+fn get_remaining() {
+ let mut reader = StringReader::from("Hello!".to_string());
+ assert_eq!(reader.remaining(), "Hello!");
+ reader.cursor = 3;
+ assert_eq!(reader.remaining(), "lo!");
+ reader.cursor = 6;
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn get_read() {
+ let mut reader = StringReader::from("Hello!".to_string());
+ assert_eq!(reader.get_read(), "");
+ reader.cursor = 3;
+ assert_eq!(reader.get_read(), "Hel");
+ reader.cursor = 6;
+ assert_eq!(reader.get_read(), "Hello!");
+}
+
+#[test]
+fn skip_whitespace_none() {
+ let mut reader = StringReader::from("Hello!".to_string());
+ reader.skip_whitespace();
+ assert_eq!(reader.cursor(), 0);
+}
+
+#[test]
+fn skip_whitespace_mixed() {
+ let mut reader = StringReader::from(" \t \t\nHello!".to_string());
+ reader.skip_whitespace();
+ assert_eq!(reader.cursor(), 5);
+}
+
+#[test]
+fn skip_whitespace_empty() {
+ let mut reader = StringReader::from("".to_string());
+ reader.skip_whitespace();
+ assert_eq!(reader.cursor(), 0);
+}
+
+#[test]
+fn read_unquoted_string() {
+ let mut reader = StringReader::from("hello world".to_string());
+ assert_eq!(reader.read_unquoted_string(), "hello");
+ assert_eq!(reader.get_read(), "hello");
+ assert_eq!(reader.remaining(), " world");
+}
+
+#[test]
+fn read_unquoted_string_empty() {
+ let mut reader = StringReader::from("".to_string());
+ assert_eq!(reader.read_unquoted_string(), "");
+ assert_eq!(reader.get_read(), "");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_unquoted_string_empty_with_remaining() {
+ let mut reader = StringReader::from(" hello world".to_string());
+ assert_eq!(reader.read_unquoted_string(), "");
+ assert_eq!(reader.get_read(), "");
+ assert_eq!(reader.remaining(), " hello world");
+}
+
+#[test]
+fn read_quoted_string() {
+ let mut reader = StringReader::from("\"hello world\"".to_string());
+ assert_eq!(reader.read_quoted_string().unwrap(), "hello world");
+ assert_eq!(reader.get_read(), "\"hello world\"");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_single_quoted_string() {
+ let mut reader = StringReader::from("'hello world'".to_string());
+ assert_eq!(reader.read_quoted_string().unwrap(), "hello world");
+ assert_eq!(reader.get_read(), "'hello world'");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_mixed_quoted_string_double_inside_single() {
+ let mut reader = StringReader::from("'hello \"world\"'".to_string());
+ assert_eq!(reader.read_quoted_string().unwrap(), "hello \"world\"");
+ assert_eq!(reader.get_read(), "'hello \"world\"'");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_mixed_quoted_string_single_inside_double() {
+ let mut reader = StringReader::from("\"hello 'world'\"".to_string());
+ assert_eq!(reader.read_quoted_string().unwrap(), "hello 'world'");
+ assert_eq!(reader.get_read(), "\"hello 'world'\"");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_quoted_string_empty_quoted() {
+ let mut reader = StringReader::from("".to_string());
+ assert_eq!(reader.read_quoted_string().unwrap(), "");
+ assert_eq!(reader.get_read(), "");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_quoted_string_empty_quoted_with_remaining() {
+ let mut reader = StringReader::from("\"\" hello world".to_string());
+ assert_eq!(reader.read_quoted_string().unwrap(), "");
+ assert_eq!(reader.get_read(), "\"\"");
+ assert_eq!(reader.remaining(), " hello world");
+}
+
+#[test]
+fn read_quoted_string_with_escaped_quote() {
+ let mut reader = StringReader::from("\"hello \\\"world\\\"\"".to_string());
+ assert_eq!(reader.read_quoted_string().unwrap(), "hello \"world\"");
+ assert_eq!(reader.get_read(), "\"hello \\\"world\\\"\"");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_quoted_string_with_escaped_escapes() {
+ let mut reader = StringReader::from("\"\\\\o/\"".to_string());
+ assert_eq!(reader.read_quoted_string().unwrap(), "\\o/");
+ assert_eq!(reader.get_read(), "\"\\\\o/\"");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_quoted_string_with_remaining() {
+ let mut reader = StringReader::from("\"hello world\" foo bar".to_string());
+ assert_eq!(reader.read_quoted_string().unwrap(), "hello world");
+ assert_eq!(reader.get_read(), "\"hello world\"");
+ assert_eq!(reader.remaining(), " foo bar");
+}
+
+#[test]
+fn read_quoted_string_with_immediate_remaining() {
+ let mut reader = StringReader::from("\"hello world\"foo bar".to_string());
+ assert_eq!(reader.read_quoted_string().unwrap(), "hello world");
+ assert_eq!(reader.get_read(), "\"hello world\"");
+ assert_eq!(reader.remaining(), "foo bar");
+}
+
+#[test]
+fn read_quoted_string_no_open() {
+ let mut reader = StringReader::from("hello world\"".to_string());
+ 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.cursor(), Some(0));
+ }
+}
+
+#[test]
+fn read_quoted_string_no_close() {
+ let mut reader = StringReader::from("\"hello world".to_string());
+ 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.cursor(), Some(12));
+ }
+}
+
+#[test]
+fn read_quoted_string_invalid_escape() {
+ let mut reader = StringReader::from("\"hello\\nworld\"".to_string());
+ let result = reader.read_quoted_string();
+ assert!(result.is_err());
+ if let Err(e) = result {
+ assert_eq!(
+ e.get_type(),
+ &BuiltInExceptions::ReaderInvalidEscape { character: 'n' }
+ );
+ assert_eq!(e.cursor(), Some(7));
+ }
+}
+
+#[test]
+fn read_quoted_string_invalid_quote_escape() {
+ let mut reader = StringReader::from("'hello\\\"\'world".to_string());
+ let result = reader.read_quoted_string();
+ assert!(result.is_err());
+ if let Err(e) = result {
+ assert_eq!(
+ e.get_type(),
+ &BuiltInExceptions::ReaderInvalidEscape { character: '"' }
+ );
+ assert_eq!(e.cursor(), Some(7));
+ }
+}
+
+#[test]
+fn read_string_no_quotes() {
+ let mut reader = StringReader::from("hello world".to_string());
+ assert_eq!(reader.read_string().unwrap(), "hello");
+ assert_eq!(reader.get_read(), "hello");
+ assert_eq!(reader.remaining(), " world");
+}
+
+#[test]
+fn read_string_single_quotes() {
+ let mut reader = StringReader::from("'hello world'".to_string());
+ assert_eq!(reader.read_string().unwrap(), "hello world");
+ assert_eq!(reader.get_read(), "'hello world'");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_string_double_quotes() {
+ let mut reader = StringReader::from("\"hello world\"".to_string());
+ assert_eq!(reader.read_string().unwrap(), "hello world");
+ assert_eq!(reader.get_read(), "\"hello world\"");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_int() {
+ let mut reader = StringReader::from("1234567890".to_string());
+ assert_eq!(reader.read_int().unwrap(), 1234567890);
+ assert_eq!(reader.get_read(), "1234567890");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_int_negative() {
+ let mut reader = StringReader::from("-1234567890".to_string());
+ assert_eq!(reader.read_int().unwrap(), -1234567890);
+ assert_eq!(reader.get_read(), "-1234567890");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_int_invalid() {
+ let mut reader = StringReader::from("12.34".to_string());
+ let result = reader.read_int();
+ assert!(result.is_err());
+ if let Err(e) = result {
+ assert_eq!(
+ e.get_type(),
+ &BuiltInExceptions::ReaderInvalidInt {
+ value: "12.34".to_string()
+ }
+ );
+ assert_eq!(e.cursor(), Some(0));
+ }
+}
+
+#[test]
+fn read_int_none() {
+ let mut reader = StringReader::from("".to_string());
+ let result = reader.read_int();
+ assert!(result.is_err());
+ if let Err(e) = result {
+ assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedInt);
+ assert_eq!(e.cursor(), Some(0));
+ }
+}
+
+#[test]
+fn read_int_with_remaining() {
+ let mut reader = StringReader::from("1234567890 foo bar".to_string());
+ assert_eq!(reader.read_int().unwrap(), 1234567890);
+ assert_eq!(reader.get_read(), "1234567890");
+ assert_eq!(reader.remaining(), " foo bar");
+}
+
+#[test]
+fn read_int_with_remaining_immediate() {
+ let mut reader = StringReader::from("1234567890foo bar".to_string());
+ assert_eq!(reader.read_int().unwrap(), 1234567890);
+ assert_eq!(reader.get_read(), "1234567890");
+ assert_eq!(reader.remaining(), "foo bar");
+}
+
+#[test]
+fn read_long() {
+ let mut reader = StringReader::from("1234567890".to_string());
+ assert_eq!(reader.read_long().unwrap(), 1234567890);
+ assert_eq!(reader.get_read(), "1234567890");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_long_negative() {
+ let mut reader = StringReader::from("-1234567890".to_string());
+ assert_eq!(reader.read_long().unwrap(), -1234567890);
+ assert_eq!(reader.get_read(), "-1234567890");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_long_invalid() {
+ let mut reader = StringReader::from("12.34".to_string());
+ let result = reader.read_long();
+ assert!(result.is_err());
+ if let Err(e) = result {
+ assert_eq!(
+ e.get_type(),
+ &BuiltInExceptions::ReaderInvalidLong {
+ value: "12.34".to_string()
+ }
+ );
+ assert_eq!(e.cursor(), Some(0));
+ }
+}
+
+#[test]
+fn read_long_none() {
+ let mut reader = StringReader::from("".to_string());
+ let result = reader.read_long();
+ assert!(result.is_err());
+ if let Err(e) = result {
+ assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedLong);
+ assert_eq!(e.cursor(), Some(0));
+ }
+}
+
+#[test]
+fn read_long_with_remaining() {
+ let mut reader = StringReader::from("1234567890 foo bar".to_string());
+ assert_eq!(reader.read_long().unwrap(), 1234567890);
+ assert_eq!(reader.get_read(), "1234567890");
+ assert_eq!(reader.remaining(), " foo bar");
+}
+
+#[test]
+fn read_long_with_remaining_immediate() {
+ let mut reader = StringReader::from("1234567890foo bar".to_string());
+ assert_eq!(reader.read_long().unwrap(), 1234567890);
+ assert_eq!(reader.get_read(), "1234567890");
+ assert_eq!(reader.remaining(), "foo bar");
+}
+
+#[test]
+fn read_double() {
+ let mut reader = StringReader::from("123".to_string());
+ assert_eq!(reader.read_double().unwrap(), 123.0);
+ assert_eq!(reader.get_read(), "123");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_double_with_decimal() {
+ let mut reader = StringReader::from("12.34".to_string());
+ assert_eq!(reader.read_double().unwrap(), 12.34);
+ assert_eq!(reader.get_read(), "12.34");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_double_negative() {
+ let mut reader = StringReader::from("-123".to_string());
+ assert_eq!(reader.read_double().unwrap(), -123.0);
+ assert_eq!(reader.get_read(), "-123");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_double_invalid() {
+ let mut reader = StringReader::from("12.34.56".to_string());
+ let result = reader.read_double();
+ assert!(result.is_err());
+ if let Err(e) = result {
+ assert_eq!(
+ e.get_type(),
+ &BuiltInExceptions::ReaderInvalidDouble {
+ value: "12.34.56".to_string()
+ }
+ );
+ assert_eq!(e.cursor(), Some(0));
+ }
+}
+
+#[test]
+fn read_double_none() {
+ let mut reader = StringReader::from("".to_string());
+ let result = reader.read_double();
+ assert!(result.is_err());
+ if let Err(e) = result {
+ assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedDouble);
+ assert_eq!(e.cursor(), Some(0));
+ }
+}
+
+#[test]
+fn read_double_with_remaining() {
+ let mut reader = StringReader::from("12.34 foo bar".to_string());
+ assert_eq!(reader.read_double().unwrap(), 12.34);
+ assert_eq!(reader.get_read(), "12.34");
+ assert_eq!(reader.remaining(), " foo bar");
+}
+
+#[test]
+fn read_double_with_remaining_immediate() {
+ let mut reader = StringReader::from("12.34foo bar".to_string());
+ assert_eq!(reader.read_double().unwrap(), 12.34);
+ assert_eq!(reader.get_read(), "12.34");
+ assert_eq!(reader.remaining(), "foo bar");
+}
+
+#[test]
+fn read_float() {
+ let mut reader = StringReader::from("123".to_string());
+ assert_eq!(reader.read_float().unwrap(), 123.0f32);
+ assert_eq!(reader.get_read(), "123");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_float_with_decimal() {
+ let mut reader = StringReader::from("12.34".to_string());
+ assert_eq!(reader.read_float().unwrap(), 12.34f32);
+ assert_eq!(reader.get_read(), "12.34");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_float_negative() {
+ let mut reader = StringReader::from("-123".to_string());
+ assert_eq!(reader.read_float().unwrap(), -123.0f32);
+ assert_eq!(reader.get_read(), "-123");
+ assert_eq!(reader.remaining(), "");
+}
+
+#[test]
+fn read_float_invalid() {
+ let mut reader = StringReader::from("12.34.56".to_string());
+ let result = reader.read_float();
+ assert!(result.is_err());
+ if let Err(e) = result {
+ assert_eq!(
+ e.get_type(),
+ &BuiltInExceptions::ReaderInvalidFloat {
+ value: "12.34.56".to_string()
+ }
+ );
+ assert_eq!(e.cursor(), Some(0));
+ }
+}
+
+#[test]
+fn read_float_none() {
+ let mut reader = StringReader::from("".to_string());
+ let result = reader.read_float();
+ assert!(result.is_err());
+ if let Err(e) = result {
+ assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedFloat);
+ assert_eq!(e.cursor(), Some(0));
+ }
+}
+
+#[test]
+fn read_float_with_remaining() {
+ let mut reader = StringReader::from("12.34 foo bar".to_string());
+ assert_eq!(reader.read_float().unwrap(), 12.34f32);
+ assert_eq!(reader.get_read(), "12.34");
+ assert_eq!(reader.remaining(), " foo bar");
+}
+
+#[test]
+fn read_float_with_remaining_immediate() {
+ let mut reader = StringReader::from("12.34foo bar".to_string());
+ assert_eq!(reader.read_float().unwrap(), 12.34f32);
+ assert_eq!(reader.get_read(), "12.34");
+ assert_eq!(reader.remaining(), "foo bar");
+}
+
+#[test]
+fn expect_correct() {
+ let mut reader = StringReader::from("abc".to_string());
+ reader.expect('a').unwrap();
+ assert_eq!(reader.cursor(), 1);
+}
+
+#[test]
+fn expect_incorrect() {
+ let mut reader = StringReader::from("bcd".to_string());
+ let result = reader.expect('a');
+ assert!(result.is_err());
+ if let Err(e) = result {
+ assert_eq!(
+ e.get_type(),
+ &BuiltInExceptions::ReaderExpectedSymbol { symbol: 'a' }
+ );
+ assert_eq!(e.cursor(), Some(0));
+ }
+}
+
+#[test]
+fn expect_none() {
+ let mut reader = StringReader::from("".to_string());
+ let result = reader.expect('a');
+ assert!(result.is_err());
+ if let Err(e) = result {
+ assert_eq!(
+ e.get_type(),
+ &BuiltInExceptions::ReaderExpectedSymbol { symbol: 'a' }
+ );
+ assert_eq!(e.cursor(), Some(0));
+ }
+}
+
+#[test]
+fn read_boolean_correct() {
+ let mut reader = StringReader::from("true".to_string());
+ assert_eq!(reader.read_boolean().unwrap(), true);
+ assert_eq!(reader.get_read(), "true");
+}
+
+#[test]
+fn read_boolean_incorrect() {
+ let mut reader = StringReader::from("tuesday".to_string());
+ let result = reader.read_boolean();
+ assert!(result.is_err());
+ if let Err(e) = result {
+ assert_eq!(
+ e.get_type(),
+ &BuiltInExceptions::ReaderInvalidBool {
+ value: "tuesday".to_string()
+ }
+ );
+ assert_eq!(e.cursor(), Some(0));
+ }
+}
+
+#[test]
+fn read_boolean_none() {
+ let mut reader = StringReader::from("".to_string());
+ let result = reader.read_boolean();
+ assert!(result.is_err());
+ if let Err(e) = result {
+ assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedBool);
+ assert_eq!(e.cursor(), Some(0));
+ }
+}
diff --git a/azalea-brigadier/tests/suggestion/suggestion_test.rs b/azalea-brigadier/tests/suggestion/suggestion_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/suggestion/suggestion_test.rs
diff --git a/azalea-brigadier/tests/suggestion/suggestions_builder_test.rs b/azalea-brigadier/tests/suggestion/suggestions_builder_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/suggestion/suggestions_builder_test.rs
diff --git a/azalea-brigadier/tests/suggestion/suggestions_test.rs b/azalea-brigadier/tests/suggestion/suggestions_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/suggestion/suggestions_test.rs
diff --git a/azalea-brigadier/tests/tree/abstract_command_node_test.rs b/azalea-brigadier/tests/tree/abstract_command_node_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/tree/abstract_command_node_test.rs
diff --git a/azalea-brigadier/tests/tree/argument_command_node_test.rs b/azalea-brigadier/tests/tree/argument_command_node_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/tree/argument_command_node_test.rs
diff --git a/azalea-brigadier/tests/tree/literal_command_node_test.rs b/azalea-brigadier/tests/tree/literal_command_node_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/tree/literal_command_node_test.rs
diff --git a/azalea-brigadier/tests/tree/root_command_node_test.rs b/azalea-brigadier/tests/tree/root_command_node_test.rs
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/azalea-brigadier/tests/tree/root_command_node_test.rs
diff --git a/azalea-chat/Cargo.toml b/azalea-chat/Cargo.toml
index 7019e5bf..7019e5bf 100644..100755
--- a/azalea-chat/Cargo.toml
+++ b/azalea-chat/Cargo.toml
diff --git a/azalea-chat/README.md b/azalea-chat/README.md
new file mode 100755
index 00000000..2bc9d418
--- /dev/null
+++ b/azalea-chat/README.md
@@ -0,0 +1,3 @@
+# Azalea Chat
+
+Parse Minecraft chat messages.
diff --git a/azalea-chat/src/base_component.rs b/azalea-chat/src/base_component.rs
index fa39a11c..fa39a11c 100644..100755
--- a/azalea-chat/src/base_component.rs
+++ b/azalea-chat/src/base_component.rs
diff --git a/azalea-chat/src/component.rs b/azalea-chat/src/component.rs
index 9f065c47..9f065c47 100644..100755
--- a/azalea-chat/src/component.rs
+++ b/azalea-chat/src/component.rs
diff --git a/azalea-chat/src/events.rs b/azalea-chat/src/events.rs
index a547169e..a547169e 100644..100755
--- a/azalea-chat/src/events.rs
+++ b/azalea-chat/src/events.rs
diff --git a/azalea-chat/src/lib.rs b/azalea-chat/src/lib.rs
index b7035e13..b7035e13 100644..100755
--- a/azalea-chat/src/lib.rs
+++ b/azalea-chat/src/lib.rs
diff --git a/azalea-chat/src/style.rs b/azalea-chat/src/style.rs
index 4e3b24de..4e3b24de 100644..100755
--- a/azalea-chat/src/style.rs
+++ b/azalea-chat/src/style.rs
diff --git a/azalea-chat/src/text_component.rs b/azalea-chat/src/text_component.rs
index 6c43f8b7..6c43f8b7 100644..100755
--- a/azalea-chat/src/text_component.rs
+++ b/azalea-chat/src/text_component.rs
diff --git a/azalea-chat/src/translatable_component.rs b/azalea-chat/src/translatable_component.rs
index 0709f7bf..0709f7bf 100644..100755
--- a/azalea-chat/src/translatable_component.rs
+++ b/azalea-chat/src/translatable_component.rs
diff --git a/azalea-chat/tests/integration_test.rs b/azalea-chat/tests/integration_test.rs
index 1278adfa..1278adfa 100644..100755
--- a/azalea-chat/tests/integration_test.rs
+++ b/azalea-chat/tests/integration_test.rs
diff --git a/azalea-client/Cargo.toml b/azalea-client/Cargo.toml
index 7e22f727..7e22f727 100644..100755
--- a/azalea-client/Cargo.toml
+++ b/azalea-client/Cargo.toml
diff --git a/azalea-client/README.md b/azalea-client/README.md
new file mode 100755
index 00000000..328eb0d5
--- /dev/null
+++ b/azalea-client/README.md
@@ -0,0 +1,3 @@
+# Azalea Client
+
+A lower level Minecraft bot library.
diff --git a/azalea-client/src/connect.rs b/azalea-client/src/connect.rs
index 7d987eab..884648b4 100644..100755
--- a/azalea-client/src/connect.rs
+++ b/azalea-client/src/connect.rs
@@ -49,10 +49,18 @@ pub async fn join_server(address: &ServerAddress) -> Result<(), String> {
println!("Got profile {:?}", p.game_profile);
break conn.game();
}
- _ => panic!("unhandled packet"),
+ LoginPacket::ServerboundHelloPacket(p) => {
+ println!("Got hello {:?}", p);
+ }
+ LoginPacket::ClientboundLoginDisconnectPacket(p) => {
+ println!("Got disconnect {:?}", p);
+ }
+ LoginPacket::ClientboundCustomQueryPacket(p) => {
+ println!("Got custom query {:?}", p);
+ }
},
Err(e) => {
- println!("Error: {:?}", e);
+ panic!("Error: {:?}", e);
}
}
};
@@ -65,13 +73,30 @@ pub async fn join_server(address: &ServerAddress) -> Result<(), String> {
GamePacket::ClientboundLoginPacket(p) => {
println!("Got login packet {:?}", p);
}
- // GamePacket::ClientboundKeepAlivePacket(p) => {
- // println!("Got keep alive packet {:?}", p.keep_alive_id);
- // }
- // GamePacket::ClientboundChatMessagePacket(p) => {
- // println!("Got chat message packet {:?}", p.message);
- // }
- _ => panic!("unhandled packet"),
+ GamePacket::ClientboundUpdateViewDistancePacket(p) => {
+ println!("Got view distance packet {:?}", p);
+ }
+ GamePacket::ClientboundCustomPayloadPacket(p) => {
+ println!("Got custom payload packet {:?}", p);
+ }
+ GamePacket::ClientboundChangeDifficultyPacket(p) => {
+ println!("Got difficulty packet {:?}", p);
+ }
+ GamePacket::ClientboundDeclareCommandsPacket(p) => {
+ println!("Got declare commands packet {:?}", p);
+ }
+ GamePacket::ClientboundPlayerAbilitiesPacket(p) => {
+ println!("Got player abilities packet {:?}", p);
+ }
+ GamePacket::ClientboundSetCarriedItemPacket(p) => {
+ println!("Got set carried item packet {:?}", p);
+ }
+ GamePacket::ClientboundUpdateTagsPacket(p) => {
+ println!("Got update tags packet {:?}", p);
+ }
+ GamePacket::ClientboundDisconnectPacket(p) => {
+ println!("Got login disconnect packet {:?}", p);
+ }
},
Err(e) => {
panic!("Error: {:?}", e);
diff --git a/azalea-client/src/crypt.rs b/azalea-client/src/crypt.rs
index e69de29b..e69de29b 100644..100755
--- a/azalea-client/src/crypt.rs
+++ b/azalea-client/src/crypt.rs
diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs
index 8c1bcfe9..8c1bcfe9 100644..100755
--- a/azalea-client/src/lib.rs
+++ b/azalea-client/src/lib.rs
diff --git a/azalea-client/src/ping.rs b/azalea-client/src/ping.rs
index 87ccdf66..8ecff7ca 100644..100755
--- a/azalea-client/src/ping.rs
+++ b/azalea-client/src/ping.rs
@@ -38,7 +38,7 @@ pub async fn ping_server(
let packet = conn.read().await.unwrap();
match packet {
- StatusPacket::ClientboundStatusResponsePacket(p) => Ok(*p),
+ StatusPacket::ClientboundStatusResponsePacket(p) => Ok(p),
_ => Err("Invalid packet type".to_string()),
}
}
diff --git a/azalea-core/Cargo.toml b/azalea-core/Cargo.toml
index b0139999..b0139999 100644..100755
--- a/azalea-core/Cargo.toml
+++ b/azalea-core/Cargo.toml
diff --git a/azalea-core/src/difficulty.rs b/azalea-core/src/difficulty.rs
new file mode 100755
index 00000000..21e980ba
--- /dev/null
+++ b/azalea-core/src/difficulty.rs
@@ -0,0 +1,96 @@
+use std::fmt::{Debug, Error, Formatter};
+
+#[derive(Hash, Clone, Debug, PartialEq)]
+pub enum Difficulty {
+ PEACEFUL,
+ EASY,
+ NORMAL,
+ HARD,
+}
+
+pub enum Err {
+ InvalidDifficulty(String),
+}
+
+impl Debug for Err {
+ fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+ match self {
+ Err::InvalidDifficulty(s) => write!(f, "Invalid difficulty: {}", s),
+ }
+ }
+}
+
+impl Difficulty {
+ pub fn name(&self) -> &'static str {
+ match self {
+ Difficulty::PEACEFUL => "peaceful",
+ Difficulty::EASY => "easy",
+ Difficulty::NORMAL => "normal",
+ Difficulty::HARD => "hard",
+ }
+ }
+
+ pub fn from_name(name: &str) -> Result<Difficulty, Err> {
+ match name {
+ "peaceful" => Ok(Difficulty::PEACEFUL),
+ "easy" => Ok(Difficulty::EASY),
+ "normal" => Ok(Difficulty::NORMAL),
+ "hard" => Ok(Difficulty::HARD),
+ _ => Err(Err::InvalidDifficulty(name.to_string())),
+ }
+ }
+
+ pub fn by_id(id: u8) -> Difficulty {
+ match id % 4 {
+ 0 => Difficulty::PEACEFUL,
+ 1 => Difficulty::EASY,
+ 2 => Difficulty::NORMAL,
+ 3 => Difficulty::HARD,
+ // this shouldn't be possible because of the modulo, so panicking is fine
+ _ => panic!("Unknown difficulty id: {}", id),
+ }
+ }
+
+ pub fn id(&self) -> u8 {
+ match self {
+ Difficulty::PEACEFUL => 0,
+ Difficulty::EASY => 1,
+ Difficulty::NORMAL => 2,
+ Difficulty::HARD => 3,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_difficulty_from_name() {
+ assert_eq!(
+ Difficulty::PEACEFUL,
+ Difficulty::from_name("peaceful").unwrap()
+ );
+ assert_eq!(Difficulty::EASY, Difficulty::from_name("easy").unwrap());
+ assert_eq!(Difficulty::NORMAL, Difficulty::from_name("normal").unwrap());
+ assert_eq!(Difficulty::HARD, Difficulty::from_name("hard").unwrap());
+ assert!(Difficulty::from_name("invalid").is_err());
+ }
+
+ #[test]
+ fn test_difficulty_id() {
+ assert_eq!(0, Difficulty::PEACEFUL.id());
+ assert_eq!(1, Difficulty::EASY.id());
+ assert_eq!(2, Difficulty::NORMAL.id());
+ assert_eq!(3, Difficulty::HARD.id());
+ assert_eq!(4, Difficulty::PEACEFUL.id());
+ }
+
+ #[test]
+ fn test_difficulty_name() {
+ assert_eq!("peaceful", Difficulty::PEACEFUL.name());
+ assert_eq!("easy", Difficulty::EASY.name());
+ assert_eq!("normal", Difficulty::NORMAL.name());
+ assert_eq!("hard", Difficulty::HARD.name());
+ }
+}
diff --git a/azalea-core/src/game_type.rs b/azalea-core/src/game_type.rs
index b6ff479d..f5b9fb38 100644..100755
--- a/azalea-core/src/game_type.rs
+++ b/azalea-core/src/game_type.rs
@@ -1,5 +1,3 @@
-use azalea_chat;
-
#[derive(Hash, Clone, Debug)]
pub enum GameType {
SURVIVAL,
diff --git a/azalea-core/src/lib.rs b/azalea-core/src/lib.rs
index 887d1686..cdf07c43 100644..100755
--- a/azalea-core/src/lib.rs
+++ b/azalea-core/src/lib.rs
@@ -1,14 +1,6 @@
//! Random miscellaneous things like UUIDs that don't deserve their own crate.
+pub mod difficulty;
pub mod game_type;
pub mod resource_location;
pub mod serializable_uuid;
-
-#[cfg(test)]
-mod tests {
- #[test]
- fn it_works() {
- let result = 2 + 2;
- assert_eq!(result, 4);
- }
-}
diff --git a/azalea-core/src/resource_location.rs b/azalea-core/src/resource_location.rs
index 7e28a2a2..7e28a2a2 100644..100755
--- a/azalea-core/src/resource_location.rs
+++ b/azalea-core/src/resource_location.rs
diff --git a/azalea-core/src/serializable_uuid.rs b/azalea-core/src/serializable_uuid.rs
index f8c03b60..f8c03b60 100644..100755
--- a/azalea-core/src/serializable_uuid.rs
+++ b/azalea-core/src/serializable_uuid.rs
diff --git a/azalea-nbt/Cargo.toml b/azalea-nbt/Cargo.toml
index 5d28d136..d4817233 100644..100755
--- a/azalea-nbt/Cargo.toml
+++ b/azalea-nbt/Cargo.toml
@@ -16,11 +16,14 @@ tokio = "^1.15.0"
[dev-dependencies]
criterion = {version = "^0.3.5", features = ["html_reports", "async_tokio"]}
-tokio = {version = "^1.15.0", features = ["fs"]}
+tokio = {version = "^1.15.0", features = ["fs", "io-util", "macros", "rt", "rt-multi-thread"]}
[profile.release]
lto = true
+[profile.bench]
+debug = true
+
[[bench]]
harness = false
name = "my_benchmark"
diff --git a/azalea-nbt/README.md b/azalea-nbt/README.md
index 0573bce2..19498cf3 100644..100755
--- a/azalea-nbt/README.md
+++ b/azalea-nbt/README.md
@@ -1,6 +1,3 @@
# Azalea NBT
-Deserialize Minecraft NBT. This is somewhat based on [Hermatite NBT](https://github.com/PistonDevelopers/hematite_nbt).
-
-
-
+A fast NBT serializer and deserializer.
diff --git a/azalea-nbt/benches/my_benchmark.rs b/azalea-nbt/benches/my_benchmark.rs
index 7b44d610..2fc7ec2f 100644..100755
--- a/azalea-nbt/benches/my_benchmark.rs
+++ b/azalea-nbt/benches/my_benchmark.rs
@@ -3,7 +3,7 @@ use criterion::{criterion_group, criterion_main, Criterion, Throughput};
use flate2::read::GzDecoder;
use std::{
fs::File,
- io::{self, Cursor, Read, Seek, SeekFrom},
+ io::{self, Read, Seek, SeekFrom},
};
fn bench_serialize(filename: &str, c: &mut Criterion) {
@@ -28,12 +28,13 @@ fn bench_serialize(filename: &str, c: &mut Criterion) {
group.throughput(Throughput::Bytes(decoded_src.len() as u64));
- // idk if this is criterion's fault or rust's fault but the async benchmark doesn't compile
// group.bench_function("Decode", |b| {
- // b.to_async(tokio::runtime::Runtime::new().unwrap();).iter(|| async {
- // decoded_src_stream.seek(SeekFrom::Start(0)).unwrap();
- // Tag::read(&mut decoded_src_stream).await.unwrap();
- // })
+ // b.to_async(tokio::runtime::Runtime::new().unwrap())
+ // .iter(|| async {
+ // let mut owned_decoded_src_stream = decoded_src_stream.clone();
+ // owned_decoded_src_stream.seek(SeekFrom::Start(0)).unwrap();
+ // Tag::read(&mut owned_decoded_src_stream).await.unwrap();
+ // })
// });
group.bench_function("Encode", |b| {
diff --git a/azalea-nbt/src/decode.rs b/azalea-nbt/src/decode.rs
index 41689a46..3e2f7adb 100644..100755
--- a/azalea-nbt/src/decode.rs
+++ b/azalea-nbt/src/decode.rs
@@ -11,16 +11,17 @@ async fn read_string<R>(stream: &mut R) -> Result<String, Error>
where
R: AsyncRead + std::marker::Unpin,
{
- let length = stream.read_u16().await.map_err(|_| Error::InvalidTag)?;
+ let length = stream.read_u16().await?;
let mut buf = Vec::with_capacity(length as usize);
for _ in 0..length {
- buf.push(stream.read_u8().await.map_err(|_| Error::InvalidTag)?);
+ buf.push(stream.read_u8().await?);
}
- String::from_utf8(buf).map_err(|_| Error::InvalidTag)
+ Ok(String::from_utf8(buf)?)
}
impl Tag {
+ #[inline]
#[async_recursion]
async fn read_known<R>(stream: &mut R, id: u8) -> Result<Tag, Error>
where
@@ -31,26 +32,26 @@ impl Tag {
// a TAG_Compound, and is not named despite being in a TAG_Compound
0 => Tag::End,
// A single signed byte
- 1 => Tag::Byte(stream.read_i8().await.map_err(|_| Error::InvalidTag)?),
+ 1 => Tag::Byte(stream.read_i8().await?),
// A single signed, big endian 16 bit integer
- 2 => Tag::Short(stream.read_i16().await.map_err(|_| Error::InvalidTag)?),
+ 2 => Tag::Short(stream.read_i16().await?),
// A single signed, big endian 32 bit integer
- 3 => Tag::Int(stream.read_i32().await.map_err(|_| Error::InvalidTag)?),
+ 3 => Tag::Int(stream.read_i32().await?),
// A single signed, big endian 64 bit integer
- 4 => Tag::Long(stream.read_i64().await.map_err(|_| Error::InvalidTag)?),
+ 4 => Tag::Long(stream.read_i64().await?),
// A single, big endian IEEE-754 single-precision floating point
// number (NaN possible)
- 5 => Tag::Float(stream.read_f32().await.map_err(|_| Error::InvalidTag)?),
+ 5 => Tag::Float(stream.read_f32().await?),
// A single, big endian IEEE-754 double-precision floating point
// number (NaN possible)
- 6 => Tag::Double(stream.read_f64().await.map_err(|_| Error::InvalidTag)?),
+ 6 => Tag::Double(stream.read_f64().await?),
// A length-prefixed array of signed bytes. The prefix is a signed
// integer (thus 4 bytes)
7 => {
- let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?;
+ let length = stream.read_i32().await?;
let mut bytes = Vec::with_capacity(length as usize);
for _ in 0..length {
- bytes.push(stream.read_i8().await.map_err(|_| Error::InvalidTag)?);
+ bytes.push(stream.read_i8().await?);
}
Tag::ByteArray(bytes)
}
@@ -67,8 +68,8 @@ impl Tag {
// another reference implementation by Mojang uses 1 instead;
// parsers should accept any type if the length is <= 0).
9 => {
- let type_id = stream.read_u8().await.map_err(|_| Error::InvalidTag)?;
- let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?;
+ let type_id = stream.read_u8().await?;
+ let length = stream.read_i32().await?;
let mut list = Vec::with_capacity(length as usize);
for _ in 0..length {
list.push(Tag::read_known(stream, type_id).await?);
@@ -94,20 +95,20 @@ impl Tag {
// signed integer (thus 4 bytes) and indicates the number of 4 byte
// integers.
11 => {
- let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?;
+ let length = stream.read_i32().await?;
let mut ints = Vec::with_capacity(length as usize);
for _ in 0..length {
- ints.push(stream.read_i32().await.map_err(|_| Error::InvalidTag)?);
+ ints.push(stream.read_i32().await?);
}
Tag::IntArray(ints)
}
// A length-prefixed array of signed longs. The prefix is a signed
// integer (thus 4 bytes) and indicates the number of 8 byte longs.
12 => {
- let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?;
+ let length = stream.read_i32().await?;
let mut longs = Vec::with_capacity(length as usize);
for _ in 0..length {
- longs.push(stream.read_i64().await.map_err(|_| Error::InvalidTag)?);
+ longs.push(stream.read_i64().await?);
}
Tag::LongArray(longs)
}
diff --git a/azalea-nbt/src/encode.rs b/azalea-nbt/src/encode.rs
index 9ce4faf4..20d13793 100644..100755
--- a/azalea-nbt/src/encode.rs
+++ b/azalea-nbt/src/encode.rs
@@ -2,96 +2,197 @@ use crate::Error;
use crate::Tag;
use byteorder::{WriteBytesExt, BE};
use flate2::write::{GzEncoder, ZlibEncoder};
+use std::collections::HashMap;
use std::io::Write;
+// who needs friends when you've got code that runs in nanoseconds?
+
#[inline]
fn write_string(writer: &mut dyn Write, string: &str) -> Result<(), Error> {
- writer
- .write_i16::<BE>(string.len() as i16)
- .map_err(|_| Error::WriteError)?;
- writer
- .write_all(string.as_bytes())
- .map_err(|_| Error::WriteError)?;
+ writer.write_i16::<BE>(string.len() as i16)?;
+ writer.write_all(string.as_bytes())?;
Ok(())
}
-impl Tag {
- pub fn write_without_end(&self, writer: &mut dyn Write) -> Result<(), Error> {
- match self {
+#[inline]
+fn write_compound(
+ writer: &mut dyn Write,
+ value: &HashMap<String, Tag>,
+ end_tag: bool,
+) -> Result<(), Error> {
+ for (key, tag) in value {
+ match tag {
Tag::End => {}
- Tag::Byte(value) => writer.write_i8(*value).map_err(|_| Error::WriteError)?,
- Tag::Short(value) => writer
- .write_i16::<BE>(*value)
- .map_err(|_| Error::WriteError)?,
- Tag::Int(value) => writer
- .write_i32::<BE>(*value)
- .map_err(|_| Error::WriteError)?,
- Tag::Long(value) => writer
- .write_i64::<BE>(*value)
- .map_err(|_| Error::WriteError)?,
- Tag::Float(value) => writer
- .write_f32::<BE>(*value)
- .map_err(|_| Error::WriteError)?,
- Tag::Double(value) => writer
- .write_f64::<BE>(*value)
- .map_err(|_| Error::WriteError)?,
+ Tag::Byte(value) => {
+ writer.write_u8(1)?;
+ write_string(writer, key)?;
+ writer.write_i8(*value)?
+ }
+ Tag::Short(value) => {
+ writer.write_u8(2)?;
+ write_string(writer, key)?;
+ writer.write_i16::<BE>(*value)?
+ }
+ Tag::Int(value) => {
+ writer.write_u8(3)?;
+ write_string(writer, key)?;
+ writer.write_i32::<BE>(*value)?
+ }
+ Tag::Long(value) => {
+ writer.write_u8(4)?;
+ write_string(writer, key)?;
+ writer.write_i64::<BE>(*value)?
+ }
+ Tag::Float(value) => {
+ writer.write_u8(5)?;
+ write_string(writer, key)?;
+ writer.write_f32::<BE>(*value)?
+ }
+ Tag::Double(value) => {
+ writer.write_u8(6)?;
+ write_string(writer, key)?;
+ writer.write_f64::<BE>(*value)?
+ }
Tag::ByteArray(value) => {
- writer
- .write_i32::<BE>(value.len() as i32)
- .map_err(|_| Error::WriteError)?;
+ writer.write_u8(7)?;
+ write_string(writer, key)?;
+ writer.write_i32::<BE>(value.len() as i32)?;
for &byte in value {
- writer.write_i8(byte).map_err(|_| Error::WriteError)?;
+ writer.write_i8(byte)?;
}
}
Tag::String(value) => {
- write_string(writer, value)?;
+ writer.write_u8(8)?;
+ write_string(writer, key)?;
+ write_string(writer, value)?
}
Tag::List(value) => {
- // we just get the type from the first item, or default the type to END
- if value.is_empty() {
- writer.write_i8(0).map_err(|_| Error::WriteError)?;
- writer.write_i32::<BE>(0).map_err(|_| Error::WriteError)?;
- } else {
- let type_id = value[0].id();
- writer.write_u8(type_id).map_err(|_| Error::WriteError)?;
- writer
- .write_i32::<BE>(value.len() as i32)
- .map_err(|_| Error::WriteError)?;
- for tag in value {
- tag.write_without_end(writer)?;
- }
- }
+ writer.write_u8(9)?;
+ write_string(writer, key)?;
+ write_list(writer, value)?
}
Tag::Compound(value) => {
- for (key, tag) in value {
- writer.write_u8(tag.id()).map_err(|_| Error::WriteError)?;
- write_string(writer, key)?;
- tag.write_without_end(writer)?;
- }
- writer
- .write_u8(Tag::End.id())
- .map_err(|_| Error::WriteError)?;
+ writer.write_u8(10)?;
+ write_string(writer, key)?;
+ write_compound(writer, value, true)?
}
Tag::IntArray(value) => {
- writer
- .write_i32::<BE>(value.len() as i32)
- .map_err(|_| Error::WriteError)?;
+ writer.write_u8(11)?;
+ write_string(writer, key)?;
+ writer.write_i32::<BE>(value.len() as i32)?;
for &int in value {
- writer.write_i32::<BE>(int).map_err(|_| Error::WriteError)?;
+ writer.write_i32::<BE>(int)?;
}
}
Tag::LongArray(value) => {
- writer
- .write_i32::<BE>(value.len() as i32)
- .map_err(|_| Error::WriteError)?;
+ writer.write_u8(12)?;
+ write_string(writer, key)?;
+ writer.write_i32::<BE>(value.len() as i32)?;
for &long in value {
- writer
- .write_i64::<BE>(long)
- .map_err(|_| Error::WriteError)?;
+ writer.write_i64::<BE>(long)?;
+ }
+ }
+ }
+ }
+ if end_tag {
+ writer.write_u8(Tag::End.id())?;
+ }
+ return Ok(());
+}
+
+#[inline]
+fn write_list(writer: &mut dyn Write, value: &[Tag]) -> Result<(), Error> {
+ // we just get the type from the first item, or default the type to END
+ if value.is_empty() {
+ writer.write_all(&[0; 5])?;
+ } else {
+ let first_tag = &value[0];
+ writer.write_u8(first_tag.id())?;
+ writer.write_i32::<BE>(value.len() as i32)?;
+ match first_tag {
+ Tag::Int(_) => {
+ for tag in value {
+ writer.write_i32::<BE>(
+ *tag.as_int().expect("List of Int should only contains Int"),
+ )?;
+ }
+ }
+ Tag::String(_) => {
+ for tag in value {
+ write_string(
+ writer,
+ tag.as_string()
+ .expect("List of String should only contain String"),
+ )?;
+ }
+ }
+ Tag::Compound(_) => {
+ for tag in value {
+ write_compound(
+ writer,
+ tag.as_compound()
+ .expect("List of Compound should only contain Compound"),
+ true,
+ )?;
+ }
+ }
+ _ => {
+ for tag in value {
+ tag.write_without_end(writer)?;
}
}
}
+ }
+
+ Ok(())
+}
+
+#[inline]
+fn write_bytearray(writer: &mut dyn Write, value: &Vec<i8>) -> Result<(), Error> {
+ writer.write_i32::<BE>(value.len() as i32)?;
+ for &byte in value {
+ writer.write_i8(byte)?;
+ }
+ Ok(())
+}
+
+#[inline]
+fn write_intarray(writer: &mut dyn Write, value: &Vec<i32>) -> Result<(), Error> {
+ writer.write_i32::<BE>(value.len() as i32)?;
+ for &int in value {
+ writer.write_i32::<BE>(int)?;
+ }
+ Ok(())
+}
+
+#[inline]
+fn write_longarray(writer: &mut dyn Write, value: &Vec<i64>) -> Result<(), Error> {
+ writer.write_i32::<BE>(value.len() as i32)?;
+ for &long in value {
+ writer.write_i64::<BE>(long)?;
+ }
+ Ok(())
+}
+
+impl Tag {
+ #[inline]
+ pub fn write_without_end(&self, writer: &mut dyn Write) -> Result<(), Error> {
+ match self {
+ Tag::End => {}
+ Tag::Byte(value) => writer.write_i8(*value)?,
+ Tag::Short(value) => writer.write_i16::<BE>(*value)?,
+ Tag::Int(value) => writer.write_i32::<BE>(*value)?,
+ Tag::Long(value) => writer.write_i64::<BE>(*value)?,
+ Tag::Float(value) => writer.write_f32::<BE>(*value)?,
+ Tag::Double(value) => writer.write_f64::<BE>(*value)?,
+ Tag::ByteArray(value) => write_bytearray(writer, value)?,
+ Tag::String(value) => write_string(writer, value)?,
+ Tag::List(value) => write_list(writer, value)?,
+ Tag::Compound(value) => write_compound(writer, value, true)?,
+ Tag::IntArray(value) => write_intarray(writer, value)?,
+ Tag::LongArray(value) => write_longarray(writer, value)?,
+ }
Ok(())
}
@@ -99,11 +200,7 @@ impl Tag {
pub fn write(&self, writer: &mut impl Write) -> Result<(), Error> {
match self {
Tag::Compound(value) => {
- for (key, tag) in value {
- writer.write_u8(tag.id()).map_err(|_| Error::WriteError)?;
- write_string(writer, key)?;
- tag.write_without_end(writer)?;
- }
+ write_compound(writer, value, false)?;
Ok(())
}
_ => Err(Error::InvalidTag),
diff --git a/azalea-nbt/src/error.rs b/azalea-nbt/src/error.rs
index 05ff15e0..219921e4 100644..100755
--- a/azalea-nbt/src/error.rs
+++ b/azalea-nbt/src/error.rs
@@ -14,3 +14,14 @@ impl std::fmt::Display for Error {
}
}
}
+
+impl From<std::io::Error> for Error {
+ fn from(_: std::io::Error) -> Self {
+ Error::WriteError
+ }
+}
+impl From<std::string::FromUtf8Error> for Error {
+ fn from(_: std::string::FromUtf8Error) -> Self {
+ Error::WriteError
+ }
+}
diff --git a/azalea-nbt/src/lib.rs b/azalea-nbt/src/lib.rs
index d14fd929..d14fd929 100644..100755
--- a/azalea-nbt/src/lib.rs
+++ b/azalea-nbt/src/lib.rs
diff --git a/azalea-nbt/src/tag.rs b/azalea-nbt/src/tag.rs
index f11b8889..61bd15ba 100644..100755
--- a/azalea-nbt/src/tag.rs
+++ b/azalea-nbt/src/tag.rs
@@ -36,4 +36,112 @@ impl Tag {
Tag::LongArray(_) => 12,
}
}
+
+ #[inline]
+ pub fn as_byte(&self) -> Option<&i8> {
+ if let Tag::Byte(v) = self {
+ Some(v)
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ pub fn as_short(&self) -> Option<&i16> {
+ if let Tag::Short(v) = self {
+ Some(v)
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ pub fn as_int(&self) -> Option<&i32> {
+ if let Tag::Int(v) = self {
+ Some(v)
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ pub fn as_long(&self) -> Option<&i64> {
+ if let Tag::Long(v) = self {
+ Some(v)
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ pub fn as_float(&self) -> Option<&f32> {
+ if let Tag::Float(v) = self {
+ Some(v)
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ pub fn as_double(&self) -> Option<&f64> {
+ if let Tag::Double(v) = self {
+ Some(v)
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ pub fn as_string(&self) -> Option<&str> {
+ if let Tag::String(v) = self {
+ Some(v)
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ pub fn as_compound(&self) -> Option<&HashMap<String, Tag>> {
+ if let Tag::Compound(v) = self {
+ Some(v)
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ pub fn as_bytearray(&self) -> Option<&Vec<i8>> {
+ if let Tag::ByteArray(v) = self {
+ Some(v)
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ pub fn as_intarray(&self) -> Option<&Vec<i32>> {
+ if let Tag::IntArray(v) = self {
+ Some(v)
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ pub fn as_longarray(&self) -> Option<&Vec<i64>> {
+ if let Tag::LongArray(v) = self {
+ Some(v)
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ pub fn as_list(&self) -> Option<&[Tag]> {
+ if let Tag::List(v) = self {
+ Some(v)
+ } else {
+ None
+ }
+ }
}
diff --git a/azalea-nbt/tests/bigtest.nbt b/azalea-nbt/tests/bigtest.nbt
index dc3769bc..dc3769bc 100644..100755
--- a/azalea-nbt/tests/bigtest.nbt
+++ b/azalea-nbt/tests/bigtest.nbt
Binary files differ
diff --git a/azalea-nbt/tests/complex_player.dat b/azalea-nbt/tests/complex_player.dat
index d7f3fcbd..d7f3fcbd 100644..100755
--- a/azalea-nbt/tests/complex_player.dat
+++ b/azalea-nbt/tests/complex_player.dat
Binary files differ
diff --git a/azalea-nbt/tests/hello_world.nbt b/azalea-nbt/tests/hello_world.nbt
index f3f5e21a..f3f5e21a 100644..100755
--- a/azalea-nbt/tests/hello_world.nbt
+++ b/azalea-nbt/tests/hello_world.nbt
Binary files differ
diff --git a/azalea-nbt/tests/inttest.nbt b/azalea-nbt/tests/inttest.nbt
index ad9255f2..ad9255f2 100644..100755
--- a/azalea-nbt/tests/inttest.nbt
+++ b/azalea-nbt/tests/inttest.nbt
Binary files differ
diff --git a/azalea-nbt/tests/level.dat b/azalea-nbt/tests/level.dat
index e5d11b8c..e5d11b8c 100644..100755
--- a/azalea-nbt/tests/level.dat
+++ b/azalea-nbt/tests/level.dat
Binary files differ
diff --git a/azalea-nbt/tests/simple_player.dat b/azalea-nbt/tests/simple_player.dat
index e5091b41..e5091b41 100644..100755
--- a/azalea-nbt/tests/simple_player.dat
+++ b/azalea-nbt/tests/simple_player.dat
Binary files differ
diff --git a/azalea-nbt/tests/stringtest.nbt b/azalea-nbt/tests/stringtest.nbt
index ac111433..ac111433 100644..100755
--- a/azalea-nbt/tests/stringtest.nbt
+++ b/azalea-nbt/tests/stringtest.nbt
Binary files differ
diff --git a/azalea-nbt/tests/tests.rs b/azalea-nbt/tests/tests.rs
index 75b3a646..75b3a646 100644..100755
--- a/azalea-nbt/tests/tests.rs
+++ b/azalea-nbt/tests/tests.rs
diff --git a/azalea-protocol/Cargo.toml b/azalea-protocol/Cargo.toml
index c9883195..37df8697 100644..100755
--- a/azalea-protocol/Cargo.toml
+++ b/azalea-protocol/Cargo.toml
@@ -10,11 +10,15 @@ async-compression = {version = "^0.3.8", features = ["tokio", "zlib"]}
async-recursion = "^0.3.2"
async-trait = "0.1.51"
azalea-auth = {path = "../azalea-auth"}
+azalea-brigadier = {path = "../azalea-brigadier"}
azalea-chat = {path = "../azalea-chat"}
azalea-core = {path = "../azalea-core"}
azalea-nbt = {path = "../azalea-nbt"}
byteorder = "^1.4.3"
bytes = "^1.1.0"
+num-derive = "^0.3.3"
+num-traits = "^0.2.14"
+packet-macros = {path = "./packet-macros"}
serde = {version = "1.0.130", features = ["serde_derive"]}
serde_json = "^1.0.72"
thiserror = "^1.0.30"
diff --git a/azalea-protocol/README.md b/azalea-protocol/README.md
new file mode 100755
index 00000000..6063ed39
--- /dev/null
+++ b/azalea-protocol/README.md
@@ -0,0 +1,3 @@
+# Azalea Protocol
+
+Sent and receive Minecraft packets.
diff --git a/azalea-protocol/packet-macros/Cargo.toml b/azalea-protocol/packet-macros/Cargo.toml
new file mode 100755
index 00000000..2c0f36d7
--- /dev/null
+++ b/azalea-protocol/packet-macros/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+edition = "2021"
+name = "packet-macros"
+version = "0.1.0"
+
+[lib]
+proc-macro = true
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+proc-macro2 = "^1.0.36"
+quote = "^1.0.10"
+syn = "^1.0.82"
diff --git a/azalea-protocol/packet-macros/src/lib.rs b/azalea-protocol/packet-macros/src/lib.rs
new file mode 100755
index 00000000..45df7e81
--- /dev/null
+++ b/azalea-protocol/packet-macros/src/lib.rs
@@ -0,0 +1,300 @@
+use proc_macro::TokenStream;
+use quote::{quote, ToTokens};
+use syn::{
+ self, braced,
+ parse::{Parse, ParseStream, Result},
+ parse_macro_input, DeriveInput, FieldsNamed, Ident, LitInt, Token,
+};
+
+fn as_packet_derive(input: TokenStream, state: proc_macro2::TokenStream) -> TokenStream {
+ let DeriveInput { ident, data, .. } = parse_macro_input!(input);
+
+ let fields = match data {
+ syn::Data::Struct(syn::DataStruct { fields, .. }) => fields,
+ _ => panic!("#[derive(*Packet)] can only be used on structs"),
+ };
+ let FieldsNamed { named, .. } = match fields {
+ syn::Fields::Named(f) => f,
+ _ => panic!("#[derive(*Packet)] can only be used on structs with named fields"),
+ };
+
+ let write_fields = named
+ .iter()
+ .map(|f| {
+ let field_name = &f.ident;
+ let field_type = &f.ty;
+ // do a different buf.write_* for each field depending on the type
+ // if it's a string, use buf.write_string
+ match field_type {
+ syn::Type::Path(_) => {
+ if f.attrs.iter().any(|attr| attr.path.is_ident("varint")) {
+ quote! {
+ crate::mc_buf::McBufVarintWritable::varint_write_into(&self.#field_name, buf)?;
+ }
+ } else {
+ quote! {
+ crate::mc_buf::McBufWritable::write_into(&self.#field_name, buf)?;
+ }
+ }
+ }
+ _ => panic!(
+ "Error writing field {}: {}",
+ field_name.clone().unwrap(),
+ field_type.to_token_stream()
+ ),
+ }
+ })
+ .collect::<Vec<_>>();
+
+ let read_fields = named
+ .iter()
+ .map(|f| {
+ let field_name = &f.ident;
+ let field_type = &f.ty;
+ // do a different buf.write_* for each field depending on the type
+ // if it's a string, use buf.write_string
+ match field_type {
+ syn::Type::Path(_) => {
+ if f.attrs.iter().any(|a| a.path.is_ident("varint")) {
+ quote! {
+ let #field_name = crate::mc_buf::McBufVarintReadable::varint_read_into(buf).await?;
+ }
+ } else {
+ quote! {
+ let #field_name = crate::mc_buf::McBufReadable::read_into(buf).await?;
+ }
+ }
+ }
+ _ => panic!(
+ "Error reading field {}: {}",
+ field_name.clone().unwrap(),
+ field_type.to_token_stream()
+ ),
+ }
+ })
+ .collect::<Vec<_>>();
+ let read_field_names = named.iter().map(|f| &f.ident).collect::<Vec<_>>();
+
+ quote! {
+ impl #ident {
+ pub fn get(self) -> #state {
+ #state::#ident(self)
+ }
+
+ pub fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ #(#write_fields)*
+ Ok(())
+ }
+
+ pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
+ buf: &mut T,
+ ) -> Result<#state, String> {
+ #(#read_fields)*
+ Ok(#ident {
+ #(#read_field_names: #read_field_names),*
+ }.get())
+ }
+ }
+ }
+ .into()
+}
+
+#[proc_macro_derive(GamePacket, attributes(varint))]
+pub fn derive_game_packet(input: TokenStream) -> TokenStream {
+ as_packet_derive(input, quote! {crate::packets::game::GamePacket})
+}
+
+#[proc_macro_derive(HandshakePacket, attributes(varint))]
+pub fn derive_handshake_packet(input: TokenStream) -> TokenStream {
+ as_packet_derive(input, quote! {crate::packets::handshake::HandshakePacket})
+}
+
+#[proc_macro_derive(LoginPacket, attributes(varint))]
+pub fn derive_login_packet(input: TokenStream) -> TokenStream {
+ as_packet_derive(input, quote! {crate::packets::login::LoginPacket})
+}
+
+#[proc_macro_derive(StatusPacket, attributes(varint))]
+pub fn derive_status_packet(input: TokenStream) -> TokenStream {
+ as_packet_derive(input, quote! {crate::packets::status::StatusPacket})
+}
+
+#[derive(Debug)]
+struct PacketIdPair {
+ id: u32,
+ module: Ident,
+ name: Ident,
+}
+#[derive(Debug)]
+struct PacketIdMap {
+ packets: Vec<PacketIdPair>,
+}
+
+impl Parse for PacketIdMap {
+ fn parse(input: ParseStream) -> Result<Self> {
+ let mut packets = vec![];
+ loop {
+ // 0x0e: clientbound_change_difficulty_packet::ClientboundChangeDifficultyPacket,
+ // 0x0e
+ let packet_id: LitInt = match input.parse() {
+ Ok(i) => i,
+ Err(_) => break,
+ };
+ let packet_id = packet_id.base10_parse::<u32>()?;
+ // :
+ input.parse::<Token![:]>()?;
+ // clientbound_change_difficulty_packet
+ let module: Ident = input.parse()?;
+ // ::
+ input.parse::<Token![::]>()?;
+ // ClientboundChangeDifficultyPacket
+ let name: Ident = input.parse()?;
+
+ packets.push(PacketIdPair {
+ id: packet_id,
+ module,
+ name,
+ });
+
+ if input.parse::<Token![,]>().is_err() {
+ break;
+ }
+ }
+
+ Ok(PacketIdMap { packets })
+ }
+}
+
+#[derive(Debug)]
+struct DeclareStatePackets {
+ name: Ident,
+ serverbound: PacketIdMap,
+ clientbound: PacketIdMap,
+}
+
+impl Parse for DeclareStatePackets {
+ fn parse(input: ParseStream) -> Result<Self> {
+ let name = input.parse()?;
+ input.parse::<Token![,]>()?;
+
+ let serverbound_token: Ident = input.parse()?;
+ if serverbound_token != "Serverbound" {
+ return Err(syn::Error::new(
+ serverbound_token.span(),
+ "Expected `Serverbound`",
+ ));
+ }
+ input.parse::<Token![=>]>()?;
+ let content;
+ braced!(content in input);
+ let serverbound = content.parse()?;
+
+ input.parse::<Token![,]>()?;
+
+ let clientbound_token: Ident = input.parse()?;
+ if clientbound_token != "Clientbound" {
+ return Err(syn::Error::new(
+ clientbound_token.span(),
+ "Expected `Clientbound`",
+ ));
+ }
+ input.parse::<Token![=>]>()?;
+ let content;
+ braced!(content in input);
+ let clientbound = content.parse()?;
+
+ Ok(DeclareStatePackets {
+ name,
+ serverbound,
+ clientbound,
+ })
+ }
+}
+#[proc_macro]
+pub fn declare_state_packets(input: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeclareStatePackets);
+
+ let state_name = input.name;
+ let state_name_litstr = syn::LitStr::new(&state_name.to_string(), state_name.span());
+
+ let mut enum_contents = quote!();
+ let mut id_match_contents = quote!();
+ let mut write_match_contents = quote!();
+ let mut serverbound_read_match_contents = quote!();
+ let mut clientbound_read_match_contents = quote!();
+ for PacketIdPair { id, module, name } in input.serverbound.packets {
+ enum_contents.extend(quote! {
+ #name(#module::#name),
+ });
+ id_match_contents.extend(quote! {
+ #state_name::#name(_packet) => #id,
+ });
+ write_match_contents.extend(quote! {
+ #state_name::#name(packet) => packet.write(buf),
+ });
+ serverbound_read_match_contents.extend(quote! {
+ #id => #module::#name::read(buf).await?,
+ });
+ }
+ for PacketIdPair { id, module, name } in input.clientbound.packets {
+ enum_contents.extend(quote! {
+ #name(#module::#name),
+ });
+ id_match_contents.extend(quote! {
+ #state_name::#name(_packet) => #id,
+ });
+ write_match_contents.extend(quote! {
+ #state_name::#name(packet) => packet.write(buf),
+ });
+ clientbound_read_match_contents.extend(quote! {
+ #id => #module::#name::read(buf).await?,
+ });
+ }
+
+ quote! {
+ #[derive(Clone, Debug)]
+ pub enum #state_name
+ where
+ Self: Sized,
+ {
+ #enum_contents
+ }
+
+ #[async_trait::async_trait]
+ impl crate::packets::ProtocolPacket for #state_name {
+ fn id(&self) -> u32 {
+ match self {
+ #id_match_contents
+ }
+ }
+
+ fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ match self {
+ #write_match_contents
+ }
+ }
+
+ /// Read a packet by its id, ConnectionProtocol, and flow
+ async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
+ id: u32,
+ flow: &crate::connect::PacketFlow,
+ buf: &mut T,
+ ) -> Result<#state_name, String>
+ where
+ Self: Sized,
+ {
+ Ok(match flow {
+ crate::connect::PacketFlow::ServerToClient => match id {
+ #clientbound_read_match_contents
+ _ => panic!("Unknown ServerToClient {} packet id: {}", #state_name_litstr, id),
+ },
+ crate::connect::PacketFlow::ClientToServer => match id {
+ #serverbound_read_match_contents
+ _ => return Err(format!("Unknown ClientToServer {} packet id: {}", #state_name_litstr, id)),
+ },
+ })
+ }
+ }
+ }
+ .into()
+}
diff --git a/azalea-protocol/src/connect.rs b/azalea-protocol/src/connect.rs
index 3d910d3a..3d910d3a 100644..100755
--- a/azalea-protocol/src/connect.rs
+++ b/azalea-protocol/src/connect.rs
diff --git a/azalea-protocol/src/lib.rs b/azalea-protocol/src/lib.rs
index 684add45..684add45 100644..100755
--- a/azalea-protocol/src/lib.rs
+++ b/azalea-protocol/src/lib.rs
diff --git a/azalea-protocol/src/mc_buf.rs b/azalea-protocol/src/mc_buf.rs
deleted file mode 100644
index 538fc212..00000000
--- a/azalea-protocol/src/mc_buf.rs
+++ /dev/null
@@ -1,478 +0,0 @@
-//! Utilities for reading and writing for the Minecraft protocol
-
-use std::io::Write;
-
-use async_trait::async_trait;
-use azalea_core::resource_location::ResourceLocation;
-use byteorder::{BigEndian, WriteBytesExt};
-use tokio::io::{AsyncRead, AsyncReadExt};
-
-// const DEFAULT_NBT_QUOTA: u32 = 2097152;
-const MAX_STRING_LENGTH: u16 = 32767;
-// const MAX_COMPONENT_STRING_LENGTH: u32 = 262144;
-
-#[async_trait]
-pub trait Writable {
- fn write_list<F, T>(&mut self, list: &[T], writer: F) -> Result<(), std::io::Error>
- where
- F: FnOnce(&mut Self, &T) -> Result<(), std::io::Error> + Copy,
- T: Sized,
- Self: Sized;
- fn write_int_id_list(&mut self, list: Vec<i32>) -> Result<(), std::io::Error>;
- fn write_map<KF, VF, KT, VT>(
- &mut self,
- map: Vec<(KT, VT)>,
- key_writer: KF,
- value_writer: VF,
- ) -> Result<(), std::io::Error>
- where
- KF: Fn(&mut Self, KT) -> Result<(), std::io::Error> + Copy,
- VF: Fn(&mut Self, VT) -> Result<(), std::io::Error> + Copy,
- Self: Sized;
-
- fn write_byte(&mut self, n: u8) -> Result<(), std::io::Error>;
- fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), std::io::Error>;
- fn write_varint(&mut self, value: i32) -> Result<(), std::io::Error>;
- fn write_utf_with_len(&mut self, string: &str, len: usize) -> Result<(), std::io::Error>;
- fn write_utf(&mut self, string: &str) -> Result<(), std::io::Error>;
- fn write_short(&mut self, n: u16) -> Result<(), std::io::Error>;
- fn write_byte_array(&mut self, bytes: &[u8]) -> Result<(), std::io::Error>;
- fn write_int(&mut self, n: i32) -> Result<(), std::io::Error>;
- fn write_boolean(&mut self, b: bool) -> Result<(), std::io::Error>;
- fn write_nbt(&mut self, nbt: &azalea_nbt::Tag) -> Result<(), std::io::Error>;
- fn write_long(&mut self, n: i64) -> Result<(), std::io::Error>;
- fn write_resource_location(
- &mut self,
- location: &ResourceLocation,
- ) -> Result<(), std::io::Error>;
-}
-
-#[async_trait]
-impl Writable for Vec<u8> {
- fn write_list<F, T>(&mut self, list: &[T], writer: F) -> Result<(), std::io::Error>
- where
- F: FnOnce(&mut Self, &T) -> Result<(), std::io::Error> + Copy,
- Self: Sized,
- {
- self.write_varint(list.len() as i32)?;
- for item in list {
- writer(self, item)?;
- }
- Ok(())
- }
-
- fn write_int_id_list(&mut self, list: Vec<i32>) -> Result<(), std::io::Error> {
- self.write_list(&list, |buf, n| buf.write_varint(*n))
- }
-
- fn write_map<KF, VF, KT, VT>(
- &mut self,
- map: Vec<(KT, VT)>,
- key_writer: KF,
- value_writer: VF,
- ) -> Result<(), std::io::Error>
- where
- KF: Fn(&mut Self, KT) -> Result<(), std::io::Error> + Copy,
- VF: Fn(&mut Self, VT) -> Result<(), std::io::Error> + Copy,
- Self: Sized,
- {
- self.write_varint(map.len() as i32)?;
- for (key, value) in map {
- key_writer(self, key)?;
- value_writer(self, value)?;
- }
- Ok(())
- }
-
- fn write_byte(&mut self, n: u8) -> Result<(), std::io::Error> {
- WriteBytesExt::write_u8(self, n)
- }
-
- fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), std::io::Error> {
- self.extend_from_slice(bytes);
- Ok(())
- }
-
- fn write_varint(&mut self, mut value: i32) -> Result<(), std::io::Error> {
- let mut buffer = [0];
- if value == 0 {
- self.write_all(&buffer).unwrap();
- }
- while value != 0 {
- buffer[0] = (value & 0b0111_1111) as u8;
- value = (value >> 7) & (i32::max_value() >> 6);
- if value != 0 {
- buffer[0] |= 0b1000_0000;
- }
- self.write_all(&buffer)?;
- }
- Ok(())
- }
-
- fn write_utf_with_len(&mut self, string: &str, len: usize) -> Result<(), std::io::Error> {
- if string.len() > len {
- panic!(
- "String too big (was {} bytes encoded, max {})",
- string.len(),
- len
- );
- }
- self.write_varint(string.len() as i32)?;
- self.write_bytes(string.as_bytes())
- }
-
- fn write_utf(&mut self, string: &str) -> Result<(), std::io::Error> {
- self.write_utf_with_len(string, MAX_STRING_LENGTH.into())
- }
-
- fn write_short(&mut self, n: u16) -> Result<(), std::io::Error> {
- WriteBytesExt::write_u16::<BigEndian>(self, n)
- }
-
- fn write_byte_array(&mut self, bytes: &[u8]) -> Result<(), std::io::Error> {
- self.write_varint(bytes.len() as i32)?;
- self.write_bytes(bytes)
- }
-
- fn write_int(&mut self, n: i32) -> Result<(), std::io::Error> {
- WriteBytesExt::write_i32::<BigEndian>(self, n)
- }
-
- fn write_boolean(&mut self, b: bool) -> Result<(), std::io::Error> {
- self.write_byte(if b { 1 } else { 0 })
- }
-
- fn write_nbt(&mut self, nbt: &azalea_nbt::Tag) -> Result<(), std::io::Error> {
- nbt.write(self)
- .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))
- }
-
- fn write_long(&mut self, n: i64) -> Result<(), std::io::Error> {
- WriteBytesExt::write_i64::<BigEndian>(self, n)
- }
-
- fn write_resource_location(
- &mut self,
- location: &ResourceLocation,
- ) -> Result<(), std::io::Error> {
- self.write_utf(&location.to_string())
- }
-}
-
-#[async_trait]
-pub trait Readable {
- async fn read_int_id_list(&mut self) -> Result<Vec<i32>, String>;
- async fn read_varint(&mut self) -> Result<i32, String>;
- fn get_varint_size(&mut self, value: i32) -> u8;
- fn get_varlong_size(&mut self, value: i32) -> u8;
- async fn read_byte_array(&mut self) -> Result<Vec<u8>, String>;
- async fn read_bytes(&mut self, n: usize) -> Result<Vec<u8>, String>;
- async fn read_utf(&mut self) -> Result<String, String>;
- async fn read_utf_with_len(&mut self, max_length: u32) -> Result<String, String>;
- async fn read_byte(&mut self) -> Result<u8, String>;
- async fn read_int(&mut self) -> Result<i32, String>;
- async fn read_boolean(&mut self) -> Result<bool, String>;
- async fn read_nbt(&mut self) -> Result<azalea_nbt::Tag, String>;
- async fn read_long(&mut self) -> Result<i64, String>;
- async fn read_resource_location(&mut self) -> Result<ResourceLocation, String>;
-}
-
-#[async_trait]
-impl<R> Readable for R
-where
- R: AsyncRead + std::marker::Unpin + std::marker::Send,
-{
- async fn read_int_id_list(&mut self) -> Result<Vec<i32>, String> {
- let len = self.read_varint().await?;
- let mut list = Vec::with_capacity(len as usize);
- for _ in 0..len {
- list.push(self.read_varint().await?);
- }
- Ok(list)
- }
-
- // fast varints stolen from https://github.com/luojia65/mc-varint/blob/master/src/lib.rs#L67
- /// Read a single varint from the reader and return the value, along with the number of bytes read
- async fn read_varint(&mut self) -> Result<i32, String> {
- let mut buffer = [0];
- let mut ans = 0;
- for i in 0..4 {
- self.read_exact(&mut buffer)
- .await
- .map_err(|_| "Invalid VarInt".to_string())?;
- ans |= ((buffer[0] & 0b0111_1111) as i32) << (7 * i);
- if buffer[0] & 0b1000_0000 == 0 {
- return Ok(ans);
- }
- }
- Ok(ans)
- }
-
- fn get_varint_size(&mut self, value: i32) -> u8 {
- for i in 1..5 {
- if (value & -1 << (i * 7)) != 0 {
- continue;
- }
- return i;
- }
- 5
- }
-
- fn get_varlong_size(&mut self, value: i32) -> u8 {
- for i in 1..10 {
- if (value & -1 << (i * 7)) != 0 {
- continue;
- }
- return i;
- }
- 10
- }
-
- async fn read_byte_array(&mut self) -> Result<Vec<u8>, String> {
- let length = self.read_varint().await? as usize;
- Ok(self.read_bytes(length).await?)
- }
-
- async fn read_bytes(&mut self, n: usize) -> Result<Vec<u8>, String> {
- let mut bytes = vec![0; n];
- match AsyncReadExt::read_exact(self, &mut bytes).await {
- Ok(_) => Ok(bytes),
- Err(_) => Err("Error reading bytes".to_string()),
- }
- }
-
- async fn read_utf(&mut self) -> Result<String, String> {
- self.read_utf_with_len(MAX_STRING_LENGTH.into()).await
- }
-
- async fn read_utf_with_len(&mut self, max_length: u32) -> Result<String, String> {
- let length = self.read_varint().await?;
- // i don't know why it's multiplied by 4 but it's like that in mojang's code so
- if length < 0 {
- return Err(
- "The received encoded string buffer length is less than zero! Weird string!"
- .to_string(),
- );
- }
- if length as u32 > max_length * 4 {
- return Err(format!(
- "The received encoded string buffer length is longer than maximum allowed ({} > {})",
- length,
- max_length * 4
- ));
- }
-
- // this is probably quite inefficient, idk how to do it better
- let mut string = String::new();
- let mut buffer = vec![0; length as usize];
- self.read_exact(&mut buffer)
- .await
- .map_err(|_| "Invalid UTF-8".to_string())?;
-
- string.push_str(std::str::from_utf8(&buffer).unwrap());
- if string.len() > length as usize {
- return Err(format!(
- "The received string length is longer than maximum allowed ({} > {})",
- length, max_length
- ));
- }
-
- Ok(string)
- }
-
- /// Read a single byte from the reader
- async fn read_byte(&mut self) -> Result<u8, String> {
- match AsyncReadExt::read_u8(self).await {
- Ok(r) => Ok(r),
- Err(_) => Err("Error reading byte".to_string()),
- }
- }
-
- async fn read_int(&mut self) -> Result<i32, String> {
- match AsyncReadExt::read_i32(self).await {
- Ok(r) => Ok(r),
- Err(_) => Err("Error reading int".to_string()),
- }
- }
-
- async fn read_boolean(&mut self) -> Result<bool, String> {
- match self.read_byte().await {
- Ok(0) => Ok(false),
- Ok(1) => Ok(true),
- _ => Err("Error reading boolean".to_string()),
- }
- }
-
- async fn read_nbt(&mut self) -> Result<azalea_nbt::Tag, String> {
- match azalea_nbt::Tag::read(self).await {
- Ok(r) => Ok(r),
- // Err(e) => Err(e.to_string()),
- Err(e) => Err(e.to_string()).unwrap(),
- }
- }
-
- async fn read_long(&mut self) -> Result<i64, String> {
- match AsyncReadExt::read_i64(self).await {
- Ok(r) => Ok(r),
- Err(_) => Err("Error reading long".to_string()),
- }
- }
-
- async fn read_resource_location(&mut self) -> Result<ResourceLocation, String> {
- // get the resource location from the string
- let location_string = self.read_utf().await?;
- let location = ResourceLocation::new(&location_string)?;
- Ok(location)
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use std::{collections::HashMap, io::Cursor};
- use tokio::io::BufReader;
-
- #[test]
- fn test_write_varint() {
- let mut buf = Vec::new();
- buf.write_varint(123456).unwrap();
- assert_eq!(buf, vec![192, 196, 7]);
-
- let mut buf = Vec::new();
- buf.write_varint(0).unwrap();
- assert_eq!(buf, vec![0]);
- }
-
- #[tokio::test]
- async fn test_read_varint() {
- let mut buf = BufReader::new(Cursor::new(vec![192, 196, 7]));
- assert_eq!(buf.read_varint().await.unwrap(), 123456);
- assert_eq!(buf.get_varint_size(123456), 3);
-
- let mut buf = BufReader::new(Cursor::new(vec![0]));
- assert_eq!(buf.read_varint().await.unwrap(), 0);
- assert_eq!(buf.get_varint_size(0), 1);
-
- let mut buf = BufReader::new(Cursor::new(vec![1]));
- assert_eq!(buf.read_varint().await.unwrap(), 1);
- assert_eq!(buf.get_varint_size(1), 1);
- }
-
- #[tokio::test]
- async fn test_read_varint_longer() {
- let mut buf = BufReader::new(Cursor::new(vec![138, 56, 0, 135, 56, 123]));
- assert_eq!(buf.read_varint().await.unwrap(), 7178);
- }
-
- #[tokio::test]
- async fn test_list() {
- let mut buf = Vec::new();
- buf.write_list(&vec!["a", "bc", "def"], |buf, s| buf.write_utf(s))
- .unwrap();
-
- // there's no read_list because idk how to do it in rust
- let mut buf = BufReader::new(Cursor::new(buf));
-
- let mut result = Vec::new();
- let length = buf.read_varint().await.unwrap();
- for _ in 0..length {
- result.push(buf.read_utf().await.unwrap());
- }
-
- assert_eq!(result, vec!["a", "bc", "def"]);
- }
-
- #[tokio::test]
- async fn test_int_id_list() {
- let mut buf = Vec::new();
- buf.write_list(&vec![1, 2, 3], |buf, i| buf.write_varint(*i))
- .unwrap();
-
- let mut buf = BufReader::new(Cursor::new(buf));
-
- let result = buf.read_int_id_list().await.unwrap();
- assert_eq!(result, vec![1, 2, 3]);
- }
-
- #[tokio::test]
- async fn test_map() {
- let mut buf = Vec::new();
- buf.write_map(
- vec![("a", 1), ("bc", 23), ("def", 456)],
- Vec::write_utf,
- Vec::write_varint,
- )
- .unwrap();
-
- let mut buf = BufReader::new(Cursor::new(buf));
-
- let mut result = Vec::new();
- let length = buf.read_varint().await.unwrap();
- for _ in 0..length {
- result.push((
- buf.read_utf().await.unwrap(),
- buf.read_varint().await.unwrap(),
- ));
- }
-
- assert_eq!(
- result,
- vec![
- ("a".to_string(), 1),
- ("bc".to_string(), 23),
- ("def".to_string(), 456)
- ]
- );
- }
-
- #[tokio::test]
- async fn test_nbt() {
- let mut buf = Vec::new();
- buf.write_nbt(&azalea_nbt::Tag::Compound(HashMap::from_iter(vec![(
- "hello world".to_string(),
- azalea_nbt::Tag::Compound(HashMap::from_iter(vec![(
- "name".to_string(),
- azalea_nbt::Tag::String("Bananrama".to_string()),
- )])),
- )])))
- .unwrap();
-
- let mut buf = BufReader::new(Cursor::new(buf));
-
- let result = buf.read_nbt().await.unwrap();
- assert_eq!(
- result,
- azalea_nbt::Tag::Compound(HashMap::from_iter(vec![(
- "hello world".to_string(),
- azalea_nbt::Tag::Compound(HashMap::from_iter(vec![(
- "name".to_string(),
- azalea_nbt::Tag::String("Bananrama".to_string()),
- )])),
- )]))
- );
- }
-
- #[tokio::test]
- async fn test_long() {
- let mut buf = Vec::new();
- buf.write_long(123456).unwrap();
-
- let mut buf = BufReader::new(Cursor::new(buf));
-
- assert_eq!(buf.read_long().await.unwrap(), 123456);
- }
-
- #[tokio::test]
- async fn test_resource_location() {
- let mut buf = Vec::new();
- buf.write_resource_location(&ResourceLocation::new("minecraft:dirt").unwrap())
- .unwrap();
-
- let mut buf = BufReader::new(Cursor::new(buf));
-
- assert_eq!(
- buf.read_resource_location().await.unwrap(),
- ResourceLocation::new("minecraft:dirt").unwrap()
- );
- }
-}
diff --git a/azalea-protocol/src/mc_buf/mod.rs b/azalea-protocol/src/mc_buf/mod.rs
new file mode 100755
index 00000000..4ecb65d1
--- /dev/null
+++ b/azalea-protocol/src/mc_buf/mod.rs
@@ -0,0 +1,231 @@
+//! Utilities for reading and writing for the Minecraft protocol
+
+mod read;
+mod write;
+
+pub use read::{McBufReadable, McBufVarintReadable, Readable};
+pub use write::{McBufVarintWritable, McBufWritable, Writable};
+
+// const DEFAULT_NBT_QUOTA: u32 = 2097152;
+const MAX_STRING_LENGTH: u16 = 32767;
+// const MAX_COMPONENT_STRING_LENGTH: u32 = 262144;
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use azalea_core::resource_location::ResourceLocation;
+ use std::{collections::HashMap, io::Cursor};
+ use tokio::io::BufReader;
+
+ #[test]
+ fn test_write_varint() {
+ let mut buf = Vec::new();
+ buf.write_varint(0).unwrap();
+ assert_eq!(buf, vec![0]);
+
+ let mut buf = Vec::new();
+ buf.write_varint(1).unwrap();
+ assert_eq!(buf, vec![1]);
+
+ let mut buf = Vec::new();
+ buf.write_varint(2).unwrap();
+ assert_eq!(buf, vec![2]);
+
+ let mut buf = Vec::new();
+ buf.write_varint(127).unwrap();
+ assert_eq!(buf, vec![127]);
+
+ let mut buf = Vec::new();
+ buf.write_varint(128).unwrap();
+ assert_eq!(buf, vec![128, 1]);
+
+ let mut buf = Vec::new();
+ buf.write_varint(255).unwrap();
+ assert_eq!(buf, vec![255, 1]);
+
+ let mut buf = Vec::new();
+ buf.write_varint(25565).unwrap();
+ assert_eq!(buf, vec![221, 199, 1]);
+
+ let mut buf = Vec::new();
+ buf.write_varint(2097151).unwrap();
+ assert_eq!(buf, vec![255, 255, 127]);
+
+ let mut buf = Vec::new();
+ buf.write_varint(2147483647).unwrap();
+ assert_eq!(buf, vec![255, 255, 255, 255, 7]);
+
+ let mut buf = Vec::new();
+ buf.write_varint(-1).unwrap();
+ assert_eq!(buf, vec![255, 255, 255, 255, 15]);
+
+ let mut buf = Vec::new();
+ buf.write_varint(-2147483648).unwrap();
+ assert_eq!(buf, vec![128, 128, 128, 128, 8]);
+ }
+
+ #[tokio::test]
+ async fn test_read_varint() {
+ let mut buf = BufReader::new(Cursor::new(vec![0]));
+ assert_eq!(buf.read_varint().await.unwrap(), 0);
+ assert_eq!(buf.get_varint_size(0), 1);
+
+ let mut buf = BufReader::new(Cursor::new(vec![1]));
+ assert_eq!(buf.read_varint().await.unwrap(), 1);
+ assert_eq!(buf.get_varint_size(1), 1);
+
+ let mut buf = BufReader::new(Cursor::new(vec![2]));
+ assert_eq!(buf.read_varint().await.unwrap(), 2);
+ assert_eq!(buf.get_varint_size(2), 1);
+
+ let mut buf = BufReader::new(Cursor::new(vec![127]));
+ assert_eq!(buf.read_varint().await.unwrap(), 127);
+ assert_eq!(buf.get_varint_size(127), 1);
+
+ let mut buf = BufReader::new(Cursor::new(vec![128, 1]));
+ assert_eq!(buf.read_varint().await.unwrap(), 128);
+ assert_eq!(buf.get_varint_size(128), 2);
+
+ let mut buf = BufReader::new(Cursor::new(vec![255, 1]));
+ assert_eq!(buf.read_varint().await.unwrap(), 255);
+ assert_eq!(buf.get_varint_size(255), 2);
+
+ let mut buf = BufReader::new(Cursor::new(vec![221, 199, 1]));
+ assert_eq!(buf.read_varint().await.unwrap(), 25565);
+ assert_eq!(buf.get_varint_size(25565), 3);
+
+ let mut buf = BufReader::new(Cursor::new(vec![255, 255, 127]));
+ assert_eq!(buf.read_varint().await.unwrap(), 2097151);
+ assert_eq!(buf.get_varint_size(2097151), 3);
+
+ let mut buf = BufReader::new(Cursor::new(vec![255, 255, 255, 255, 7]));
+ assert_eq!(buf.read_varint().await.unwrap(), 2147483647);
+ assert_eq!(buf.get_varint_size(2147483647), 5);
+
+ let mut buf = BufReader::new(Cursor::new(vec![255, 255, 255, 255, 15]));
+ assert_eq!(buf.read_varint().await.unwrap(), -1);
+ assert_eq!(buf.get_varint_size(-1), 5);
+
+ let mut buf = BufReader::new(Cursor::new(vec![128, 128, 128, 128, 8]));
+ assert_eq!(buf.read_varint().await.unwrap(), -2147483648);
+ assert_eq!(buf.get_varint_size(-2147483648), 5);
+ }
+
+ #[tokio::test]
+ async fn test_read_varint_longer() {
+ let mut buf = BufReader::new(Cursor::new(vec![138, 56, 0, 135, 56, 123]));
+ assert_eq!(buf.read_varint().await.unwrap(), 7178);
+ }
+
+ #[tokio::test]
+ async fn test_list() {
+ let mut buf = Vec::new();
+ buf.write_list(&vec!["a", "bc", "def"], |buf, s| buf.write_utf(s))
+ .unwrap();
+
+ // there's no read_list because idk how to do it in rust
+ let mut buf = BufReader::new(Cursor::new(buf));
+
+ let mut result = Vec::new();
+ let length = buf.read_varint().await.unwrap();
+ for _ in 0..length {
+ result.push(buf.read_utf().await.unwrap());
+ }
+
+ assert_eq!(result, vec!["a", "bc", "def"]);
+ }
+
+ #[tokio::test]
+ async fn test_int_id_list() {
+ let mut buf = Vec::new();
+ buf.write_list(&vec![1, 2, 3], |buf, i| buf.write_varint(*i))
+ .unwrap();
+
+ let mut buf = BufReader::new(Cursor::new(buf));
+
+ let result = buf.read_int_id_list().await.unwrap();
+ assert_eq!(result, vec![1, 2, 3]);
+ }
+
+ #[tokio::test]
+ async fn test_map() {
+ let mut buf = Vec::new();
+ buf.write_map(
+ vec![("a", 1), ("bc", 23), ("def", 456)],
+ Vec::write_utf,
+ Vec::write_varint,
+ )
+ .unwrap();
+
+ let mut buf = BufReader::new(Cursor::new(buf));
+
+ let mut result = Vec::new();
+ let length = buf.read_varint().await.unwrap();
+ for _ in 0..length {
+ result.push((
+ buf.read_utf().await.unwrap(),
+ buf.read_varint().await.unwrap(),
+ ));
+ }
+
+ assert_eq!(
+ result,
+ vec![
+ ("a".to_string(), 1),
+ ("bc".to_string(), 23),
+ ("def".to_string(), 456)
+ ]
+ );
+ }
+
+ #[tokio::test]
+ async fn test_nbt() {
+ let mut buf = Vec::new();
+ buf.write_nbt(&azalea_nbt::Tag::Compound(HashMap::from_iter(vec![(
+ "hello world".to_string(),
+ azalea_nbt::Tag::Compound(HashMap::from_iter(vec![(
+ "name".to_string(),
+ azalea_nbt::Tag::String("Bananrama".to_string()),
+ )])),
+ )])))
+ .unwrap();
+
+ let mut buf = BufReader::new(Cursor::new(buf));
+
+ let result = buf.read_nbt().await.unwrap();
+ assert_eq!(
+ result,
+ azalea_nbt::Tag::Compound(HashMap::from_iter(vec![(
+ "hello world".to_string(),
+ azalea_nbt::Tag::Compound(HashMap::from_iter(vec![(
+ "name".to_string(),
+ azalea_nbt::Tag::String("Bananrama".to_string()),
+ )])),
+ )]))
+ );
+ }
+
+ #[tokio::test]
+ async fn test_long() {
+ let mut buf = Vec::new();
+ buf.write_long(123456).unwrap();
+
+ let mut buf = BufReader::new(Cursor::new(buf));
+
+ assert_eq!(buf.read_long().await.unwrap(), 123456);
+ }
+
+ #[tokio::test]
+ async fn test_resource_location() {
+ let mut buf = Vec::new();
+ buf.write_resource_location(&ResourceLocation::new("minecraft:dirt").unwrap())
+ .unwrap();
+
+ let mut buf = BufReader::new(Cursor::new(buf));
+
+ assert_eq!(
+ buf.read_resource_location().await.unwrap(),
+ ResourceLocation::new("minecraft:dirt").unwrap()
+ );
+ }
+}
diff --git a/azalea-protocol/src/mc_buf/read.rs b/azalea-protocol/src/mc_buf/read.rs
new file mode 100755
index 00000000..1e031916
--- /dev/null
+++ b/azalea-protocol/src/mc_buf/read.rs
@@ -0,0 +1,459 @@
+use async_trait::async_trait;
+use azalea_chat::component::Component;
+use azalea_core::{
+ difficulty::Difficulty, game_type::GameType, resource_location::ResourceLocation,
+};
+use serde::Deserialize;
+use tokio::io::{AsyncRead, AsyncReadExt};
+
+use super::MAX_STRING_LENGTH;
+
+#[async_trait]
+pub trait Readable {
+ async fn read_int_id_list(&mut self) -> Result<Vec<i32>, String>;
+ async fn read_varint(&mut self) -> Result<i32, String>;
+ fn get_varint_size(&mut self, value: i32) -> u8;
+ fn get_varlong_size(&mut self, value: i32) -> u8;
+ async fn read_byte_array(&mut self) -> Result<Vec<u8>, String>;
+ async fn read_bytes_with_len(&mut self, n: usize) -> Result<Vec<u8>, String>;
+ async fn read_bytes(&mut self) -> Result<Vec<u8>, String>;
+ async fn read_utf(&mut self) -> Result<String, String>;
+ async fn read_utf_with_len(&mut self, max_length: u32) -> Result<String, String>;
+ async fn read_byte(&mut self) -> Result<u8, String>;
+ async fn read_int(&mut self) -> Result<i32, String>;
+ async fn read_boolean(&mut self) -> Result<bool, String>;
+ async fn read_nbt(&mut self) -> Result<azalea_nbt::Tag, String>;
+ async fn read_long(&mut self) -> Result<i64, String>;
+ async fn read_resource_location(&mut self) -> Result<ResourceLocation, String>;
+ async fn read_short(&mut self) -> Result<i16, String>;
+ async fn read_float(&mut self) -> Result<f32, String>;
+}
+
+#[async_trait]
+impl<R> Readable for R
+where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+{
+ async fn read_int_id_list(&mut self) -> Result<Vec<i32>, String> {
+ let len = self.read_varint().await?;
+ let mut list = Vec::with_capacity(len as usize);
+ for _ in 0..len {
+ list.push(self.read_varint().await?);
+ }
+ Ok(list)
+ }
+
+ // fast varints modified from https://github.com/luojia65/mc-varint/blob/master/src/lib.rs#L67
+ /// Read a single varint from the reader and return the value, along with the number of bytes read
+ async fn read_varint(&mut self) -> Result<i32, String> {
+ let mut buffer = [0];
+ let mut ans = 0;
+ for i in 0..5 {
+ self.read_exact(&mut buffer)
+ .await
+ .map_err(|_| "Invalid VarInt".to_string())?;
+ ans |= ((buffer[0] & 0b0111_1111) as i32) << (7 * i);
+ if buffer[0] & 0b1000_0000 == 0 {
+ return Ok(ans);
+ }
+ }
+ Ok(ans)
+ }
+
+ fn get_varint_size(&mut self, value: i32) -> u8 {
+ for i in 1..5 {
+ if (value & -1 << (i * 7)) != 0 {
+ continue;
+ }
+ return i;
+ }
+ 5
+ }
+
+ fn get_varlong_size(&mut self, value: i32) -> u8 {
+ for i in 1..10 {
+ if (value & -1 << (i * 7)) != 0 {
+ continue;
+ }
+ return i;
+ }
+ 10
+ }
+
+ async fn read_byte_array(&mut self) -> Result<Vec<u8>, String> {
+ let length = self.read_varint().await? as usize;
+ Ok(self.read_bytes_with_len(length).await?)
+ }
+
+ async fn read_bytes_with_len(&mut self, n: usize) -> Result<Vec<u8>, String> {
+ let mut bytes = vec![0; n];
+ match AsyncReadExt::read_exact(self, &mut bytes).await {
+ Ok(_) => Ok(bytes),
+ Err(_) => Err("Error reading bytes".to_string()),
+ }
+ }
+
+ async fn read_bytes(&mut self) -> Result<Vec<u8>, String> {
+ // read to end of the buffer
+ let mut bytes = vec![];
+ AsyncReadExt::read_to_end(self, &mut bytes)
+ .await
+ .map_err(|_| "Error reading bytes".to_string())?;
+ Ok(bytes)
+ }
+
+ async fn read_utf(&mut self) -> Result<String, String> {
+ self.read_utf_with_len(MAX_STRING_LENGTH.into()).await
+ }
+
+ async fn read_utf_with_len(&mut self, max_length: u32) -> Result<String, String> {
+ let length = self.read_varint().await?;
+ // i don't know why it's multiplied by 4 but it's like that in mojang's code so
+ if length < 0 {
+ return Err(
+ "The received encoded string buffer length is less than zero! Weird string!"
+ .to_string(),
+ );
+ }
+ if length as u32 > max_length * 4 {
+ return Err(format!(
+ "The received encoded string buffer length is longer than maximum allowed ({} > {})",
+ length,
+ max_length * 4
+ ));
+ }
+
+ // this is probably quite inefficient, idk how to do it better
+ let mut string = String::new();
+ let mut buffer = vec![0; length as usize];
+ self.read_exact(&mut buffer)
+ .await
+ .map_err(|_| "Invalid UTF-8".to_string())?;
+
+ string.push_str(std::str::from_utf8(&buffer).unwrap());
+ if string.len() > length as usize {
+ return Err(format!(
+ "The received string length is longer than maximum allowed ({} > {})",
+ length, max_length
+ ));
+ }
+
+ Ok(string)
+ }
+
+ /// Read a single byte from the reader
+ async fn read_byte(&mut self) -> Result<u8, String> {
+ match AsyncReadExt::read_u8(self).await {
+ Ok(r) => Ok(r),
+ Err(_) => Err("Error reading byte".to_string()),
+ }
+ }
+
+ async fn read_int(&mut self) -> Result<i32, String> {
+ match AsyncReadExt::read_i32(self).await {
+ Ok(r) => Ok(r),
+ Err(_) => Err("Error reading int".to_string()),
+ }
+ }
+
+ async fn read_boolean(&mut self) -> Result<bool, String> {
+ match self.read_byte().await {
+ Ok(0) => Ok(false),
+ Ok(1) => Ok(true),
+ _ => Err("Error reading boolean".to_string()),
+ }
+ }
+
+ async fn read_nbt(&mut self) -> Result<azalea_nbt::Tag, String> {
+ match azalea_nbt::Tag::read(self).await {
+ Ok(r) => Ok(r),
+ // Err(e) => Err(e.to_string()),
+ Err(e) => Err(e.to_string()).unwrap(),
+ }
+ }
+
+ async fn read_long(&mut self) -> Result<i64, String> {
+ match AsyncReadExt::read_i64(self).await {
+ Ok(r) => Ok(r),
+ Err(_) => Err("Error reading long".to_string()),
+ }
+ }
+
+ async fn read_resource_location(&mut self) -> Result<ResourceLocation, String> {
+ // get the resource location from the string
+ let location_string = self.read_utf().await?;
+ let location = ResourceLocation::new(&location_string)?;
+ Ok(location)
+ }
+
+ async fn read_short(&mut self) -> Result<i16, String> {
+ match AsyncReadExt::read_i16(self).await {
+ Ok(r) => Ok(r),
+ Err(_) => Err("Error reading short".to_string()),
+ }
+ }
+
+ async fn read_float(&mut self) -> Result<f32, String> {
+ match AsyncReadExt::read_f32(self).await {
+ Ok(r) => Ok(r),
+ Err(_) => Err("Error reading float".to_string()),
+ }
+ }
+}
+
+#[async_trait]
+pub trait McBufReadable
+where
+ Self: Sized,
+{
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send;
+}
+
+#[async_trait]
+pub trait McBufVarintReadable
+where
+ Self: Sized,
+{
+ async fn varint_read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send;
+}
+
+#[async_trait]
+impl McBufReadable for i32 {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ buf.read_int().await
+ }
+}
+
+#[async_trait]
+impl McBufVarintReadable for i32 {
+ async fn varint_read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ buf.read_varint().await
+ }
+}
+
+#[async_trait]
+impl McBufReadable for Vec<u8> {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ buf.read_bytes().await
+ }
+}
+
+// string
+#[async_trait]
+impl McBufReadable for String {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ buf.read_utf().await
+ }
+}
+
+// ResourceLocation
+#[async_trait]
+impl McBufReadable for ResourceLocation {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ buf.read_resource_location().await
+ }
+}
+
+// u32
+#[async_trait]
+impl McBufReadable for u32 {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ buf.read_int().await.map(|i| i as u32)
+ }
+}
+
+// u32 varint
+#[async_trait]
+impl McBufVarintReadable for u32 {
+ async fn varint_read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ buf.read_varint().await.map(|i| i as u32)
+ }
+}
+
+// u16
+#[async_trait]
+impl McBufReadable for u16 {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ buf.read_short().await.map(|i| i as u16)
+ }
+}
+
+// u16 varint
+#[async_trait]
+impl McBufVarintReadable for u16 {
+ async fn varint_read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ buf.read_varint().await.map(|i| i as u16)
+ }
+}
+
+// i64
+#[async_trait]
+impl McBufReadable for i64 {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ buf.read_long().await
+ }
+}
+
+// u64
+#[async_trait]
+impl McBufReadable for u64 {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ i64::read_into(buf).await.map(|i| i as u64)
+ }
+}
+
+// bool
+#[async_trait]
+impl McBufReadable for bool {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ buf.read_boolean().await
+ }
+}
+
+// u8
+#[async_trait]
+impl McBufReadable for u8 {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ buf.read_byte().await
+ }
+}
+
+// i8
+#[async_trait]
+impl McBufReadable for i8 {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ buf.read_byte().await.map(|i| i as i8)
+ }
+}
+
+// f32
+#[async_trait]
+impl McBufReadable for f32 {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ buf.read_float().await
+ }
+}
+
+// GameType
+#[async_trait]
+impl McBufReadable for GameType {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ GameType::from_id(buf.read_byte().await?)
+ }
+}
+
+// Option<GameType>
+#[async_trait]
+impl McBufReadable for Option<GameType> {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ GameType::from_optional_id(buf.read_byte().await? as i8)
+ }
+}
+
+// Vec<ResourceLocation>
+#[async_trait]
+impl McBufReadable for Vec<ResourceLocation> {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ let mut vec = Vec::new();
+ let length = buf.read_varint().await?;
+ for _ in 0..length {
+ vec.push(buf.read_resource_location().await?);
+ }
+ Ok(vec)
+ }
+}
+
+// azalea_nbt::Tag
+#[async_trait]
+impl McBufReadable for azalea_nbt::Tag {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ buf.read_nbt().await
+ }
+}
+
+// Difficulty
+#[async_trait]
+impl McBufReadable for Difficulty {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ Ok(Difficulty::by_id(u8::read_into(buf).await?))
+ }
+}
+
+// Component
+#[async_trait]
+impl McBufReadable for Component {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ let string = buf.read_utf().await?;
+ let json: serde_json::Value = serde_json::from_str(string.as_str())
+ .map_err(|e| "Component isn't valid JSON".to_string())?;
+ let component = Component::deserialize(json).map_err(|e| e.to_string())?;
+ Ok(component)
+ }
+}
diff --git a/azalea-protocol/src/mc_buf/write.rs b/azalea-protocol/src/mc_buf/write.rs
new file mode 100755
index 00000000..05f613d8
--- /dev/null
+++ b/azalea-protocol/src/mc_buf/write.rs
@@ -0,0 +1,341 @@
+use async_trait::async_trait;
+use azalea_chat::component::Component;
+use azalea_core::{
+ difficulty::Difficulty, game_type::GameType, resource_location::ResourceLocation,
+};
+use byteorder::{BigEndian, WriteBytesExt};
+use std::io::Write;
+
+use super::MAX_STRING_LENGTH;
+
+#[async_trait]
+pub trait Writable {
+ fn write_list<F, T>(&mut self, list: &[T], writer: F) -> Result<(), std::io::Error>
+ where
+ F: FnOnce(&mut Self, &T) -> Result<(), std::io::Error> + Copy,
+ T: Sized,
+ Self: Sized;
+ fn write_int_id_list(&mut self, list: &Vec<i32>) -> Result<(), std::io::Error>;
+ fn write_map<KF, VF, KT, VT>(
+ &mut self,
+ map: Vec<(KT, VT)>,
+ key_writer: KF,
+ value_writer: VF,
+ ) -> Result<(), std::io::Error>
+ where
+ KF: Fn(&mut Self, KT) -> Result<(), std::io::Error> + Copy,
+ VF: Fn(&mut Self, VT) -> Result<(), std::io::Error> + Copy,
+ Self: Sized;
+
+ fn write_byte(&mut self, n: u8) -> Result<(), std::io::Error>;
+ fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), std::io::Error>;
+ fn write_varint(&mut self, value: i32) -> Result<(), std::io::Error>;
+ fn write_utf_with_len(&mut self, string: &str, len: usize) -> Result<(), std::io::Error>;
+ fn write_utf(&mut self, string: &str) -> Result<(), std::io::Error>;
+ fn write_short(&mut self, n: i16) -> Result<(), std::io::Error>;
+ fn write_byte_array(&mut self, bytes: &[u8]) -> Result<(), std::io::Error>;
+ fn write_int(&mut self, n: i32) -> Result<(), std::io::Error>;
+ fn write_boolean(&mut self, b: bool) -> Result<(), std::io::Error>;
+ fn write_nbt(&mut self, nbt: &azalea_nbt::Tag) -> Result<(), std::io::Error>;
+ fn write_long(&mut self, n: i64) -> Result<(), std::io::Error>;
+ fn write_resource_location(
+ &mut self,
+ location: &ResourceLocation,
+ ) -> Result<(), std::io::Error>;
+ fn write_float(&mut self, n: f32) -> Result<(), std::io::Error>;
+}
+
+#[async_trait]
+impl Writable for Vec<u8> {
+ fn write_list<F, T>(&mut self, list: &[T], writer: F) -> Result<(), std::io::Error>
+ where
+ F: FnOnce(&mut Self, &T) -> Result<(), std::io::Error> + Copy,
+ Self: Sized,
+ {
+ self.write_varint(list.len() as i32)?;
+ for item in list {
+ writer(self, item)?;
+ }
+ Ok(())
+ }
+
+ fn write_int_id_list(&mut self, list: &Vec<i32>) -> Result<(), std::io::Error> {
+ self.write_list(&list, |buf, n| buf.write_varint(*n))
+ }
+
+ fn write_map<KF, VF, KT, VT>(
+ &mut self,
+ map: Vec<(KT, VT)>,
+ key_writer: KF,
+ value_writer: VF,
+ ) -> Result<(), std::io::Error>
+ where
+ KF: Fn(&mut Self, KT) -> Result<(), std::io::Error> + Copy,
+ VF: Fn(&mut Self, VT) -> Result<(), std::io::Error> + Copy,
+ Self: Sized,
+ {
+ self.write_varint(map.len() as i32)?;
+ for (key, value) in map {
+ key_writer(self, key)?;
+ value_writer(self, value)?;
+ }
+ Ok(())
+ }
+
+ fn write_byte(&mut self, n: u8) -> Result<(), std::io::Error> {
+ WriteBytesExt::write_u8(self, n)
+ }
+
+ fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), std::io::Error> {
+ self.extend_from_slice(bytes);
+ Ok(())
+ }
+
+ fn write_varint(&mut self, mut value: i32) -> Result<(), std::io::Error> {
+ let mut buffer = [0];
+ if value == 0 {
+ self.write_all(&buffer).unwrap();
+ }
+ while value != 0 {
+ buffer[0] = (value & 0b0111_1111) as u8;
+ value = (value >> 7) & (i32::max_value() >> 6);
+ if value != 0 {
+ buffer[0] |= 0b1000_0000;
+ }
+ self.write_all(&buffer)?;
+ }
+ Ok(())
+ }
+
+ fn write_utf_with_len(&mut self, string: &str, len: usize) -> Result<(), std::io::Error> {
+ if string.len() > len {
+ panic!(
+ "String too big (was {} bytes encoded, max {})",
+ string.len(),
+ len
+ );
+ }
+ self.write_varint(string.len() as i32)?;
+ self.write_bytes(string.as_bytes())
+ }
+
+ fn write_utf(&mut self, string: &str) -> Result<(), std::io::Error> {
+ self.write_utf_with_len(string, MAX_STRING_LENGTH.into())
+ }
+
+ fn write_short(&mut self, n: i16) -> Result<(), std::io::Error> {
+ WriteBytesExt::write_i16::<BigEndian>(self, n)
+ }
+
+ fn write_byte_array(&mut self, bytes: &[u8]) -> Result<(), std::io::Error> {
+ self.write_varint(bytes.len() as i32)?;
+ self.write_bytes(bytes)
+ }
+
+ fn write_int(&mut self, n: i32) -> Result<(), std::io::Error> {
+ WriteBytesExt::write_i32::<BigEndian>(self, n)
+ }
+
+ fn write_boolean(&mut self, b: bool) -> Result<(), std::io::Error> {
+ self.write_byte(if b { 1 } else { 0 })
+ }
+
+ fn write_nbt(&mut self, nbt: &azalea_nbt::Tag) -> Result<(), std::io::Error> {
+ nbt.write(self)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))
+ }
+
+ fn write_long(&mut self, n: i64) -> Result<(), std::io::Error> {
+ WriteBytesExt::write_i64::<BigEndian>(self, n)
+ }
+
+ fn write_float(&mut self, n: f32) -> Result<(), std::io::Error> {
+ WriteBytesExt::write_f32::<BigEndian>(self, n)
+ }
+
+ fn write_resource_location(
+ &mut self,
+ location: &ResourceLocation,
+ ) -> Result<(), std::io::Error> {
+ self.write_utf(&location.to_string())
+ }
+}
+
+pub trait McBufWritable
+where
+ Self: Sized,
+{
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error>;
+}
+
+pub trait McBufVarintWritable
+where
+ Self: Sized,
+{
+ fn varint_write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error>;
+}
+
+impl McBufWritable for i32 {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ Writable::write_int(buf, *self)
+ }
+}
+
+impl McBufVarintWritable for i32 {
+ fn varint_write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ buf.write_varint(*self)
+ }
+}
+
+impl McBufWritable for Vec<u8> {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ buf.write_bytes(self)
+ }
+}
+
+// string
+impl McBufWritable for String {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ buf.write_utf(self)
+ }
+}
+
+// ResourceLocation
+impl McBufWritable for ResourceLocation {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ buf.write_resource_location(self)
+ }
+}
+
+// u32
+impl McBufWritable for u32 {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ i16::write_into(&(*self as i16), buf)
+ }
+}
+
+// u32 varint
+impl McBufVarintWritable for u32 {
+ fn varint_write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ i32::varint_write_into(&(*self as i32), buf)
+ }
+}
+
+// u16
+impl McBufWritable for u16 {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ i16::write_into(&(*self as i16), buf)
+ }
+}
+
+// u16 varint
+impl McBufVarintWritable for u16 {
+ fn varint_write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ i32::varint_write_into(&(*self as i32), buf)
+ }
+}
+
+// u8
+impl McBufWritable for u8 {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ buf.write_byte(*self)
+ }
+}
+
+// i16
+impl McBufWritable for i16 {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ Writable::write_short(buf, *self)
+ }
+}
+
+// i64
+impl McBufWritable for i64 {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ Writable::write_long(buf, *self)
+ }
+}
+
+// u64
+impl McBufWritable for u64 {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ i64::write_into(&(*self as i64), buf)
+ }
+}
+
+// bool
+impl McBufWritable for bool {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ buf.write_boolean(*self)
+ }
+}
+
+// i8
+impl McBufWritable for i8 {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ buf.write_byte(*self as u8)
+ }
+}
+
+// f32
+impl McBufWritable for f32 {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ buf.write_float(*self)
+ }
+}
+
+// GameType
+impl McBufWritable for GameType {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ u8::write_into(&self.to_id(), buf)
+ }
+}
+
+// Option<GameType>
+impl McBufWritable for Option<GameType> {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ buf.write_byte(GameType::to_optional_id(self) as u8)
+ }
+}
+
+// Vec<ResourceLocation>
+impl McBufWritable for Vec<ResourceLocation> {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ buf.write_list(self, |buf, resource_location| {
+ buf.write_resource_location(resource_location)
+ })
+ }
+}
+
+// azalea_nbt::Tag
+impl McBufWritable for azalea_nbt::Tag {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ buf.write_nbt(self)
+ }
+}
+
+// Difficulty
+impl McBufWritable for Difficulty {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ u8::write_into(&self.id(), buf)
+ }
+}
+
+// Component
+#[async_trait]
+impl McBufWritable for Component {
+ // async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ // where
+ // R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ // {
+ // let string = buf.read_utf().await?;
+ // let json: serde_json::Value = serde_json::from_str(string.as_str())
+ // .map_err(|e| "Component isn't valid JSON".to_string())?;
+ // let component = Component::deserialize(json).map_err(|e| e.to_string())?;
+ // Ok(component)
+ // }
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ // component doesn't have serialize implemented yet
+ todo!()
+ }
+}
diff --git a/azalea-protocol/src/packets/game/clientbound_change_difficulty_packet.rs b/azalea-protocol/src/packets/game/clientbound_change_difficulty_packet.rs
new file mode 100755
index 00000000..e12cfff3
--- /dev/null
+++ b/azalea-protocol/src/packets/game/clientbound_change_difficulty_packet.rs
@@ -0,0 +1,8 @@
+use azalea_core::difficulty::Difficulty;
+use packet_macros::GamePacket;
+
+#[derive(Clone, Debug, GamePacket)]
+pub struct ClientboundChangeDifficultyPacket {
+ pub difficulty: Difficulty,
+ pub locked: bool,
+}
diff --git a/azalea-protocol/src/packets/game/clientbound_custom_payload_packet.rs b/azalea-protocol/src/packets/game/clientbound_custom_payload_packet.rs
new file mode 100755
index 00000000..134a3109
--- /dev/null
+++ b/azalea-protocol/src/packets/game/clientbound_custom_payload_packet.rs
@@ -0,0 +1,8 @@
+use azalea_core::resource_location::ResourceLocation;
+use packet_macros::GamePacket;
+
+#[derive(Clone, Debug, GamePacket)]
+pub struct ClientboundCustomPayloadPacket {
+ pub identifier: ResourceLocation,
+ pub data: Vec<u8>,
+}
diff --git a/azalea-protocol/src/packets/game/clientbound_declare_commands_packet.rs b/azalea-protocol/src/packets/game/clientbound_declare_commands_packet.rs
new file mode 100755
index 00000000..1403630d
--- /dev/null
+++ b/azalea-protocol/src/packets/game/clientbound_declare_commands_packet.rs
@@ -0,0 +1,96 @@
+use std::hash::Hash;
+
+use async_trait::async_trait;
+use tokio::io::AsyncRead;
+
+use crate::mc_buf::{McBufReadable, Readable};
+
+use super::GamePacket;
+
+#[derive(Hash, Clone, Debug)]
+pub struct ClientboundDeclareCommandsPacket {
+ pub entries: Vec<BrigadierNodeStub>,
+ pub root_index: i32,
+}
+
+impl ClientboundDeclareCommandsPacket {
+ pub fn get(self) -> GamePacket {
+ GamePacket::ClientboundDeclareCommandsPacket(self)
+ }
+
+ pub fn write(&self, _buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ panic!("ClientboundDeclareCommandsPacket::write not implemented")
+ }
+
+ pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
+ buf: &mut T,
+ ) -> Result<GamePacket, String> {
+ let node_count = buf.read_varint().await?;
+ println!("node_count: {}", node_count);
+ let mut nodes = Vec::with_capacity(node_count as usize);
+ for _ in 0..node_count {
+ let node = BrigadierNodeStub::read_into(buf).await?;
+ nodes.push(node);
+ }
+ let root_index = buf.read_varint().await?;
+ Ok(GamePacket::ClientboundDeclareCommandsPacket(
+ ClientboundDeclareCommandsPacket {
+ entries: nodes,
+ root_index,
+ },
+ ))
+ }
+}
+
+#[derive(Hash, Debug, Clone)]
+pub struct BrigadierNodeStub {}
+
+// azalea_brigadier::tree::CommandNode
+#[async_trait]
+impl McBufReadable for BrigadierNodeStub {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ let flags = u8::read_into(buf).await?;
+
+ let node_type = flags & 0x03;
+ let is_executable = flags & 0x04 != 0;
+ let has_redirect = flags & 0x08 != 0;
+ let has_suggestions_type = flags & 0x10 != 0;
+ println!("flags: {}, node_type: {}, is_executable: {}, has_redirect: {}, has_suggestions_type: {}", flags, node_type, is_executable, has_redirect, has_suggestions_type);
+
+ let children = buf.read_int_id_list().await?;
+ println!("children: {:?}", children);
+ let redirect_node = if has_redirect {
+ buf.read_varint().await?
+ } else {
+ 0
+ };
+ println!("redirect_node: {}", redirect_node);
+
+ if node_type == 2 {
+ let name = buf.read_utf().await?;
+ println!("name: {}", name);
+
+ let resource_location = if has_suggestions_type {
+ Some(buf.read_resource_location().await?)
+ } else {
+ None
+ };
+ println!(
+ "node_type=2, flags={}, name={}, resource_location={:?}",
+ flags, name, resource_location
+ );
+ return Ok(BrigadierNodeStub {});
+ }
+ if node_type == 1 {
+ let name = buf.read_utf().await?;
+ println!("node_type=1, flags={}, name={}", flags, name);
+ return Ok(BrigadierNodeStub {});
+ }
+ println!("node_type={}, flags={}", node_type, flags);
+ Ok(BrigadierNodeStub {})
+ // return Err("Unknown node type".to_string());
+ }
+}
diff --git a/azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs b/azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs
new file mode 100644
index 00000000..74f5f72e
--- /dev/null
+++ b/azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs
@@ -0,0 +1,9 @@
+use azalea_chat::component::Component;
+use azalea_core::resource_location::ResourceLocation;
+use packet_macros::GamePacket;
+use serde::Deserialize;
+
+#[derive(Clone, Debug, GamePacket)]
+pub struct ClientboundDisconnectPacket {
+ pub reason: Component,
+}
diff --git a/azalea-protocol/src/packets/game/clientbound_login_packet.rs b/azalea-protocol/src/packets/game/clientbound_login_packet.rs
index 9043fa1a..57869202 100644..100755
--- a/azalea-protocol/src/packets/game/clientbound_login_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_login_packet.rs
@@ -1,26 +1,8 @@
-use super::GamePacket;
-use crate::mc_buf::{Readable, Writable};
use azalea_core::{game_type::GameType, resource_location::ResourceLocation};
+use packet_macros::GamePacket;
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundLoginPacket {
- // private final int playerId;
- // private final boolean hardcore;
- // private final GameType gameType;
- // @Nullable
- // private final GameType previousGameType;
- // private final Set<ResourceKey<Level>> levels;
- // private final RegistryAccess.RegistryHolder registryHolder;
- // private final DimensionType dimensionType;
- // private final ResourceKey<Level> dimension;
- // private final long seed;
- // private final int maxPlayers;
- // private final int chunkRadius;
- // private final int simulationDistance;
- // private final boolean reducedDebugInfo;
- // private final boolean showDeathScreen;
- // private final boolean isDebug;
- // private final boolean isFlat;
pub player_id: i32,
pub hardcore: bool,
pub game_type: GameType,
@@ -30,93 +12,14 @@ pub struct ClientboundLoginPacket {
pub dimension_type: azalea_nbt::Tag,
pub dimension: ResourceLocation,
pub seed: i64,
+ #[varint]
pub max_players: i32,
+ #[varint]
pub chunk_radius: i32,
+ #[varint]
pub simulation_distance: i32,
pub reduced_debug_info: bool,
pub show_death_screen: bool,
pub is_debug: bool,
pub is_flat: bool,
}
-
-impl ClientboundLoginPacket {
- pub fn get(self) -> GamePacket {
- GamePacket::ClientboundLoginPacket(self)
- }
-
- pub fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
- buf.write_int(self.player_id)?;
- buf.write_boolean(self.hardcore)?;
- buf.write_byte(self.game_type.to_id())?;
- buf.write_byte(GameType::to_optional_id(&self.previous_game_type) as u8)?;
- buf.write_list(&self.levels, |buf, resource_location| {
- buf.write_resource_location(resource_location)
- })?;
- buf.write_nbt(&self.registry_holder)?;
- buf.write_nbt(&self.dimension_type)?;
- buf.write_resource_location(&self.dimension)?;
- buf.write_long(self.seed)?;
- buf.write_varint(self.max_players)?;
- buf.write_varint(self.chunk_radius)?;
- buf.write_varint(self.simulation_distance)?;
- buf.write_boolean(self.reduced_debug_info)?;
- buf.write_boolean(self.show_death_screen)?;
- buf.write_boolean(self.is_debug)?;
- buf.write_boolean(self.is_flat)?;
- Ok(())
- }
-
- pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
- buf: &mut T,
- ) -> Result<GamePacket, String> {
- let player_id = buf.read_int().await?;
- let hardcore = buf.read_boolean().await?;
- let game_type = GameType::from_id(buf.read_byte().await?)?;
- let previous_game_type = GameType::from_optional_id(buf.read_byte().await? as i8)?;
-
- let mut levels = Vec::new();
- let length = buf.read_varint().await?;
- for _ in 0..length {
- levels.push(buf.read_resource_location().await?);
- }
-
- // println!("about to read nbt");
- // // read all the bytes into a buffer, print it, and panic
- // let mut registry_holder_buf = Vec::new();
- // buf.read_to_end(&mut registry_holder_buf).await.unwrap();
- // println!("{:?}", String::from_utf8_lossy(&registry_holder_buf));
- // panic!("");
-
- let registry_holder = buf.read_nbt().await?;
- let dimension_type = buf.read_nbt().await?;
- let dimension = buf.read_resource_location().await?;
- let seed = buf.read_long().await?;
- let max_players = buf.read_varint().await?;
- let chunk_radius = buf.read_varint().await?;
- let simulation_distance = buf.read_varint().await?;
- let reduced_debug_info = buf.read_boolean().await?;
- let show_death_screen = buf.read_boolean().await?;
- let is_debug = buf.read_boolean().await?;
- let is_flat = buf.read_boolean().await?;
-
- Ok(ClientboundLoginPacket {
- player_id,
- hardcore,
- game_type,
- previous_game_type,
- levels,
- registry_holder,
- dimension_type,
- dimension,
- seed,
- max_players,
- chunk_radius,
- simulation_distance,
- reduced_debug_info,
- show_death_screen,
- is_debug,
- is_flat,
- }
- .get())
- }
-}
diff --git a/azalea-protocol/src/packets/game/clientbound_player_abilities_packet.rs b/azalea-protocol/src/packets/game/clientbound_player_abilities_packet.rs
new file mode 100755
index 00000000..f4f528cf
--- /dev/null
+++ b/azalea-protocol/src/packets/game/clientbound_player_abilities_packet.rs
@@ -0,0 +1,57 @@
+// i don't know the actual name of this packet, i couldn't find it in the source code
+
+use crate::mc_buf::{McBufReadable, McBufWritable, Readable};
+use async_trait::async_trait;
+use packet_macros::GamePacket;
+use tokio::io::AsyncRead;
+
+#[derive(Clone, Debug, GamePacket)]
+pub struct ClientboundPlayerAbilitiesPacket {
+ pub flags: PlayerAbilitiesFlags,
+ pub flying_speed: f32,
+ /// Used for the fov
+ pub walking_speed: f32,
+}
+
+#[derive(Clone, Debug)]
+pub struct PlayerAbilitiesFlags {
+ pub invulnerable: bool,
+ pub flying: bool,
+ pub can_fly: bool,
+ pub instant_break: bool,
+}
+
+#[async_trait]
+impl McBufReadable for PlayerAbilitiesFlags {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ let byte = buf.read_byte().await?;
+ Ok(PlayerAbilitiesFlags {
+ invulnerable: byte & 1 != 0,
+ flying: byte & 2 != 0,
+ can_fly: byte & 4 != 0,
+ instant_break: byte & 8 != 0,
+ })
+ }
+}
+
+impl McBufWritable for PlayerAbilitiesFlags {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ let mut byte = 0;
+ if self.invulnerable {
+ byte = byte | 1;
+ }
+ if self.flying {
+ byte = byte | 2;
+ }
+ if self.can_fly {
+ byte = byte | 4;
+ }
+ if self.instant_break {
+ byte = byte | 8;
+ }
+ u8::write_into(&byte, buf)
+ }
+}
diff --git a/azalea-protocol/src/packets/game/clientbound_set_carried_item_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_carried_item_packet.rs
new file mode 100755
index 00000000..4f0bf575
--- /dev/null
+++ b/azalea-protocol/src/packets/game/clientbound_set_carried_item_packet.rs
@@ -0,0 +1,7 @@
+use packet_macros::GamePacket;
+
+/// Sent to change the player's slot selection.
+#[derive(Clone, Debug, GamePacket)]
+pub struct ClientboundSetCarriedItemPacket {
+ pub slot: u8,
+}
diff --git a/azalea-protocol/src/packets/game/clientbound_update_tags_packet.rs b/azalea-protocol/src/packets/game/clientbound_update_tags_packet.rs
new file mode 100755
index 00000000..a2f17370
--- /dev/null
+++ b/azalea-protocol/src/packets/game/clientbound_update_tags_packet.rs
@@ -0,0 +1,105 @@
+use std::collections::HashMap;
+
+use async_trait::async_trait;
+use azalea_core::resource_location::ResourceLocation;
+use packet_macros::GamePacket;
+use tokio::io::AsyncRead;
+
+use crate::mc_buf::{McBufReadable, McBufWritable, Readable, Writable};
+
+#[derive(Clone, Debug, GamePacket)]
+pub struct ClientboundUpdateTagsPacket {
+ pub tags: HashMap<ResourceLocation, Vec<Tags>>,
+}
+
+#[derive(Clone, Debug)]
+pub struct Tags {
+ pub name: ResourceLocation,
+ pub elements: Vec<i32>,
+}
+
+#[async_trait]
+impl McBufReadable for HashMap<ResourceLocation, Vec<Tags>> {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ println!("reading tags!");
+ let length = buf.read_varint().await? as usize;
+ println!("length: {}", length);
+ let mut data = HashMap::with_capacity(length);
+ for _ in 0..length {
+ let tag_type = buf.read_resource_location().await?;
+ println!("read tag type {}", tag_type);
+ let tags_count = buf.read_varint().await? as usize;
+ let mut tags_vec = Vec::with_capacity(tags_count);
+ for _ in 0..tags_count {
+ let tags = Tags::read_into(buf).await?;
+ tags_vec.push(tags);
+ }
+ println!("tags: {} {:?}", tag_type, tags_vec);
+ data.insert(tag_type, tags_vec);
+ }
+ Ok(data)
+ }
+}
+
+impl McBufWritable for HashMap<ResourceLocation, Vec<Tags>> {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ buf.write_varint(self.len() as i32)?;
+ for (k, v) in self {
+ k.write_into(buf)?;
+ v.write_into(buf)?;
+ }
+ Ok(())
+ }
+}
+
+#[async_trait]
+impl McBufReadable for Vec<Tags> {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ let tags_count = buf.read_varint().await? as usize;
+ let mut tags_vec = Vec::with_capacity(tags_count);
+ for _ in 0..tags_count {
+ let tags = Tags::read_into(buf).await?;
+ tags_vec.push(tags);
+ }
+ Ok(tags_vec)
+ }
+}
+
+impl McBufWritable for Vec<Tags> {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ buf.write_varint(self.len() as i32)?;
+ for tag in self {
+ tag.write_into(buf)?;
+ }
+ Ok(())
+ }
+}
+
+#[async_trait]
+impl McBufReadable for Tags {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ println!("reading tags.");
+ let name = buf.read_resource_location().await?;
+ println!("tags name: {}", name);
+ let elements = buf.read_int_id_list().await?;
+ println!("elements: {:?}", elements);
+ Ok(Tags { name, elements })
+ }
+}
+
+impl McBufWritable for Tags {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ self.name.write_into(buf)?;
+ buf.write_int_id_list(&self.elements)?;
+ Ok(())
+ }
+}
diff --git a/azalea-protocol/src/packets/game/clientbound_update_view_distance_packet.rs b/azalea-protocol/src/packets/game/clientbound_update_view_distance_packet.rs
new file mode 100755
index 00000000..8301c089
--- /dev/null
+++ b/azalea-protocol/src/packets/game/clientbound_update_view_distance_packet.rs
@@ -0,0 +1,9 @@
+// i don't know the actual name of this packet, i couldn't find it in the source code
+
+use packet_macros::GamePacket;
+
+#[derive(Clone, Debug, GamePacket)]
+pub struct ClientboundUpdateViewDistancePacket {
+ #[varint]
+ pub view_distance: i32,
+}
diff --git a/azalea-protocol/src/packets/game/mod.rs b/azalea-protocol/src/packets/game/mod.rs
index 00fa1d75..dde3f753 100644..100755
--- a/azalea-protocol/src/packets/game/mod.rs
+++ b/azalea-protocol/src/packets/game/mod.rs
@@ -1,46 +1,27 @@
+pub mod clientbound_change_difficulty_packet;
+pub mod clientbound_custom_payload_packet;
+pub mod clientbound_declare_commands_packet;
+pub mod clientbound_disconnect_packet;
pub mod clientbound_login_packet;
+pub mod clientbound_player_abilities_packet;
+pub mod clientbound_set_carried_item_packet;
+pub mod clientbound_update_tags_packet;
+pub mod clientbound_update_view_distance_packet;
-use super::ProtocolPacket;
-use crate::connect::PacketFlow;
-use async_trait::async_trait;
+use packet_macros::declare_state_packets;
-#[derive(Clone, Debug)]
-pub enum GamePacket
-where
- Self: Sized,
-{
- ClientboundLoginPacket(clientbound_login_packet::ClientboundLoginPacket),
-}
-
-#[async_trait]
-impl ProtocolPacket for GamePacket {
- fn id(&self) -> u32 {
- match self {
- GamePacket::ClientboundLoginPacket(_packet) => 0x00,
- }
- }
-
- fn write(&self, _buf: &mut Vec<u8>) {}
-
- /// Read a packet by its id, ConnectionProtocol, and flow
- async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
- id: u32,
- flow: &PacketFlow,
- buf: &mut T,
- ) -> Result<GamePacket, String>
- where
- Self: Sized,
- {
- Ok(match flow {
- PacketFlow::ServerToClient => match id {
- 0x26 => clientbound_login_packet::ClientboundLoginPacket::read(buf).await?,
-
- _ => return Err(format!("Unknown ServerToClient game packet id: {}", id)),
- },
- PacketFlow::ClientToServer => match id {
- // 0x00 => serverbound_hello_packet::ServerboundHelloPacket::read(buf).await?,
- _ => return Err(format!("Unknown ClientToServer game packet id: {}", id)),
- },
- })
+declare_state_packets!(
+ GamePacket,
+ Serverbound => {},
+ Clientbound => {
+ 0x0e: clientbound_change_difficulty_packet::ClientboundChangeDifficultyPacket,
+ 0x12: clientbound_declare_commands_packet::ClientboundDeclareCommandsPacket,
+ 0x1a: clientbound_disconnect_packet::ClientboundDisconnectPacket,
+ 0x18: clientbound_custom_payload_packet::ClientboundCustomPayloadPacket,
+ 0x26: clientbound_login_packet::ClientboundLoginPacket,
+ 0x32: clientbound_player_abilities_packet::ClientboundPlayerAbilitiesPacket,
+ 0x48: clientbound_set_carried_item_packet::ClientboundSetCarriedItemPacket,
+ 0x4a: clientbound_update_view_distance_packet::ClientboundUpdateViewDistancePacket,
+ 0x67: clientbound_update_tags_packet::ClientboundUpdateTagsPacket
}
-}
+);
diff --git a/azalea-protocol/src/packets/handshake/client_intention_packet.rs b/azalea-protocol/src/packets/handshake/client_intention_packet.rs
index 939a695e..a92d65f6 100644..100755
--- a/azalea-protocol/src/packets/handshake/client_intention_packet.rs
+++ b/azalea-protocol/src/packets/handshake/client_intention_packet.rs
@@ -1,34 +1,34 @@
-use std::hash::Hash;
-
use crate::{mc_buf::Writable, packets::ConnectionProtocol};
+use packet_macros::HandshakePacket;
+use std::hash::Hash;
use super::HandshakePacket;
-#[derive(Hash, Clone, Debug)]
+#[derive(Hash, Clone, Debug, HandshakePacket)]
pub struct ClientIntentionPacket {
+ #[varint]
pub protocol_version: u32,
pub hostname: String,
pub port: u16,
- /// 1 for status, 2 for login
pub intention: ConnectionProtocol,
}
-impl ClientIntentionPacket {
- pub fn get(self) -> HandshakePacket {
- HandshakePacket::ClientIntentionPacket(self)
- }
+// impl ClientIntentionPacket {
+// pub fn get(self) -> HandshakePacket {
+// HandshakePacket::ClientIntentionPacket(self)
+// }
- pub fn write(&self, buf: &mut Vec<u8>) {
- buf.write_varint(self.protocol_version as i32).unwrap();
- buf.write_utf(&self.hostname).unwrap();
- buf.write_short(self.port).unwrap();
- buf.write_varint(self.intention.clone() as i32).unwrap();
- }
+// pub fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+// buf.write_varint(self.protocol_version as i32)?;
+// buf.write_utf(&self.hostname)?;
+// buf.write_short(self.port as i16)?;
+// buf.write_varint(self.intention as i32)?;
+// Ok(())
+// }
- pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
- _buf: &mut T,
- ) -> Result<HandshakePacket, String> {
- Err("ClientIntentionPacket::parse not implemented".to_string())
- // Ok(ClientIntentionPacket {}.get())
- }
-}
+// pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
+// buf: &mut T,
+// ) -> Result<HandshakePacket, String> {
+// todo!()
+// }
+// }
diff --git a/azalea-protocol/src/packets/handshake/mod.rs b/azalea-protocol/src/packets/handshake/mod.rs
index 1d753645..88d9939b 100644..100755
--- a/azalea-protocol/src/packets/handshake/mod.rs
+++ b/azalea-protocol/src/packets/handshake/mod.rs
@@ -1,48 +1,11 @@
pub mod client_intention_packet;
-use async_trait::async_trait;
+use packet_macros::declare_state_packets;
-use crate::connect::PacketFlow;
-
-use super::ProtocolPacket;
-
-#[derive(Clone, Debug)]
-pub enum HandshakePacket
-where
- Self: Sized,
-{
- ClientIntentionPacket(client_intention_packet::ClientIntentionPacket),
-}
-
-#[async_trait]
-impl ProtocolPacket for HandshakePacket {
- fn id(&self) -> u32 {
- match self {
- HandshakePacket::ClientIntentionPacket(_packet) => 0x00,
- }
- }
-
- fn write(&self, buf: &mut Vec<u8>) {
- match self {
- HandshakePacket::ClientIntentionPacket(packet) => packet.write(buf),
- }
- }
-
- /// Read a packet by its id, ConnectionProtocol, and flow
- async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
- id: u32,
- flow: &PacketFlow,
- buf: &mut T,
- ) -> Result<HandshakePacket, String>
- where
- Self: Sized,
- {
- match flow {
- PacketFlow::ServerToClient => Err("HandshakePacket::read not implemented".to_string()),
- PacketFlow::ClientToServer => match id {
- 0x00 => Ok(client_intention_packet::ClientIntentionPacket::read(buf).await?),
- _ => Err(format!("Unknown ClientToServer status packet id: {}", id)),
- },
- }
+declare_state_packets!(
+ HandshakePacket,
+ Serverbound => {},
+ Clientbound => {
+ 0x00: client_intention_packet::ClientIntentionPacket,
}
-}
+);
diff --git a/azalea-protocol/src/packets/login/clientbound_custom_query_packet.rs b/azalea-protocol/src/packets/login/clientbound_custom_query_packet.rs
index 2bc1fc1e..3138106e 100644..100755
--- a/azalea-protocol/src/packets/login/clientbound_custom_query_packet.rs
+++ b/azalea-protocol/src/packets/login/clientbound_custom_query_packet.rs
@@ -1,37 +1,11 @@
-use super::LoginPacket;
-use crate::mc_buf::{Readable, Writable};
use azalea_core::resource_location::ResourceLocation;
+use packet_macros::LoginPacket;
use std::hash::Hash;
-#[derive(Hash, Clone, Debug)]
+#[derive(Hash, Clone, Debug, LoginPacket)]
pub struct ClientboundCustomQueryPacket {
+ #[varint]
pub transaction_id: u32,
pub identifier: ResourceLocation,
pub data: Vec<u8>,
}
-
-impl ClientboundCustomQueryPacket {
- pub fn get(self) -> LoginPacket {
- LoginPacket::ClientboundCustomQueryPacket(self)
- }
-
- pub fn write(&self, buf: &mut Vec<u8>) {
- buf.write_varint(self.transaction_id as i32).unwrap();
- buf.write_utf(self.identifier.to_string().as_str()).unwrap();
- buf.write_bytes(&self.data).unwrap();
- }
-
- pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
- buf: &mut T,
- ) -> Result<LoginPacket, String> {
- let transaction_id = buf.read_varint().await? as u32;
- let identifier = ResourceLocation::new(&buf.read_utf().await?)?;
- let data = buf.read_bytes(1048576).await?;
- Ok(ClientboundCustomQueryPacket {
- transaction_id,
- identifier,
- data,
- }
- .get())
- }
-}
diff --git a/azalea-protocol/src/packets/login/clientbound_game_profile_packet.rs b/azalea-protocol/src/packets/login/clientbound_game_profile_packet.rs
index 1a752c1a..c54aa819 100644..100755
--- a/azalea-protocol/src/packets/login/clientbound_game_profile_packet.rs
+++ b/azalea-protocol/src/packets/login/clientbound_game_profile_packet.rs
@@ -14,11 +14,12 @@ impl ClientboundGameProfilePacket {
LoginPacket::ClientboundGameProfilePacket(self)
}
- pub fn write(&self, buf: &mut Vec<u8>) {
+ pub fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
for n in self.game_profile.uuid.to_int_array() {
buf.write_int(n as i32).unwrap();
}
buf.write_utf(self.game_profile.name.as_str()).unwrap();
+ Ok(())
}
pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
diff --git a/azalea-protocol/src/packets/login/clientbound_hello_packet.rs b/azalea-protocol/src/packets/login/clientbound_hello_packet.rs
index e0b865be..9d0cec39 100644..100755
--- a/azalea-protocol/src/packets/login/clientbound_hello_packet.rs
+++ b/azalea-protocol/src/packets/login/clientbound_hello_packet.rs
@@ -16,7 +16,7 @@ impl ClientboundHelloPacket {
LoginPacket::ClientboundHelloPacket(self)
}
- pub fn write(&self, _buf: &mut Vec<u8>) {
+ pub fn write(&self, _buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
panic!("ClientboundHelloPacket::write not implemented")
}
diff --git a/azalea-protocol/src/packets/login/clientbound_login_compression_packet.rs b/azalea-protocol/src/packets/login/clientbound_login_compression_packet.rs
index af355192..a88c6cbf 100644..100755
--- a/azalea-protocol/src/packets/login/clientbound_login_compression_packet.rs
+++ b/azalea-protocol/src/packets/login/clientbound_login_compression_packet.rs
@@ -14,8 +14,9 @@ impl ClientboundLoginCompressionPacket {
LoginPacket::ClientboundLoginCompressionPacket(self)
}
- pub fn write(&self, buf: &mut Vec<u8>) {
+ pub fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
buf.write_varint(self.compression_threshold).unwrap();
+ Ok(())
}
pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
diff --git a/azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs b/azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs
new file mode 100644
index 00000000..28d91c79
--- /dev/null
+++ b/azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs
@@ -0,0 +1,7 @@
+use azalea_chat::component::Component;
+use packet_macros::LoginPacket;
+
+#[derive(Clone, Debug, LoginPacket)]
+pub struct ClientboundLoginDisconnectPacket {
+ pub reason: Component,
+}
diff --git a/azalea-protocol/src/packets/login/mod.rs b/azalea-protocol/src/packets/login/mod.rs
index 4d490d08..ab68518c 100644..100755
--- a/azalea-protocol/src/packets/login/mod.rs
+++ b/azalea-protocol/src/packets/login/mod.rs
@@ -2,78 +2,22 @@ pub mod clientbound_custom_query_packet;
pub mod clientbound_game_profile_packet;
pub mod clientbound_hello_packet;
pub mod clientbound_login_compression_packet;
+pub mod clientbound_login_disconnect_packet;
pub mod serverbound_hello_packet;
-use super::ProtocolPacket;
-use crate::connect::PacketFlow;
-use async_trait::async_trait;
+use packet_macros::declare_state_packets;
-#[derive(Clone, Debug)]
-pub enum LoginPacket
-where
- Self: Sized,
-{
- ClientboundCustomQueryPacket(clientbound_custom_query_packet::ClientboundCustomQueryPacket),
- ClientboundGameProfilePacket(clientbound_game_profile_packet::ClientboundGameProfilePacket),
- ClientboundHelloPacket(clientbound_hello_packet::ClientboundHelloPacket),
- ClientboundLoginCompressionPacket(
- clientbound_login_compression_packet::ClientboundLoginCompressionPacket,
- ),
- ServerboundHelloPacket(serverbound_hello_packet::ServerboundHelloPacket),
-}
-
-#[async_trait]
-impl ProtocolPacket for LoginPacket {
- fn id(&self) -> u32 {
- match self {
- LoginPacket::ClientboundCustomQueryPacket(_packet) => 0x04,
- LoginPacket::ClientboundGameProfilePacket(_packet) => 0x02,
- LoginPacket::ClientboundHelloPacket(_packet) => 0x01,
- LoginPacket::ClientboundLoginCompressionPacket(_packet) => 0x03,
- LoginPacket::ServerboundHelloPacket(_packet) => 0x00,
- }
- }
-
- fn write(&self, buf: &mut Vec<u8>) {
- match self {
- LoginPacket::ClientboundCustomQueryPacket(packet) => packet.write(buf),
- LoginPacket::ClientboundGameProfilePacket(packet) => packet.write(buf),
- LoginPacket::ClientboundHelloPacket(packet) => packet.write(buf),
- LoginPacket::ClientboundLoginCompressionPacket(packet) => packet.write(buf),
- LoginPacket::ServerboundHelloPacket(packet) => packet.write(buf),
- }
- }
-
- /// Read a packet by its id, ConnectionProtocol, and flow
- async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
- id: u32,
- flow: &PacketFlow,
- buf: &mut T,
- ) -> Result<LoginPacket, String>
- where
- Self: Sized,
- {
- Ok(match flow {
- PacketFlow::ServerToClient => match id {
- 0x01 => clientbound_hello_packet::ClientboundHelloPacket::read(buf).await?,
- 0x02 => {
- clientbound_game_profile_packet::ClientboundGameProfilePacket::read(buf).await?
- }
- 0x04 => {
- clientbound_custom_query_packet::ClientboundCustomQueryPacket::read(buf).await?
- }
- 0x03 => {
- clientbound_login_compression_packet::ClientboundLoginCompressionPacket::read(
- buf,
- )
- .await?
- }
- _ => return Err(format!("Unknown ServerToClient login packet id: {}", id)),
- },
- PacketFlow::ClientToServer => match id {
- 0x00 => serverbound_hello_packet::ServerboundHelloPacket::read(buf).await?,
- _ => return Err(format!("Unknown ClientToServer login packet id: {}", id)),
- },
- })
+declare_state_packets!(
+ LoginPacket,
+ Serverbound => {
+ 0x00: serverbound_hello_packet::ServerboundHelloPacket,
+ },
+ Clientbound => {
+ // 0x00: clientbound_login_disconnect_packet::ClientboundLoginDisconnectPacket,
+ 26: clientbound_login_disconnect_packet::ClientboundLoginDisconnectPacket,
+ 0x01: clientbound_hello_packet::ClientboundHelloPacket,
+ 0x02: clientbound_game_profile_packet::ClientboundGameProfilePacket,
+ 0x03: clientbound_login_compression_packet::ClientboundLoginCompressionPacket,
+ 0x04: clientbound_custom_query_packet::ClientboundCustomQueryPacket,
}
-}
+);
diff --git a/azalea-protocol/src/packets/login/serverbound_hello_packet.rs b/azalea-protocol/src/packets/login/serverbound_hello_packet.rs
index 0039cbce..a72480f2 100644..100755
--- a/azalea-protocol/src/packets/login/serverbound_hello_packet.rs
+++ b/azalea-protocol/src/packets/login/serverbound_hello_packet.rs
@@ -14,8 +14,9 @@ impl ServerboundHelloPacket {
LoginPacket::ServerboundHelloPacket(self)
}
- pub fn write(&self, buf: &mut Vec<u8>) {
+ pub fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
buf.write_utf(&self.username).unwrap();
+ Ok(())
}
pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
diff --git a/azalea-protocol/src/packets/mod.rs b/azalea-protocol/src/packets/mod.rs
index e065b65c..98741a75 100644..100755
--- a/azalea-protocol/src/packets/mod.rs
+++ b/azalea-protocol/src/packets/mod.rs
@@ -3,13 +3,18 @@ pub mod handshake;
pub mod login;
pub mod status;
+use crate::{
+ connect::PacketFlow,
+ mc_buf::{McBufReadable, McBufWritable, Readable, Writable},
+};
use async_trait::async_trait;
+use num_derive::FromPrimitive;
+use num_traits::FromPrimitive;
+use tokio::io::AsyncRead;
-use crate::connect::PacketFlow;
+pub const PROTOCOL_VERSION: u32 = 758;
-pub const PROTOCOL_VERSION: u32 = 757;
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive)]
pub enum ConnectionProtocol {
Handshake = -1,
Game = 0,
@@ -42,5 +47,22 @@ where
where
Self: Sized;
- fn write(&self, buf: &mut Vec<u8>);
+ fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error>;
+}
+
+#[async_trait]
+impl McBufReadable for ConnectionProtocol {
+ async fn read_into<R>(buf: &mut R) -> Result<Self, String>
+ where
+ R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ {
+ ConnectionProtocol::from_i32(buf.read_varint().await?)
+ .ok_or_else(|| "Invalid intention".to_string())
+ }
+}
+
+impl McBufWritable for ConnectionProtocol {
+ fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ buf.write_varint(*self as i32)
+ }
}
diff --git a/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs b/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs
index 38270ad1..884cf408 100644..100755
--- a/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs
+++ b/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs
@@ -36,10 +36,12 @@ pub struct ClientboundStatusResponsePacket {
impl ClientboundStatusResponsePacket {
pub fn get(self) -> StatusPacket {
- StatusPacket::ClientboundStatusResponsePacket(Box::new(self))
+ StatusPacket::ClientboundStatusResponsePacket(self)
}
- pub fn write(&self, _buf: &mut Vec<u8>) {}
+ pub fn write(&self, _buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
+ Ok(())
+ }
pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
buf: &mut T,
diff --git a/azalea-protocol/src/packets/status/mod.rs b/azalea-protocol/src/packets/status/mod.rs
index 6383bae8..56aa577e 100644..100755
--- a/azalea-protocol/src/packets/status/mod.rs
+++ b/azalea-protocol/src/packets/status/mod.rs
@@ -1,65 +1,14 @@
pub mod clientbound_status_response_packet;
pub mod serverbound_status_request_packet;
-use async_trait::async_trait;
-
-use crate::connect::PacketFlow;
-
-use super::ProtocolPacket;
-
-#[derive(Clone, Debug)]
-pub enum StatusPacket
-where
- Self: Sized,
-{
- ServerboundStatusRequestPacket(
- serverbound_status_request_packet::ServerboundStatusRequestPacket,
- ),
- ClientboundStatusResponsePacket(
- Box<clientbound_status_response_packet::ClientboundStatusResponsePacket>,
- ),
-}
-
-#[async_trait]
-impl ProtocolPacket for StatusPacket {
- fn id(&self) -> u32 {
- match self {
- StatusPacket::ServerboundStatusRequestPacket(_packet) => 0x00,
- StatusPacket::ClientboundStatusResponsePacket(_packet) => 0x00,
- }
- }
-
- fn write(&self, buf: &mut Vec<u8>) {
- match self {
- StatusPacket::ServerboundStatusRequestPacket(packet) => packet.write(buf),
- StatusPacket::ClientboundStatusResponsePacket(packet) => packet.write(buf),
- }
- }
-
- /// Read a packet by its id, ConnectionProtocol, and flow
- async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
- id: u32,
- flow: &PacketFlow,
- buf: &mut T,
- ) -> Result<StatusPacket, String>
- where
- Self: Sized,
- {
- match flow {
- PacketFlow::ServerToClient => match id {
- 0x00 => Ok(
- clientbound_status_response_packet::ClientboundStatusResponsePacket::read(buf)
- .await?,
- ),
- _ => Err(format!("Unknown ServerToClient status packet id: {}", id)),
- },
- PacketFlow::ClientToServer => match id {
- 0x00 => Ok(
- serverbound_status_request_packet::ServerboundStatusRequestPacket::read(buf)
- .await?,
- ),
- _ => Err(format!("Unknown ClientToServer status packet id: {}", id)),
- },
- }
+use packet_macros::declare_state_packets;
+
+declare_state_packets!(
+ StatusPacket,
+ Serverbound => {
+ 0x00: serverbound_status_request_packet::ServerboundStatusRequestPacket,
+ },
+ Clientbound => {
+ 0x00: clientbound_status_response_packet::ClientboundStatusResponsePacket,
}
-}
+);
diff --git a/azalea-protocol/src/packets/status/serverbound_status_request_packet.rs b/azalea-protocol/src/packets/status/serverbound_status_request_packet.rs
index 3a25ac42..e77687ec 100644..100755
--- a/azalea-protocol/src/packets/status/serverbound_status_request_packet.rs
+++ b/azalea-protocol/src/packets/status/serverbound_status_request_packet.rs
@@ -1,22 +1,5 @@
+use packet_macros::StatusPacket;
use std::hash::Hash;
-use super::StatusPacket;
-
-#[derive(Hash, Clone, Debug)]
+#[derive(Clone, Debug, StatusPacket)]
pub struct ServerboundStatusRequestPacket {}
-
-impl ServerboundStatusRequestPacket {
- pub fn get(self) -> StatusPacket {
- StatusPacket::ServerboundStatusRequestPacket(self)
- }
-
- pub fn write(&self, _buf: &mut Vec<u8>) {
- panic!("ServerboundStatusRequestPacket::write not implemented")
- }
-
- pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
- _buf: &mut T,
- ) -> Result<StatusPacket, String> {
- Err("ServerboundStatusRequestPacket::read not implemented".to_string())
- }
-}
diff --git a/azalea-protocol/src/read.rs b/azalea-protocol/src/read.rs
index ed36d6b6..ed36d6b6 100644..100755
--- a/azalea-protocol/src/read.rs
+++ b/azalea-protocol/src/read.rs
diff --git a/azalea-protocol/src/resolver.rs b/azalea-protocol/src/resolver.rs
index 24687a6e..24687a6e 100644..100755
--- a/azalea-protocol/src/resolver.rs
+++ b/azalea-protocol/src/resolver.rs
diff --git a/azalea-protocol/src/write.rs b/azalea-protocol/src/write.rs
index 3898e74a..821ed6e8 100644..100755
--- a/azalea-protocol/src/write.rs
+++ b/azalea-protocol/src/write.rs
@@ -17,7 +17,7 @@ fn packet_encoder<P: ProtocolPacket + std::fmt::Debug>(packet: &P) -> Result<Vec
let mut buf = Vec::new();
buf.write_varint(packet.id() as i32)
.map_err(|e| e.to_string())?;
- packet.write(&mut buf);
+ packet.write(&mut buf).map_err(|e| e.to_string())?;
if buf.len() > MAXIMUM_UNCOMPRESSED_LENGTH as usize {
return Err(format!(
"Packet too big (is {} bytes, should be less than {}): {:?}",
diff --git a/bot/Cargo.toml b/bot/Cargo.toml
index fd6ad067..fd6ad067 100644..100755
--- a/bot/Cargo.toml
+++ b/bot/Cargo.toml
diff --git a/bot/src/main.rs b/bot/src/main.rs
index 435803e2..7d129478 100644
--- a/bot/src/main.rs
+++ b/bot/src/main.rs
@@ -1,12 +1,15 @@
-use azalea_client::connect::join_server;
-
#[tokio::main]
async fn main() {
println!("Hello, world!");
// let address = "95.111.249.143:10000";
- let address = "localhost:58566";
- let _response = join_server(&address.try_into().unwrap()).await.unwrap();
+ let address = "localhost:52400";
+ // let response = azalea_client::ping::ping_server(&address.try_into().unwrap())
+ // .await
+ // .unwrap();
// println!("{}", response.description.to_ansi(None));
+ let _response = azalea_client::connect::join_server(&address.try_into().unwrap())
+ .await
+ .unwrap();
println!("connected");
}