mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
merge main
This commit is contained in:
commit
538bd78476
189 changed files with 4131 additions and 3114 deletions
|
@ -19,7 +19,9 @@ is breaking anyways, semantic versioning is not followed.
|
|||
- Add auto-reconnecting which is enabled by default.
|
||||
- `client.start_use_item()`.
|
||||
- The pathfinder no longer avoids slabs, stairs, and dirt path blocks.
|
||||
- The pathfinder now immediately recalculates if blocks are placed in its path.
|
||||
- The reach distance for the pathfinder `ReachBlockPosGoal` is now configurable. (@x-osc)
|
||||
- azalea-brigadier now supports suggestions, command contexts, result consumers, and returning errors with `ArgumentBuilder::executes_result`.
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
10
README.md
10
README.md
|
@ -13,15 +13,15 @@ A collection of Rust crates for making Minecraft bots, clients, and tools.
|
|||
_Currently supported Minecraft version: `1.21.6-pre2`._
|
||||
|
||||
> [!WARNING]
|
||||
> Azalea is still unfinished, though most crates are in a useable state
|
||||
> Azalea is still unfinished, though most crates are in a useable state.
|
||||
|
||||
## Features
|
||||
|
||||
- [Accurate physics](https://github.com/azalea-rs/azalea/blob/main/azalea-physics/src/lib.rs) (but some features like entity pushing and elytras aren't yet implemented)
|
||||
- [Accurate physics](https://github.com/azalea-rs/azalea/blob/main/azalea-physics/src/lib.rs) (but some features like entity pushing and elytras aren't implemented yet)
|
||||
- [Pathfinder](https://azalea.matdoes.dev/azalea/pathfinder/index.html)
|
||||
- [Swarms](https://azalea.matdoes.dev/azalea/swarm/index.html)
|
||||
- [Breaking blocks](https://azalea.matdoes.dev/azalea/struct.Client.html#method.mine)
|
||||
- [Block interactions & building](https://azalea.matdoes.dev/azalea/struct.Client.html#method.block_interact) (this doesn't predict the block interactions/placement on the client yet but it's usually fine)
|
||||
- [Block interactions & building](https://azalea.matdoes.dev/azalea/struct.Client.html#method.block_interact) (this doesn't predict the block interactions/placement on the client yet, but it's usually fine)
|
||||
- [Inventories](https://azalea.matdoes.dev/azalea/struct.Client.html#impl-ContainerClientExt-for-Client)
|
||||
- [Attacking entities](https://azalea.matdoes.dev/azalea/struct.Client.html#method.attack) (but you can't get the entity at the crosshair yet)
|
||||
- [Plugins](#plugins)
|
||||
|
@ -53,7 +53,7 @@ If you'd like to chat about Azalea, you can join the Matrix space at [#azalea:ma
|
|||
|
||||
Here's an incomplete list of bots built using Azalea, primarily intended as a reference in addition to the existing documentation and examples:
|
||||
|
||||
- [ShayBox/ShaysBot](https://github.com/ShayBox/ShaysBot) - Pearl statis bot featuring a Discord bot, an HTTP API, and more.
|
||||
- [ShayBox/ShaysBot](https://github.com/ShayBox/ShaysBot) - Pearl stasis bot featuring a Discord bot, an HTTP API, and more.
|
||||
- [EnderKill98/statis-bot](https://github.com/EnderKill98/stasis-bot) - This bot can automatically detect thrown pearls and later walk there and pull them for you.
|
||||
- [as1100k/aether](https://github.com/as1100k/aether) - Collection of Minecraft bots and plugins.
|
||||
- [mat-1/potato-bot-2](https://github.com/mat-1/potato-bot-2) - Hardened Discord chat bridge created for the LiveOverflow SMP.
|
||||
|
@ -63,7 +63,7 @@ You can see more projects built with Azalea in the [GitHub dependency graph](htt
|
|||
|
||||
## Plugins
|
||||
|
||||
Azalea has support for Bevy plugins, which can significantly alter its functionality. Here's some plugins you may find useful:
|
||||
Azalea has support for Bevy plugins, which can significantly alter its functionality. Here are some plugins that you may find useful:
|
||||
|
||||
- [azalea-rs/azalea-viaversion](https://github.com/azalea-rs/azalea-viaversion) - Multi-version compatibility for your Azalea bots using ViaProxy.
|
||||
- [azalea-rs/azalea-hax](https://github.com/azalea-rs/azalea-hax) - Anti-knockback.
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
A port of Mojang's Authlib and launcher authentication.
|
||||
|
||||
The default location of Azalea's cache is at `~/.minecraft/azalea-auth.json`. You can delete or modify this file if you'd like to associate an email with a different account.
|
||||
|
||||
# Examples
|
||||
|
||||
```no_run
|
||||
|
@ -24,4 +26,4 @@ async fn main() {
|
|||
}
|
||||
```
|
||||
|
||||
Thanks to [wiki contributors](https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Microsoft_Authentication_Scheme), [Overhash](https://gist.github.com/OverHash/a71b32846612ba09d8f79c9d775bfadf), and [prismarine-auth contributors](https://github.com/PrismarineJS/prismarine-auth).
|
||||
Thanks to [wiki contributors](https://minecraft.wiki/w/Microsoft_authentication), [Overhash](https://gist.github.com/OverHash/a71b32846612ba09d8f79c9d775bfadf), and [prismarine-auth contributors](https://github.com/PrismarineJS/prismarine-auth).
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
path::PathBuf,
|
||||
time::{Instant, SystemTime, UNIX_EPOCH},
|
||||
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use thiserror::Error;
|
||||
use tokio::time::sleep;
|
||||
use tracing::{error, trace};
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -75,8 +76,9 @@ pub async fn auth(email: &str, opts: AuthOpts<'_>) -> Result<AuthResult, AuthErr
|
|||
None
|
||||
};
|
||||
|
||||
if cached_account.is_some() && !cached_account.as_ref().unwrap().mca.is_expired() {
|
||||
let account = cached_account.as_ref().unwrap();
|
||||
if let Some(account) = &cached_account
|
||||
&& !account.mca.is_expired()
|
||||
{
|
||||
// the minecraft auth data is cached and not expired, so we can just
|
||||
// use that instead of doing auth all over again :)
|
||||
|
||||
|
@ -129,8 +131,8 @@ pub async fn auth(email: &str, opts: AuthOpts<'_>) -> Result<AuthResult, AuthErr
|
|||
|
||||
let profile: ProfileResponse = get_profile(&client, &res.minecraft_access_token).await?;
|
||||
|
||||
if let Some(cache_file) = opts.cache_file {
|
||||
if let Err(e) = cache::set_account_in_cache(
|
||||
if let Some(cache_file) = opts.cache_file
|
||||
&& let Err(e) = cache::set_account_in_cache(
|
||||
&cache_file,
|
||||
email,
|
||||
CachedAccount {
|
||||
|
@ -142,9 +144,8 @@ pub async fn auth(email: &str, opts: AuthOpts<'_>) -> Result<AuthResult, AuthErr
|
|||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("{}", e);
|
||||
}
|
||||
{
|
||||
error!("{}", e);
|
||||
}
|
||||
|
||||
Ok(AuthResult {
|
||||
|
@ -328,7 +329,7 @@ pub async fn get_ms_link_code(
|
|||
|
||||
Ok(client
|
||||
.post("https://login.live.com/oauth20_connect.srf")
|
||||
.form(&vec![
|
||||
.form(&[
|
||||
("scope", scope),
|
||||
("client_id", client_id),
|
||||
("response_type", "device_code"),
|
||||
|
@ -354,17 +355,17 @@ pub async fn get_ms_auth_token(
|
|||
CLIENT_ID
|
||||
};
|
||||
|
||||
let login_expires_at = Instant::now() + std::time::Duration::from_secs(res.expires_in);
|
||||
let login_expires_at = Instant::now() + Duration::from_secs(res.expires_in);
|
||||
|
||||
while Instant::now() < login_expires_at {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(res.interval)).await;
|
||||
sleep(Duration::from_secs(res.interval)).await;
|
||||
|
||||
trace!("Polling to check if user has logged in...");
|
||||
let res = client
|
||||
.post(format!(
|
||||
"https://login.live.com/oauth20_token.srf?client_id={client_id}"
|
||||
))
|
||||
.form(&vec![
|
||||
.form(&[
|
||||
("client_id", client_id),
|
||||
("device_code", &res.device_code),
|
||||
("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
|
||||
|
@ -375,8 +376,8 @@ pub async fn get_ms_auth_token(
|
|||
.await;
|
||||
if let Ok(access_token_response) = res {
|
||||
trace!("access_token_response: {:?}", access_token_response);
|
||||
let expires_at = SystemTime::now()
|
||||
+ std::time::Duration::from_secs(access_token_response.expires_in);
|
||||
let expires_at =
|
||||
SystemTime::now() + Duration::from_secs(access_token_response.expires_in);
|
||||
return Ok(ExpiringValue {
|
||||
data: access_token_response,
|
||||
expires_at: expires_at
|
||||
|
@ -428,7 +429,7 @@ pub async fn refresh_ms_auth_token(
|
|||
|
||||
let access_token_response_text = client
|
||||
.post("https://login.live.com/oauth20_token.srf")
|
||||
.form(&vec![
|
||||
.form(&[
|
||||
("scope", scope),
|
||||
("client_id", client_id),
|
||||
("grant_type", "refresh_token"),
|
||||
|
@ -441,8 +442,7 @@ pub async fn refresh_ms_auth_token(
|
|||
let access_token_response: AccessTokenResponse =
|
||||
serde_json::from_str(&access_token_response_text)?;
|
||||
|
||||
let expires_at =
|
||||
SystemTime::now() + std::time::Duration::from_secs(access_token_response.expires_in);
|
||||
let expires_at = SystemTime::now() + Duration::from_secs(access_token_response.expires_in);
|
||||
Ok(ExpiringValue {
|
||||
data: access_token_response,
|
||||
expires_at: expires_at
|
||||
|
@ -558,7 +558,7 @@ async fn auth_with_minecraft(
|
|||
.await?;
|
||||
trace!("{:?}", res);
|
||||
|
||||
let expires_at = SystemTime::now() + std::time::Duration::from_secs(res.expires_in);
|
||||
let expires_at = SystemTime::now() + Duration::from_secs(res.expires_in);
|
||||
Ok(ExpiringValue {
|
||||
data: res,
|
||||
// to seconds since epoch
|
||||
|
|
|
@ -1,22 +1,27 @@
|
|||
//! Cache auth information
|
||||
|
||||
use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::{
|
||||
io,
|
||||
path::Path,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::{
|
||||
fs::{self, File},
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
};
|
||||
use tracing::{debug, trace};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CacheError {
|
||||
#[error("Failed to read cache file: {0}")]
|
||||
Read(std::io::Error),
|
||||
Read(io::Error),
|
||||
#[error("Failed to write cache file: {0}")]
|
||||
Write(std::io::Error),
|
||||
Write(io::Error),
|
||||
#[error("Failed to create cache file directory: {0}")]
|
||||
MkDir(std::io::Error),
|
||||
MkDir(io::Error),
|
||||
#[error("Failed to parse cache file: {0}")]
|
||||
Parse(serde_json::Error),
|
||||
}
|
||||
|
@ -94,7 +99,9 @@ async fn set_entire_cache(cache_file: &Path, cache: Vec<CachedAccount>) -> Resul
|
|||
"Making cache file parent directory at {}",
|
||||
cache_file_parent.to_string_lossy()
|
||||
);
|
||||
std::fs::create_dir_all(cache_file_parent).map_err(CacheError::MkDir)?;
|
||||
fs::create_dir_all(cache_file_parent)
|
||||
.await
|
||||
.map_err(CacheError::MkDir)?;
|
||||
}
|
||||
let mut cache_file = File::create(cache_file).await.map_err(CacheError::Write)?;
|
||||
let cache = serde_json::to_string_pretty(&cache).map_err(CacheError::Parse)?;
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
mod utils;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
use std::{collections::HashMap, fmt::Write};
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenTree;
|
||||
|
@ -717,10 +716,10 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
// ```
|
||||
// match state_id {
|
||||
// // this is just an example of how it might look, these state ids are definitely not correct
|
||||
// 0|3|6 => Some(Self::Axis::X),
|
||||
// 1|4|7 => Some(Self::Axis::Y),
|
||||
// 2|5|8 => Some(Self::Axis::Z),
|
||||
// _ => None
|
||||
// 0 | 3 | 6 => Some(Self::Axis::X),
|
||||
// 1 | 4 | 7 => Some(Self::Axis::Y),
|
||||
// 2 | 5 | 8 => Some(Self::Axis::Z),
|
||||
// _ => None,
|
||||
// }
|
||||
// ```
|
||||
let mut property_impls = quote! {};
|
||||
|
|
|
@ -94,6 +94,12 @@ impl TryFrom<u16> for BlockState {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl From<BlockState> for u32 {
|
||||
/// See [`BlockState::id`].
|
||||
fn from(value: BlockState) -> Self {
|
||||
value.id as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl AzaleaRead for BlockState {
|
||||
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
|
@ -104,7 +110,7 @@ impl AzaleaRead for BlockState {
|
|||
}
|
||||
}
|
||||
impl AzaleaWrite for BlockState {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
u32::azalea_write_var(&(self.id as u32), buf)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use std::{any::Any, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
exceptions::CommandSyntaxException,
|
||||
errors::CommandSyntaxError,
|
||||
string_reader::StringReader,
|
||||
suggestion::{Suggestions, SuggestionsBuilder},
|
||||
};
|
||||
|
||||
pub trait ArgumentType {
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException>;
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError>;
|
||||
|
||||
fn list_suggestions(&self, _builder: SuggestionsBuilder) -> Suggestions {
|
||||
Suggestions::default()
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{any::Any, sync::Arc};
|
|||
use super::ArgumentType;
|
||||
use crate::{
|
||||
context::CommandContext,
|
||||
exceptions::CommandSyntaxException,
|
||||
errors::CommandSyntaxError,
|
||||
string_reader::StringReader,
|
||||
suggestion::{Suggestions, SuggestionsBuilder},
|
||||
};
|
||||
|
@ -12,7 +12,7 @@ use crate::{
|
|||
struct Boolean;
|
||||
|
||||
impl ArgumentType for Boolean {
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError> {
|
||||
Ok(Arc::new(reader.read_boolean()?))
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{any::Any, sync::Arc};
|
|||
use super::ArgumentType;
|
||||
use crate::{
|
||||
context::CommandContext,
|
||||
exceptions::{BuiltInExceptions, CommandSyntaxException},
|
||||
errors::{BuiltInError, CommandSyntaxError},
|
||||
string_reader::StringReader,
|
||||
};
|
||||
|
||||
|
@ -14,28 +14,28 @@ struct Double {
|
|||
}
|
||||
|
||||
impl ArgumentType for Double {
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError> {
|
||||
let start = reader.cursor;
|
||||
let result = reader.read_double()?;
|
||||
if let Some(minimum) = self.minimum {
|
||||
if result < minimum {
|
||||
reader.cursor = start;
|
||||
return Err(BuiltInExceptions::DoubleTooSmall {
|
||||
found: result,
|
||||
min: minimum,
|
||||
}
|
||||
.create_with_context(reader));
|
||||
if let Some(minimum) = self.minimum
|
||||
&& result < minimum
|
||||
{
|
||||
reader.cursor = start;
|
||||
return Err(BuiltInError::DoubleTooSmall {
|
||||
found: result,
|
||||
min: minimum,
|
||||
}
|
||||
.create_with_context(reader));
|
||||
}
|
||||
if let Some(maximum) = self.maximum {
|
||||
if result > maximum {
|
||||
reader.cursor = start;
|
||||
return Err(BuiltInExceptions::DoubleTooBig {
|
||||
found: result,
|
||||
max: maximum,
|
||||
}
|
||||
.create_with_context(reader));
|
||||
if let Some(maximum) = self.maximum
|
||||
&& result > maximum
|
||||
{
|
||||
reader.cursor = start;
|
||||
return Err(BuiltInError::DoubleTooBig {
|
||||
found: result,
|
||||
max: maximum,
|
||||
}
|
||||
.create_with_context(reader));
|
||||
}
|
||||
Ok(Arc::new(result))
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{any::Any, sync::Arc};
|
|||
use super::ArgumentType;
|
||||
use crate::{
|
||||
context::CommandContext,
|
||||
exceptions::{BuiltInExceptions, CommandSyntaxException},
|
||||
errors::{BuiltInError, CommandSyntaxError},
|
||||
string_reader::StringReader,
|
||||
};
|
||||
|
||||
|
@ -14,28 +14,28 @@ struct Float {
|
|||
}
|
||||
|
||||
impl ArgumentType for Float {
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError> {
|
||||
let start = reader.cursor;
|
||||
let result = reader.read_float()?;
|
||||
if let Some(minimum) = self.minimum {
|
||||
if result < minimum {
|
||||
reader.cursor = start;
|
||||
return Err(BuiltInExceptions::FloatTooSmall {
|
||||
found: result,
|
||||
min: minimum,
|
||||
}
|
||||
.create_with_context(reader));
|
||||
if let Some(minimum) = self.minimum
|
||||
&& result < minimum
|
||||
{
|
||||
reader.cursor = start;
|
||||
return Err(BuiltInError::FloatTooSmall {
|
||||
found: result,
|
||||
min: minimum,
|
||||
}
|
||||
.create_with_context(reader));
|
||||
}
|
||||
if let Some(maximum) = self.maximum {
|
||||
if result > maximum {
|
||||
reader.cursor = start;
|
||||
return Err(BuiltInExceptions::FloatTooBig {
|
||||
found: result,
|
||||
max: maximum,
|
||||
}
|
||||
.create_with_context(reader));
|
||||
if let Some(maximum) = self.maximum
|
||||
&& result > maximum
|
||||
{
|
||||
reader.cursor = start;
|
||||
return Err(BuiltInError::FloatTooBig {
|
||||
found: result,
|
||||
max: maximum,
|
||||
}
|
||||
.create_with_context(reader));
|
||||
}
|
||||
Ok(Arc::new(result))
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{any::Any, sync::Arc};
|
|||
use super::ArgumentType;
|
||||
use crate::{
|
||||
context::CommandContext,
|
||||
exceptions::{BuiltInExceptions, CommandSyntaxException},
|
||||
errors::{BuiltInError, CommandSyntaxError},
|
||||
string_reader::StringReader,
|
||||
};
|
||||
|
||||
|
@ -14,28 +14,28 @@ struct Integer {
|
|||
}
|
||||
|
||||
impl ArgumentType for Integer {
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError> {
|
||||
let start = reader.cursor;
|
||||
let result = reader.read_int()?;
|
||||
if let Some(minimum) = self.minimum {
|
||||
if result < minimum {
|
||||
reader.cursor = start;
|
||||
return Err(BuiltInExceptions::IntegerTooSmall {
|
||||
found: result,
|
||||
min: minimum,
|
||||
}
|
||||
.create_with_context(reader));
|
||||
if let Some(minimum) = self.minimum
|
||||
&& result < minimum
|
||||
{
|
||||
reader.cursor = start;
|
||||
return Err(BuiltInError::IntegerTooSmall {
|
||||
found: result,
|
||||
min: minimum,
|
||||
}
|
||||
.create_with_context(reader));
|
||||
}
|
||||
if let Some(maximum) = self.maximum {
|
||||
if result > maximum {
|
||||
reader.cursor = start;
|
||||
return Err(BuiltInExceptions::IntegerTooBig {
|
||||
found: result,
|
||||
max: maximum,
|
||||
}
|
||||
.create_with_context(reader));
|
||||
if let Some(maximum) = self.maximum
|
||||
&& result > maximum
|
||||
{
|
||||
reader.cursor = start;
|
||||
return Err(BuiltInError::IntegerTooBig {
|
||||
found: result,
|
||||
max: maximum,
|
||||
}
|
||||
.create_with_context(reader));
|
||||
}
|
||||
Ok(Arc::new(result))
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{any::Any, sync::Arc};
|
|||
use super::ArgumentType;
|
||||
use crate::{
|
||||
context::CommandContext,
|
||||
exceptions::{BuiltInExceptions, CommandSyntaxException},
|
||||
errors::{BuiltInError, CommandSyntaxError},
|
||||
string_reader::StringReader,
|
||||
};
|
||||
|
||||
|
@ -14,28 +14,28 @@ struct Long {
|
|||
}
|
||||
|
||||
impl ArgumentType for Long {
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError> {
|
||||
let start = reader.cursor;
|
||||
let result = reader.read_long()?;
|
||||
if let Some(minimum) = self.minimum {
|
||||
if result < minimum {
|
||||
reader.cursor = start;
|
||||
return Err(BuiltInExceptions::LongTooSmall {
|
||||
found: result,
|
||||
min: minimum,
|
||||
}
|
||||
.create_with_context(reader));
|
||||
if let Some(minimum) = self.minimum
|
||||
&& result < minimum
|
||||
{
|
||||
reader.cursor = start;
|
||||
return Err(BuiltInError::LongTooSmall {
|
||||
found: result,
|
||||
min: minimum,
|
||||
}
|
||||
.create_with_context(reader));
|
||||
}
|
||||
if let Some(maximum) = self.maximum {
|
||||
if result > maximum {
|
||||
reader.cursor = start;
|
||||
return Err(BuiltInExceptions::LongTooBig {
|
||||
found: result,
|
||||
max: maximum,
|
||||
}
|
||||
.create_with_context(reader));
|
||||
if let Some(maximum) = self.maximum
|
||||
&& result > maximum
|
||||
{
|
||||
reader.cursor = start;
|
||||
return Err(BuiltInError::LongTooBig {
|
||||
found: result,
|
||||
max: maximum,
|
||||
}
|
||||
.create_with_context(reader));
|
||||
}
|
||||
Ok(Arc::new(result))
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
use std::{any::Any, sync::Arc};
|
||||
|
||||
use super::ArgumentType;
|
||||
use crate::{
|
||||
context::CommandContext, exceptions::CommandSyntaxException, string_reader::StringReader,
|
||||
};
|
||||
use crate::{context::CommandContext, errors::CommandSyntaxError, string_reader::StringReader};
|
||||
|
||||
pub enum StringArgument {
|
||||
/// Match up until the next space.
|
||||
|
@ -16,7 +14,7 @@ pub enum StringArgument {
|
|||
}
|
||||
|
||||
impl ArgumentType for StringArgument {
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError> {
|
||||
let result = match self {
|
||||
StringArgument::SingleWord => reader.read_unquoted_string().to_string(),
|
||||
StringArgument::QuotablePhrase => reader.read_string()?,
|
||||
|
|
|
@ -1,18 +1,32 @@
|
|||
use std::{fmt::Debug, sync::Arc};
|
||||
use std::{
|
||||
fmt::{self, Debug},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use super::{literal_argument_builder::Literal, required_argument_builder::Argument};
|
||||
use crate::{
|
||||
context::CommandContext,
|
||||
errors::CommandSyntaxError,
|
||||
modifier::RedirectModifier,
|
||||
tree::{Command, CommandNode},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ArgumentBuilderType {
|
||||
#[derive(Debug)]
|
||||
pub enum ArgumentBuilderType<S> {
|
||||
Literal(Literal),
|
||||
Argument(Argument),
|
||||
Argument(Argument<S>),
|
||||
}
|
||||
impl<S> Clone for ArgumentBuilderType<S> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
ArgumentBuilderType::Literal(literal) => ArgumentBuilderType::Literal(literal.clone()),
|
||||
ArgumentBuilderType::Argument(argument) => {
|
||||
ArgumentBuilderType::Argument(argument.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A node that hasn't yet been built.
|
||||
|
@ -30,7 +44,7 @@ pub struct ArgumentBuilder<S> {
|
|||
|
||||
/// A node that isn't yet built.
|
||||
impl<S> ArgumentBuilder<S> {
|
||||
pub fn new(value: ArgumentBuilderType) -> Self {
|
||||
pub fn new(value: ArgumentBuilderType<S>) -> Self {
|
||||
Self {
|
||||
arguments: CommandNode {
|
||||
value,
|
||||
|
@ -49,9 +63,7 @@ impl<S> ArgumentBuilder<S> {
|
|||
/// ```
|
||||
/// # use azalea_brigadier::prelude::*;
|
||||
/// # let mut subject = CommandDispatcher::<()>::new();
|
||||
/// literal("foo").then(
|
||||
/// literal("bar").executes(|ctx: &CommandContext<()>| 42)
|
||||
/// )
|
||||
/// literal("foo").then(literal("bar").executes(|ctx: &CommandContext<()>| 42))
|
||||
/// # ;
|
||||
/// ```
|
||||
pub fn then(self, argument: ArgumentBuilder<S>) -> Self {
|
||||
|
@ -79,6 +91,16 @@ impl<S> ArgumentBuilder<S> {
|
|||
pub fn executes<F>(mut self, f: F) -> Self
|
||||
where
|
||||
F: Fn(&CommandContext<S>) -> i32 + Send + Sync + 'static,
|
||||
{
|
||||
self.command = Some(Arc::new(move |ctx: &CommandContext<S>| Ok(f(ctx))));
|
||||
self
|
||||
}
|
||||
|
||||
/// Same as [`Self::executes`] but returns a `Result<i32,
|
||||
/// CommandSyntaxError>`.
|
||||
pub fn executes_result<F>(mut self, f: F) -> Self
|
||||
where
|
||||
F: Fn(&CommandContext<S>) -> Result<i32, CommandSyntaxError> + Send + Sync + 'static,
|
||||
{
|
||||
self.command = Some(Arc::new(f));
|
||||
self
|
||||
|
@ -163,7 +185,7 @@ impl<S> ArgumentBuilder<S> {
|
|||
}
|
||||
|
||||
impl<S> Debug for ArgumentBuilder<S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ArgumentBuilder")
|
||||
.field("arguments", &self.arguments)
|
||||
// .field("command", &self.command)
|
||||
|
|
|
@ -12,7 +12,7 @@ impl Literal {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Literal> for ArgumentBuilderType {
|
||||
impl<S> From<Literal> for ArgumentBuilderType<S> {
|
||||
fn from(literal: Literal) -> Self {
|
||||
Self::Literal(literal)
|
||||
}
|
||||
|
|
|
@ -1,37 +1,52 @@
|
|||
use std::{any::Any, fmt::Debug, sync::Arc};
|
||||
use std::{
|
||||
any::Any,
|
||||
fmt::{self, Debug},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType};
|
||||
use crate::{
|
||||
arguments::ArgumentType,
|
||||
exceptions::CommandSyntaxException,
|
||||
context::CommandContext,
|
||||
errors::CommandSyntaxError,
|
||||
string_reader::StringReader,
|
||||
suggestion::{Suggestions, SuggestionsBuilder},
|
||||
suggestion::{SuggestionProvider, Suggestions, SuggestionsBuilder},
|
||||
};
|
||||
|
||||
/// An argument node type. The `T` type parameter is the type of the argument,
|
||||
/// which can be anything.
|
||||
#[derive(Clone)]
|
||||
pub struct Argument {
|
||||
pub struct Argument<S> {
|
||||
pub name: String,
|
||||
parser: Arc<dyn ArgumentType + Send + Sync>,
|
||||
custom_suggestions: Option<Arc<dyn SuggestionProvider<S> + Send + Sync>>,
|
||||
}
|
||||
impl Argument {
|
||||
pub fn new(name: &str, parser: Arc<dyn ArgumentType + Send + Sync>) -> Self {
|
||||
impl<S> Argument<S> {
|
||||
pub fn new(
|
||||
name: &str,
|
||||
parser: Arc<dyn ArgumentType + Send + Sync>,
|
||||
custom_suggestions: Option<Arc<dyn SuggestionProvider<S> + Send + Sync>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
parser,
|
||||
custom_suggestions,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||
pub fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError> {
|
||||
self.parser.parse(reader)
|
||||
}
|
||||
|
||||
pub fn list_suggestions(&self, builder: SuggestionsBuilder) -> Suggestions {
|
||||
// TODO: custom suggestions
|
||||
// https://github.com/Mojang/brigadier/blob/master/src/main/java/com/mojang/brigadier/tree/ArgumentCommandNode.java#L71
|
||||
|
||||
self.parser.list_suggestions(builder)
|
||||
pub fn list_suggestions(
|
||||
&self,
|
||||
context: CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
if let Some(s) = &self.custom_suggestions {
|
||||
s.get_suggestions(context, builder)
|
||||
} else {
|
||||
self.parser.list_suggestions(builder)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn examples(&self) -> Vec<String> {
|
||||
|
@ -39,14 +54,14 @@ impl Argument {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Argument> for ArgumentBuilderType {
|
||||
fn from(argument: Argument) -> Self {
|
||||
impl<S> From<Argument<S>> for ArgumentBuilderType<S> {
|
||||
fn from(argument: Argument<S>) -> Self {
|
||||
Self::Argument(argument)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Argument {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl<S> Debug for Argument<S> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Argument")
|
||||
.field("name", &self.name)
|
||||
// .field("parser", &self.parser)
|
||||
|
@ -59,5 +74,15 @@ pub fn argument<S>(
|
|||
name: &str,
|
||||
parser: impl ArgumentType + Send + Sync + 'static,
|
||||
) -> ArgumentBuilder<S> {
|
||||
ArgumentBuilder::new(Argument::new(name, Arc::new(parser)).into())
|
||||
ArgumentBuilder::new(Argument::new(name, Arc::new(parser), None).into())
|
||||
}
|
||||
|
||||
impl<S> Clone for Argument<S> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
name: self.name.clone(),
|
||||
parser: self.parser.clone(),
|
||||
custom_suggestions: self.custom_suggestions.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{HashMap, HashSet},
|
||||
mem, ptr,
|
||||
ptr,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
|
@ -10,9 +10,10 @@ use parking_lot::RwLock;
|
|||
|
||||
use crate::{
|
||||
builder::argument_builder::ArgumentBuilder,
|
||||
context::{CommandContext, CommandContextBuilder},
|
||||
exceptions::{BuiltInExceptions, CommandSyntaxException},
|
||||
context::{CommandContextBuilder, ContextChain},
|
||||
errors::{BuiltInError, CommandSyntaxError},
|
||||
parse_results::ParseResults,
|
||||
result_consumer::{DefaultResultConsumer, ResultConsumer},
|
||||
string_reader::StringReader,
|
||||
suggestion::{Suggestions, SuggestionsBuilder},
|
||||
tree::CommandNode,
|
||||
|
@ -30,12 +31,14 @@ where
|
|||
Self: Sync + Send,
|
||||
{
|
||||
pub root: Arc<RwLock<CommandNode<S>>>,
|
||||
consumer: Box<dyn ResultConsumer<S> + Send + Sync>,
|
||||
}
|
||||
|
||||
impl<S> CommandDispatcher<S> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
root: Arc::new(RwLock::new(CommandNode::default())),
|
||||
consumer: Box::new(DefaultResultConsumer),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,10 +67,10 @@ impl<S> CommandDispatcher<S> {
|
|||
node: &Arc<RwLock<CommandNode<S>>>,
|
||||
original_reader: &StringReader,
|
||||
context_so_far: CommandContextBuilder<'a, S>,
|
||||
) -> Result<ParseResults<'a, S>, CommandSyntaxException> {
|
||||
) -> Result<ParseResults<'a, S>, CommandSyntaxError> {
|
||||
let source = context_so_far.source.clone();
|
||||
#[allow(clippy::mutable_key_type)] // this is fine because we don't mutate the key
|
||||
let mut errors = HashMap::<Rc<CommandNode<S>>, CommandSyntaxException>::new();
|
||||
let mut errors = HashMap::<Rc<CommandNode<S>>, CommandSyntaxError>::new();
|
||||
let mut potentials: Vec<ParseResults<S>> = vec![];
|
||||
let cursor = original_reader.cursor();
|
||||
|
||||
|
@ -83,7 +86,7 @@ impl<S> CommandDispatcher<S> {
|
|||
if let Err(ex) = parse_with_context_result {
|
||||
errors.insert(
|
||||
Rc::new((*child.read()).clone()),
|
||||
BuiltInExceptions::DispatcherParseException {
|
||||
BuiltInError::DispatcherParseException {
|
||||
message: ex.message(),
|
||||
}
|
||||
.create_with_context(&reader),
|
||||
|
@ -94,8 +97,7 @@ impl<S> CommandDispatcher<S> {
|
|||
if reader.can_read() && reader.peek() != ' ' {
|
||||
errors.insert(
|
||||
Rc::new((*child.read()).clone()),
|
||||
BuiltInExceptions::DispatcherExpectedArgumentSeparator
|
||||
.create_with_context(&reader),
|
||||
BuiltInError::DispatcherExpectedArgumentSeparator.create_with_context(&reader),
|
||||
);
|
||||
reader.cursor = cursor;
|
||||
continue;
|
||||
|
@ -179,11 +181,11 @@ impl<S> CommandDispatcher<S> {
|
|||
&self,
|
||||
input: impl Into<StringReader>,
|
||||
source: S,
|
||||
) -> Result<i32, CommandSyntaxException> {
|
||||
) -> Result<i32, CommandSyntaxError> {
|
||||
let input = input.into();
|
||||
|
||||
let parse = self.parse(input, source);
|
||||
Self::execute_parsed(parse)
|
||||
self.execute_parsed(parse)
|
||||
}
|
||||
|
||||
pub fn add_paths(
|
||||
|
@ -235,91 +237,26 @@ impl<S> CommandDispatcher<S> {
|
|||
}
|
||||
|
||||
/// Executes a given pre-parsed command.
|
||||
pub fn execute_parsed(parse: ParseResults<S>) -> Result<i32, CommandSyntaxException> {
|
||||
pub fn execute_parsed(&self, parse: ParseResults<S>) -> Result<i32, CommandSyntaxError> {
|
||||
if parse.reader.can_read() {
|
||||
if parse.exceptions.len() == 1 {
|
||||
return Err(parse.exceptions.values().next().unwrap().clone());
|
||||
}
|
||||
if parse.context.range.is_empty() {
|
||||
return Err(
|
||||
BuiltInExceptions::DispatcherUnknownCommand.create_with_context(&parse.reader)
|
||||
);
|
||||
}
|
||||
return Err(
|
||||
BuiltInExceptions::DispatcherUnknownArgument.create_with_context(&parse.reader)
|
||||
);
|
||||
return Err(if parse.exceptions.len() == 1 {
|
||||
parse.exceptions.values().next().unwrap().clone()
|
||||
} else if parse.context.range.is_empty() {
|
||||
BuiltInError::DispatcherUnknownCommand.create_with_context(&parse.reader)
|
||||
} else {
|
||||
BuiltInError::DispatcherUnknownArgument.create_with_context(&parse.reader)
|
||||
});
|
||||
}
|
||||
let mut result = 0i32;
|
||||
let mut successful_forks = 0;
|
||||
let mut forked = false;
|
||||
let mut found_command = false;
|
||||
|
||||
let command = parse.reader.string();
|
||||
let original = parse.context.build(command);
|
||||
let mut contexts = vec![original];
|
||||
let mut next: Vec<CommandContext<S>> = vec![];
|
||||
let original = Rc::new(parse.context.build(command));
|
||||
let flat_context = ContextChain::try_flatten(original.clone());
|
||||
let Some(flat_context) = flat_context else {
|
||||
self.consumer.on_command_complete(original, false, 0);
|
||||
return Err(BuiltInError::DispatcherUnknownCommand.create_with_context(&parse.reader));
|
||||
};
|
||||
|
||||
while !contexts.is_empty() {
|
||||
for context in &contexts {
|
||||
let child = &context.child;
|
||||
if let Some(child) = child {
|
||||
forked |= child.forks;
|
||||
if child.has_nodes() {
|
||||
found_command = true;
|
||||
let modifier = &context.modifier;
|
||||
if let Some(modifier) = modifier {
|
||||
let results = modifier(context);
|
||||
match results {
|
||||
Ok(results) => {
|
||||
if !results.is_empty() {
|
||||
next.extend(
|
||||
results.iter().map(|s| child.copy_for(s.clone())),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// TODO
|
||||
// self.consumer.on_command_complete(context, false, 0);
|
||||
if !forked {
|
||||
return Err(results.err().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
next.push(child.copy_for(context.source.clone()));
|
||||
}
|
||||
}
|
||||
} else if let Some(context_command) = &context.command {
|
||||
found_command = true;
|
||||
|
||||
let value = context_command(context);
|
||||
result += value;
|
||||
// consumer.on_command_complete(context, true, value);
|
||||
successful_forks += 1;
|
||||
|
||||
// TODO: allow context_command to error and handle
|
||||
// those errors
|
||||
}
|
||||
}
|
||||
|
||||
// move next into contexts and clear next
|
||||
mem::swap(&mut contexts, &mut next);
|
||||
next.clear();
|
||||
}
|
||||
|
||||
if !found_command {
|
||||
// consumer.on_command_complete(original, false, 0);
|
||||
return Err(
|
||||
BuiltInExceptions::DispatcherUnknownCommand.create_with_context(&parse.reader)
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: this is not how vanilla does it but it works
|
||||
Ok(if successful_forks >= 2 {
|
||||
successful_forks
|
||||
} else {
|
||||
result
|
||||
})
|
||||
// Ok(if forked { successful_forks } else { result })
|
||||
flat_context.execute_all(original.source.clone(), self.consumer.as_ref())
|
||||
}
|
||||
|
||||
pub fn get_all_usage(
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
use std::{any::Any, collections::HashMap, fmt::Debug, rc::Rc, sync::Arc};
|
||||
use std::{
|
||||
any::Any,
|
||||
collections::HashMap,
|
||||
fmt::{self, Debug},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use parking_lot::RwLock;
|
||||
|
||||
|
@ -11,15 +17,15 @@ use crate::{
|
|||
/// A built `CommandContextBuilder`.
|
||||
pub struct CommandContext<S> {
|
||||
pub source: Arc<S>,
|
||||
pub input: String,
|
||||
pub arguments: HashMap<String, ParsedArgument>,
|
||||
pub command: Command<S>,
|
||||
pub root_node: Arc<RwLock<CommandNode<S>>>,
|
||||
pub nodes: Vec<ParsedCommandNode<S>>,
|
||||
pub range: StringRange,
|
||||
pub child: Option<Rc<CommandContext<S>>>,
|
||||
pub modifier: Option<Arc<RedirectModifier<S>>>,
|
||||
pub forks: bool,
|
||||
pub(super) input: String,
|
||||
pub(super) arguments: HashMap<String, ParsedArgument>,
|
||||
pub(super) command: Command<S>,
|
||||
pub(super) root_node: Arc<RwLock<CommandNode<S>>>,
|
||||
pub(super) nodes: Vec<ParsedCommandNode<S>>,
|
||||
pub(super) range: StringRange,
|
||||
pub(super) child: Option<Rc<CommandContext<S>>>,
|
||||
pub(super) modifier: Option<Arc<RedirectModifier<S>>>,
|
||||
pub(super) forks: bool,
|
||||
}
|
||||
|
||||
impl<S> Clone for CommandContext<S> {
|
||||
|
@ -40,7 +46,7 @@ impl<S> Clone for CommandContext<S> {
|
|||
}
|
||||
|
||||
impl<S> Debug for CommandContext<S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("CommandContext")
|
||||
// .field("source", &self.source)
|
||||
.field("input", &self.input)
|
||||
|
@ -59,8 +65,10 @@ impl<S> Debug for CommandContext<S> {
|
|||
impl<S> CommandContext<S> {
|
||||
pub fn copy_for(&self, source: Arc<S>) -> Self {
|
||||
if Arc::ptr_eq(&source, &self.source) {
|
||||
// fast path
|
||||
return self.clone();
|
||||
}
|
||||
|
||||
CommandContext {
|
||||
source,
|
||||
input: self.input.clone(),
|
||||
|
@ -75,12 +83,52 @@ impl<S> CommandContext<S> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn child(&self) -> Option<&CommandContext<S>> {
|
||||
self.child.as_ref().map(|c| c.as_ref())
|
||||
}
|
||||
|
||||
pub fn last_child(&self) -> &CommandContext<S> {
|
||||
let mut result = self;
|
||||
while let Some(child) = result.child() {
|
||||
result = child;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn command(&self) -> &Command<S> {
|
||||
&self.command
|
||||
}
|
||||
|
||||
pub fn argument(&self, name: &str) -> Option<&dyn Any> {
|
||||
let argument = self.arguments.get(name);
|
||||
argument.map(|a| a.result.as_ref())
|
||||
}
|
||||
|
||||
pub fn redirect_modifier(&self) -> Option<&RedirectModifier<S>> {
|
||||
self.modifier.as_ref().map(|m| m.as_ref())
|
||||
}
|
||||
|
||||
pub fn range(&self) -> &StringRange {
|
||||
&self.range
|
||||
}
|
||||
|
||||
pub fn input(&self) -> &str {
|
||||
&self.input
|
||||
}
|
||||
|
||||
pub fn root_node(&self) -> &Arc<RwLock<CommandNode<S>>> {
|
||||
&self.root_node
|
||||
}
|
||||
|
||||
pub fn nodes(&self) -> &[ParsedCommandNode<S>] {
|
||||
&self.nodes
|
||||
}
|
||||
|
||||
pub fn has_nodes(&self) -> bool {
|
||||
!self.nodes.is_empty()
|
||||
}
|
||||
|
||||
pub fn argument(&self, name: &str) -> Option<Arc<dyn Any>> {
|
||||
let argument = self.arguments.get(name);
|
||||
argument.map(|a| a.result.clone())
|
||||
pub fn is_forked(&self) -> bool {
|
||||
self.forks
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
use std::{collections::HashMap, fmt::Debug, rc::Rc, sync::Arc};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{self, Debug},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use parking_lot::RwLock;
|
||||
|
||||
|
@ -140,7 +145,7 @@ impl<'a, S> CommandContextBuilder<'a, S> {
|
|||
}
|
||||
|
||||
impl<S> Debug for CommandContextBuilder<'_, S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("CommandContextBuilder")
|
||||
// .field("arguments", &self.arguments)
|
||||
.field("root", &self.root)
|
||||
|
|
167
azalea-brigadier/src/context/context_chain.rs
Normal file
167
azalea-brigadier/src/context/context_chain.rs
Normal file
|
@ -0,0 +1,167 @@
|
|||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
use super::CommandContext;
|
||||
use crate::{errors::CommandSyntaxError, result_consumer::ResultConsumer};
|
||||
|
||||
pub struct ContextChain<S> {
|
||||
modifiers: Vec<Rc<CommandContext<S>>>,
|
||||
executable: Rc<CommandContext<S>>,
|
||||
next_stage_cache: Option<Rc<ContextChain<S>>>,
|
||||
}
|
||||
|
||||
impl<S> ContextChain<S> {
|
||||
pub fn new(modifiers: Vec<Rc<CommandContext<S>>>, executable: Rc<CommandContext<S>>) -> Self {
|
||||
if executable.command.is_none() {
|
||||
panic!("Last command in chain must be executable");
|
||||
}
|
||||
Self {
|
||||
modifiers,
|
||||
executable,
|
||||
next_stage_cache: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_flatten(root_context: Rc<CommandContext<S>>) -> Option<Self> {
|
||||
let mut modifiers = Vec::new();
|
||||
let mut current = root_context;
|
||||
loop {
|
||||
let child = current.child.clone();
|
||||
let Some(child) = child else {
|
||||
// Last entry must be executable command
|
||||
current.command.as_ref()?;
|
||||
|
||||
return Some(ContextChain::new(modifiers, current));
|
||||
};
|
||||
|
||||
modifiers.push(current);
|
||||
current = child;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_modifier(
|
||||
modifier: Rc<CommandContext<S>>,
|
||||
source: Arc<S>,
|
||||
result_consumer: &dyn ResultConsumer<S>,
|
||||
forked_mode: bool,
|
||||
) -> Result<Vec<Arc<S>>, CommandSyntaxError> {
|
||||
let source_modifier = modifier.redirect_modifier();
|
||||
let Some(source_modifier) = source_modifier else {
|
||||
return Ok(vec![source]);
|
||||
};
|
||||
|
||||
let context_to_use = Rc::new(modifier.copy_for(source));
|
||||
let err = match (source_modifier)(&context_to_use) {
|
||||
Ok(res) => return Ok(res),
|
||||
Err(e) => e,
|
||||
};
|
||||
|
||||
result_consumer.on_command_complete(context_to_use, false, 0);
|
||||
if forked_mode {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
Err(err)
|
||||
}
|
||||
|
||||
pub fn run_executable(
|
||||
&self,
|
||||
executable: Rc<CommandContext<S>>,
|
||||
source: Arc<S>,
|
||||
result_consumer: &dyn ResultConsumer<S>,
|
||||
forked_mode: bool,
|
||||
) -> Result<i32, CommandSyntaxError> {
|
||||
let context_to_use = Rc::new(executable.copy_for(source));
|
||||
let Some(command) = &executable.command else {
|
||||
unimplemented!();
|
||||
};
|
||||
|
||||
let err = match (command)(&context_to_use) {
|
||||
Ok(result) => {
|
||||
result_consumer.on_command_complete(context_to_use, true, result);
|
||||
return if forked_mode { Ok(1) } else { Ok(result) };
|
||||
}
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
result_consumer.on_command_complete(context_to_use, false, 0);
|
||||
if forked_mode { Ok(0) } else { Err(err) }
|
||||
}
|
||||
|
||||
pub fn execute_all(
|
||||
&self,
|
||||
source: Arc<S>,
|
||||
result_consumer: &(dyn ResultConsumer<S>),
|
||||
) -> Result<i32, CommandSyntaxError> {
|
||||
if self.modifiers.is_empty() {
|
||||
return self.run_executable(self.executable.clone(), source, result_consumer, false);
|
||||
}
|
||||
|
||||
let mut forked_mode = false;
|
||||
let mut current_sources = vec![source];
|
||||
|
||||
for modifier in &self.modifiers {
|
||||
forked_mode |= modifier.is_forked();
|
||||
|
||||
let mut next_sources = Vec::new();
|
||||
for source_to_run in current_sources {
|
||||
next_sources.extend(Self::run_modifier(
|
||||
modifier.clone(),
|
||||
source_to_run.clone(),
|
||||
result_consumer,
|
||||
forked_mode,
|
||||
)?);
|
||||
}
|
||||
if next_sources.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
current_sources = next_sources;
|
||||
}
|
||||
|
||||
let mut result = 0;
|
||||
for execution_source in current_sources {
|
||||
result += self.run_executable(
|
||||
self.executable.clone(),
|
||||
execution_source,
|
||||
result_consumer,
|
||||
forked_mode,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn stage(&self) -> Stage {
|
||||
if self.modifiers.is_empty() {
|
||||
Stage::Execute
|
||||
} else {
|
||||
Stage::Modify
|
||||
}
|
||||
}
|
||||
|
||||
pub fn top_context(&self) -> Rc<CommandContext<S>> {
|
||||
self.modifiers
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| self.executable.clone())
|
||||
}
|
||||
|
||||
pub fn next_stage(&mut self) -> Option<Rc<ContextChain<S>>> {
|
||||
let modifier_count = self.modifiers.len();
|
||||
if modifier_count == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.next_stage_cache.is_none() {
|
||||
self.next_stage_cache = Some(Rc::new(ContextChain::new(
|
||||
self.modifiers[1..].to_vec(),
|
||||
self.executable.clone(),
|
||||
)));
|
||||
}
|
||||
|
||||
self.next_stage_cache.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Stage {
|
||||
Modify,
|
||||
Execute,
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
mod command_context;
|
||||
mod command_context_builder;
|
||||
mod context_chain;
|
||||
mod parsed_argument;
|
||||
mod parsed_command_node;
|
||||
mod string_range;
|
||||
|
@ -7,6 +8,7 @@ pub mod suggestion_context;
|
|||
|
||||
pub use command_context::CommandContext;
|
||||
pub use command_context_builder::CommandContextBuilder;
|
||||
pub use context_chain::ContextChain;
|
||||
pub use parsed_argument::ParsedArgument;
|
||||
pub use parsed_command_node::ParsedCommandNode;
|
||||
pub use string_range::StringRange;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use std::fmt;
|
||||
|
||||
use super::command_syntax_exception::CommandSyntaxException;
|
||||
use super::command_syntax_error::CommandSyntaxError;
|
||||
use crate::string_reader::StringReader;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum BuiltInExceptions {
|
||||
pub enum BuiltInError {
|
||||
DoubleTooSmall { found: f64, min: f64 },
|
||||
DoubleTooBig { found: f64, max: f64 },
|
||||
|
||||
|
@ -40,114 +40,114 @@ pub enum BuiltInExceptions {
|
|||
DispatcherParseException { message: String },
|
||||
}
|
||||
|
||||
impl fmt::Debug for BuiltInExceptions {
|
||||
impl fmt::Debug for BuiltInError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
BuiltInExceptions::DoubleTooSmall { found, min } => {
|
||||
BuiltInError::DoubleTooSmall { found, min } => {
|
||||
write!(f, "Double must not be less than {min}, found {found}")
|
||||
}
|
||||
BuiltInExceptions::DoubleTooBig { found, max } => {
|
||||
BuiltInError::DoubleTooBig { found, max } => {
|
||||
write!(f, "Double must not be more than {max}, found {found}")
|
||||
}
|
||||
|
||||
BuiltInExceptions::FloatTooSmall { found, min } => {
|
||||
BuiltInError::FloatTooSmall { found, min } => {
|
||||
write!(f, "Float must not be less than {min}, found {found}")
|
||||
}
|
||||
BuiltInExceptions::FloatTooBig { found, max } => {
|
||||
BuiltInError::FloatTooBig { found, max } => {
|
||||
write!(f, "Float must not be more than {max}, found {found}")
|
||||
}
|
||||
|
||||
BuiltInExceptions::IntegerTooSmall { found, min } => {
|
||||
BuiltInError::IntegerTooSmall { found, min } => {
|
||||
write!(f, "Integer must not be less than {min}, found {found}")
|
||||
}
|
||||
BuiltInExceptions::IntegerTooBig { found, max } => {
|
||||
BuiltInError::IntegerTooBig { found, max } => {
|
||||
write!(f, "Integer must not be more than {max}, found {found}")
|
||||
}
|
||||
|
||||
BuiltInExceptions::LongTooSmall { found, min } => {
|
||||
BuiltInError::LongTooSmall { found, min } => {
|
||||
write!(f, "Long must not be less than {min}, found {found}")
|
||||
}
|
||||
BuiltInExceptions::LongTooBig { found, max } => {
|
||||
BuiltInError::LongTooBig { found, max } => {
|
||||
write!(f, "Long must not be more than {max}, found {found}")
|
||||
}
|
||||
|
||||
BuiltInExceptions::LiteralIncorrect { expected } => {
|
||||
BuiltInError::LiteralIncorrect { expected } => {
|
||||
write!(f, "Expected literal {expected}")
|
||||
}
|
||||
|
||||
BuiltInExceptions::ReaderExpectedStartOfQuote => {
|
||||
BuiltInError::ReaderExpectedStartOfQuote => {
|
||||
write!(f, "Expected quote to start a string")
|
||||
}
|
||||
BuiltInExceptions::ReaderExpectedEndOfQuote => {
|
||||
BuiltInError::ReaderExpectedEndOfQuote => {
|
||||
write!(f, "Unclosed quoted string")
|
||||
}
|
||||
BuiltInExceptions::ReaderInvalidEscape { character } => {
|
||||
BuiltInError::ReaderInvalidEscape { character } => {
|
||||
write!(f, "Invalid escape sequence '{character}' in quoted string")
|
||||
}
|
||||
BuiltInExceptions::ReaderInvalidBool { value } => {
|
||||
BuiltInError::ReaderInvalidBool { value } => {
|
||||
write!(
|
||||
f,
|
||||
"Invalid bool, expected true or false but found '{value}'"
|
||||
)
|
||||
}
|
||||
BuiltInExceptions::ReaderInvalidInt { value } => {
|
||||
BuiltInError::ReaderInvalidInt { value } => {
|
||||
write!(f, "Invalid Integer '{value}'")
|
||||
}
|
||||
BuiltInExceptions::ReaderExpectedInt => {
|
||||
BuiltInError::ReaderExpectedInt => {
|
||||
write!(f, "Expected Integer")
|
||||
}
|
||||
BuiltInExceptions::ReaderInvalidLong { value } => {
|
||||
BuiltInError::ReaderInvalidLong { value } => {
|
||||
write!(f, "Invalid long '{value}'")
|
||||
}
|
||||
BuiltInExceptions::ReaderExpectedLong => {
|
||||
BuiltInError::ReaderExpectedLong => {
|
||||
write!(f, "Expected long")
|
||||
}
|
||||
BuiltInExceptions::ReaderInvalidDouble { value } => {
|
||||
BuiltInError::ReaderInvalidDouble { value } => {
|
||||
write!(f, "Invalid double '{value}'")
|
||||
}
|
||||
BuiltInExceptions::ReaderExpectedDouble => {
|
||||
BuiltInError::ReaderExpectedDouble => {
|
||||
write!(f, "Expected double")
|
||||
}
|
||||
BuiltInExceptions::ReaderInvalidFloat { value } => {
|
||||
BuiltInError::ReaderInvalidFloat { value } => {
|
||||
write!(f, "Invalid Float '{value}'")
|
||||
}
|
||||
BuiltInExceptions::ReaderExpectedFloat => {
|
||||
BuiltInError::ReaderExpectedFloat => {
|
||||
write!(f, "Expected Float")
|
||||
}
|
||||
BuiltInExceptions::ReaderExpectedBool => {
|
||||
BuiltInError::ReaderExpectedBool => {
|
||||
write!(f, "Expected bool")
|
||||
}
|
||||
BuiltInExceptions::ReaderExpectedSymbol { symbol } => {
|
||||
BuiltInError::ReaderExpectedSymbol { symbol } => {
|
||||
write!(f, "Expected '{symbol}'")
|
||||
}
|
||||
|
||||
BuiltInExceptions::DispatcherUnknownCommand => {
|
||||
BuiltInError::DispatcherUnknownCommand => {
|
||||
write!(f, "Unknown command")
|
||||
}
|
||||
BuiltInExceptions::DispatcherUnknownArgument => {
|
||||
BuiltInError::DispatcherUnknownArgument => {
|
||||
write!(f, "Incorrect argument for command")
|
||||
}
|
||||
BuiltInExceptions::DispatcherExpectedArgumentSeparator => {
|
||||
BuiltInError::DispatcherExpectedArgumentSeparator => {
|
||||
write!(
|
||||
f,
|
||||
"Expected whitespace to end one argument, but found trailing data"
|
||||
)
|
||||
}
|
||||
BuiltInExceptions::DispatcherParseException { message } => {
|
||||
BuiltInError::DispatcherParseException { message } => {
|
||||
write!(f, "Could not parse command: {message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BuiltInExceptions {
|
||||
pub fn create(self) -> CommandSyntaxException {
|
||||
impl BuiltInError {
|
||||
pub fn create(self) -> CommandSyntaxError {
|
||||
let message = format!("{self:?}");
|
||||
CommandSyntaxException::create(self, message)
|
||||
CommandSyntaxError::create(self, message)
|
||||
}
|
||||
|
||||
pub fn create_with_context(self, reader: &StringReader) -> CommandSyntaxException {
|
||||
pub fn create_with_context(self, reader: &StringReader) -> CommandSyntaxError {
|
||||
let message = format!("{self:?}");
|
||||
CommandSyntaxException::new(self, message, reader.string(), reader.cursor())
|
||||
CommandSyntaxError::new(self, message, reader.string(), reader.cursor())
|
||||
}
|
||||
}
|
93
azalea-brigadier/src/errors/command_syntax_error.rs
Normal file
93
azalea-brigadier/src/errors/command_syntax_error.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
use std::{
|
||||
cmp,
|
||||
fmt::{self, Debug, Write},
|
||||
};
|
||||
|
||||
use super::builtin_errors::BuiltInError;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct CommandSyntaxError {
|
||||
kind: BuiltInError,
|
||||
message: String,
|
||||
input: Option<String>,
|
||||
cursor: Option<usize>,
|
||||
}
|
||||
|
||||
const CONTEXT_AMOUNT: usize = 10;
|
||||
|
||||
impl CommandSyntaxError {
|
||||
pub fn new(kind: BuiltInError, message: String, input: &str, cursor: usize) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
message,
|
||||
input: Some(input.to_string()),
|
||||
cursor: Some(cursor),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(kind: BuiltInError, message: String) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
message,
|
||||
input: None,
|
||||
cursor: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message(&self) -> String {
|
||||
let mut message = self.message.clone();
|
||||
let context = self.context();
|
||||
if let Some(context) = context {
|
||||
write!(
|
||||
message,
|
||||
" at position {}: {context}",
|
||||
self.cursor.unwrap_or(usize::MAX)
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
message
|
||||
}
|
||||
|
||||
pub fn raw_message(&self) -> &String {
|
||||
&self.message
|
||||
}
|
||||
|
||||
pub fn context(&self) -> Option<String> {
|
||||
if let Some(input) = &self.input
|
||||
&& let Some(cursor) = self.cursor
|
||||
{
|
||||
let mut builder = String::new();
|
||||
let cursor = cmp::min(input.len(), cursor);
|
||||
|
||||
if cursor > CONTEXT_AMOUNT {
|
||||
builder.push_str("...");
|
||||
}
|
||||
|
||||
builder.push_str(
|
||||
&input[(cmp::max(0, cursor as isize - CONTEXT_AMOUNT as isize) as usize)..cursor],
|
||||
);
|
||||
builder.push_str("<--[HERE]");
|
||||
|
||||
return Some(builder);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> &BuiltInError {
|
||||
&self.kind
|
||||
}
|
||||
|
||||
pub fn input(&self) -> &Option<String> {
|
||||
&self.input
|
||||
}
|
||||
|
||||
pub fn cursor(&self) -> Option<usize> {
|
||||
self.cursor
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for CommandSyntaxError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.message())
|
||||
}
|
||||
}
|
5
azalea-brigadier/src/errors/mod.rs
Normal file
5
azalea-brigadier/src/errors/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod builtin_errors;
|
||||
mod command_syntax_error;
|
||||
|
||||
pub use builtin_errors::BuiltInError;
|
||||
pub use command_syntax_error::CommandSyntaxError;
|
|
@ -1,95 +0,0 @@
|
|||
use std::{
|
||||
cmp,
|
||||
fmt::{self, Write},
|
||||
};
|
||||
|
||||
use super::builtin_exceptions::BuiltInExceptions;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct CommandSyntaxException {
|
||||
pub type_: BuiltInExceptions,
|
||||
message: String,
|
||||
input: Option<String>,
|
||||
cursor: Option<usize>,
|
||||
}
|
||||
|
||||
const CONTEXT_AMOUNT: usize = 10;
|
||||
|
||||
impl CommandSyntaxException {
|
||||
pub fn new(type_: BuiltInExceptions, message: String, input: &str, cursor: usize) -> Self {
|
||||
Self {
|
||||
type_,
|
||||
message,
|
||||
input: Some(input.to_string()),
|
||||
cursor: Some(cursor),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(type_: BuiltInExceptions, message: String) -> Self {
|
||||
Self {
|
||||
type_,
|
||||
message,
|
||||
input: None,
|
||||
cursor: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message(&self) -> String {
|
||||
let mut message = self.message.clone();
|
||||
let context = self.context();
|
||||
if let Some(context) = context {
|
||||
write!(
|
||||
message,
|
||||
" at position {}: {}",
|
||||
self.cursor.unwrap_or(usize::MAX),
|
||||
context
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
message
|
||||
}
|
||||
|
||||
pub fn raw_message(&self) -> &String {
|
||||
&self.message
|
||||
}
|
||||
|
||||
pub fn context(&self) -> Option<String> {
|
||||
if let Some(input) = &self.input {
|
||||
if let Some(cursor) = self.cursor {
|
||||
let mut builder = String::new();
|
||||
let cursor = cmp::min(input.len(), cursor);
|
||||
|
||||
if cursor > CONTEXT_AMOUNT {
|
||||
builder.push_str("...");
|
||||
}
|
||||
|
||||
builder.push_str(
|
||||
&input
|
||||
[(cmp::max(0, cursor as isize - CONTEXT_AMOUNT as isize) as usize)..cursor],
|
||||
);
|
||||
builder.push_str("<--[HERE]");
|
||||
|
||||
return Some(builder);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> &BuiltInExceptions {
|
||||
&self.type_
|
||||
}
|
||||
|
||||
pub fn input(&self) -> &Option<String> {
|
||||
&self.input
|
||||
}
|
||||
|
||||
pub fn cursor(&self) -> Option<usize> {
|
||||
self.cursor
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for CommandSyntaxException {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.message())
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
mod builtin_exceptions;
|
||||
mod command_syntax_exception;
|
||||
|
||||
pub use builtin_exceptions::BuiltInExceptions;
|
||||
pub use command_syntax_exception::CommandSyntaxException;
|
|
@ -4,9 +4,10 @@ pub mod arguments;
|
|||
pub mod builder;
|
||||
pub mod command_dispatcher;
|
||||
pub mod context;
|
||||
pub mod exceptions;
|
||||
pub mod errors;
|
||||
pub mod modifier;
|
||||
pub mod parse_results;
|
||||
pub mod result_consumer;
|
||||
pub mod string_reader;
|
||||
pub mod suggestion;
|
||||
pub mod tree;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::{context::CommandContext, exceptions::CommandSyntaxException};
|
||||
use crate::{context::CommandContext, errors::CommandSyntaxError};
|
||||
|
||||
pub type RedirectModifier<S> =
|
||||
dyn Fn(&CommandContext<S>) -> Result<Vec<Arc<S>>, CommandSyntaxException> + Send + Sync;
|
||||
dyn Fn(&CommandContext<S>) -> Result<Vec<Arc<S>>, CommandSyntaxError> + Send + Sync;
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
use std::{collections::HashMap, fmt::Debug, rc::Rc};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{self, Debug},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
context::CommandContextBuilder, exceptions::CommandSyntaxException,
|
||||
string_reader::StringReader, tree::CommandNode,
|
||||
context::CommandContextBuilder, errors::CommandSyntaxError, string_reader::StringReader,
|
||||
tree::CommandNode,
|
||||
};
|
||||
|
||||
pub struct ParseResults<'a, S> {
|
||||
pub context: CommandContextBuilder<'a, S>,
|
||||
pub reader: StringReader,
|
||||
pub exceptions: HashMap<Rc<CommandNode<S>>, CommandSyntaxException>,
|
||||
pub exceptions: HashMap<Rc<CommandNode<S>>, CommandSyntaxError>,
|
||||
}
|
||||
|
||||
impl<S> Debug for ParseResults<'_, S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ParseResults")
|
||||
.field("context", &self.context)
|
||||
// .field("reader", &self.reader)
|
||||
|
|
12
azalea-brigadier/src/result_consumer.rs
Normal file
12
azalea-brigadier/src/result_consumer.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use crate::context::CommandContext;
|
||||
|
||||
pub trait ResultConsumer<S> {
|
||||
fn on_command_complete(&self, context: Rc<CommandContext<S>>, success: bool, result: i32);
|
||||
}
|
||||
|
||||
pub struct DefaultResultConsumer;
|
||||
impl<S> ResultConsumer<S> for DefaultResultConsumer {
|
||||
fn on_command_complete(&self, _context: Rc<CommandContext<S>>, _success: bool, _result: i32) {}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use crate::exceptions::{BuiltInExceptions, CommandSyntaxException};
|
||||
use crate::errors::{BuiltInError, CommandSyntaxError};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StringReader {
|
||||
|
@ -91,19 +91,19 @@ impl StringReader {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn read_int(&mut self) -> Result<i32, CommandSyntaxException> {
|
||||
pub fn read_int(&mut self) -> Result<i32, CommandSyntaxError> {
|
||||
let start = self.cursor;
|
||||
while self.can_read() && StringReader::is_allowed_number(self.peek()) {
|
||||
self.skip();
|
||||
}
|
||||
let number = &self.string[start..self.cursor];
|
||||
if number.is_empty() {
|
||||
return Err(BuiltInExceptions::ReaderExpectedInt.create_with_context(self));
|
||||
return Err(BuiltInError::ReaderExpectedInt.create_with_context(self));
|
||||
}
|
||||
let result = i32::from_str(number);
|
||||
if result.is_err() {
|
||||
self.cursor = start;
|
||||
return Err(BuiltInExceptions::ReaderInvalidInt {
|
||||
return Err(BuiltInError::ReaderInvalidInt {
|
||||
value: number.to_string(),
|
||||
}
|
||||
.create_with_context(self));
|
||||
|
@ -112,19 +112,19 @@ impl StringReader {
|
|||
Ok(result.unwrap())
|
||||
}
|
||||
|
||||
pub fn read_long(&mut self) -> Result<i64, CommandSyntaxException> {
|
||||
pub fn read_long(&mut self) -> Result<i64, CommandSyntaxError> {
|
||||
let start = self.cursor;
|
||||
while self.can_read() && StringReader::is_allowed_number(self.peek()) {
|
||||
self.skip();
|
||||
}
|
||||
let number = &self.string[start..self.cursor];
|
||||
if number.is_empty() {
|
||||
return Err(BuiltInExceptions::ReaderExpectedLong.create_with_context(self));
|
||||
return Err(BuiltInError::ReaderExpectedLong.create_with_context(self));
|
||||
}
|
||||
let result = i64::from_str(number);
|
||||
if result.is_err() {
|
||||
self.cursor = start;
|
||||
return Err(BuiltInExceptions::ReaderInvalidLong {
|
||||
return Err(BuiltInError::ReaderInvalidLong {
|
||||
value: number.to_string(),
|
||||
}
|
||||
.create_with_context(self));
|
||||
|
@ -133,19 +133,19 @@ impl StringReader {
|
|||
Ok(result.unwrap())
|
||||
}
|
||||
|
||||
pub fn read_double(&mut self) -> Result<f64, CommandSyntaxException> {
|
||||
pub fn read_double(&mut self) -> Result<f64, CommandSyntaxError> {
|
||||
let start = self.cursor;
|
||||
while self.can_read() && StringReader::is_allowed_number(self.peek()) {
|
||||
self.skip();
|
||||
}
|
||||
let number = &self.string[start..self.cursor];
|
||||
if number.is_empty() {
|
||||
return Err(BuiltInExceptions::ReaderExpectedDouble.create_with_context(self));
|
||||
return Err(BuiltInError::ReaderExpectedDouble.create_with_context(self));
|
||||
}
|
||||
let result = f64::from_str(number);
|
||||
if result.is_err() {
|
||||
self.cursor = start;
|
||||
return Err(BuiltInExceptions::ReaderInvalidDouble {
|
||||
return Err(BuiltInError::ReaderInvalidDouble {
|
||||
value: number.to_string(),
|
||||
}
|
||||
.create_with_context(self));
|
||||
|
@ -154,19 +154,19 @@ impl StringReader {
|
|||
Ok(result.unwrap())
|
||||
}
|
||||
|
||||
pub fn read_float(&mut self) -> Result<f32, CommandSyntaxException> {
|
||||
pub fn read_float(&mut self) -> Result<f32, CommandSyntaxError> {
|
||||
let start = self.cursor;
|
||||
while self.can_read() && StringReader::is_allowed_number(self.peek()) {
|
||||
self.skip();
|
||||
}
|
||||
let number = &self.string[start..self.cursor];
|
||||
if number.is_empty() {
|
||||
return Err(BuiltInExceptions::ReaderExpectedFloat.create_with_context(self));
|
||||
return Err(BuiltInError::ReaderExpectedFloat.create_with_context(self));
|
||||
}
|
||||
let result = f32::from_str(number);
|
||||
if result.is_err() {
|
||||
self.cursor = start;
|
||||
return Err(BuiltInExceptions::ReaderInvalidFloat {
|
||||
return Err(BuiltInError::ReaderInvalidFloat {
|
||||
value: number.to_string(),
|
||||
}
|
||||
.create_with_context(self));
|
||||
|
@ -193,22 +193,19 @@ impl StringReader {
|
|||
&self.string[start..self.cursor]
|
||||
}
|
||||
|
||||
pub fn read_quoted_string(&mut self) -> Result<String, CommandSyntaxException> {
|
||||
pub fn read_quoted_string(&mut self) -> Result<String, CommandSyntaxError> {
|
||||
if !self.can_read() {
|
||||
return Ok(String::new());
|
||||
}
|
||||
let next = self.peek();
|
||||
if !StringReader::is_quoted_string_start(next) {
|
||||
return Err(BuiltInExceptions::ReaderExpectedStartOfQuote.create_with_context(self));
|
||||
return Err(BuiltInError::ReaderExpectedStartOfQuote.create_with_context(self));
|
||||
}
|
||||
self.skip();
|
||||
self.read_string_until(next)
|
||||
}
|
||||
|
||||
pub fn read_string_until(
|
||||
&mut self,
|
||||
terminator: char,
|
||||
) -> Result<String, CommandSyntaxException> {
|
||||
pub fn read_string_until(&mut self, terminator: char) -> Result<String, CommandSyntaxError> {
|
||||
let mut result = String::new();
|
||||
let mut escaped = false;
|
||||
while self.can_read() {
|
||||
|
@ -219,7 +216,7 @@ impl StringReader {
|
|||
escaped = false;
|
||||
} else {
|
||||
self.cursor -= 1;
|
||||
return Err(BuiltInExceptions::ReaderInvalidEscape { character: c }
|
||||
return Err(BuiltInError::ReaderInvalidEscape { character: c }
|
||||
.create_with_context(self));
|
||||
}
|
||||
} else if c == SYNTAX_ESCAPE {
|
||||
|
@ -231,10 +228,10 @@ impl StringReader {
|
|||
}
|
||||
}
|
||||
|
||||
Err(BuiltInExceptions::ReaderExpectedEndOfQuote.create_with_context(self))
|
||||
Err(BuiltInError::ReaderExpectedEndOfQuote.create_with_context(self))
|
||||
}
|
||||
|
||||
pub fn read_string(&mut self) -> Result<String, CommandSyntaxException> {
|
||||
pub fn read_string(&mut self) -> Result<String, CommandSyntaxError> {
|
||||
if !self.can_read() {
|
||||
return Ok(String::new());
|
||||
}
|
||||
|
@ -246,11 +243,11 @@ impl StringReader {
|
|||
Ok(self.read_unquoted_string().to_string())
|
||||
}
|
||||
|
||||
pub fn read_boolean(&mut self) -> Result<bool, CommandSyntaxException> {
|
||||
pub fn read_boolean(&mut self) -> Result<bool, CommandSyntaxError> {
|
||||
let start = self.cursor;
|
||||
let value = self.read_string()?;
|
||||
if value.is_empty() {
|
||||
return Err(BuiltInExceptions::ReaderExpectedBool.create_with_context(self));
|
||||
return Err(BuiltInError::ReaderExpectedBool.create_with_context(self));
|
||||
}
|
||||
|
||||
if value == "true" {
|
||||
|
@ -259,15 +256,13 @@ impl StringReader {
|
|||
Ok(false)
|
||||
} else {
|
||||
self.cursor = start;
|
||||
Err(BuiltInExceptions::ReaderInvalidBool { value }.create_with_context(self))
|
||||
Err(BuiltInError::ReaderInvalidBool { value }.create_with_context(self))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect(&mut self, c: char) -> Result<(), CommandSyntaxException> {
|
||||
pub fn expect(&mut self, c: char) -> Result<(), CommandSyntaxError> {
|
||||
if !self.can_read() || self.peek() != c {
|
||||
return Err(
|
||||
BuiltInExceptions::ReaderExpectedSymbol { symbol: c }.create_with_context(self)
|
||||
);
|
||||
return Err(BuiltInError::ReaderExpectedSymbol { symbol: c }.create_with_context(self));
|
||||
}
|
||||
self.skip();
|
||||
Ok(())
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
mod suggestion_provider;
|
||||
mod suggestions;
|
||||
mod suggestions_builder;
|
||||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
use std::io::Write;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
fmt::{self, Display},
|
||||
hash::Hash,
|
||||
io,
|
||||
};
|
||||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
use azalea_buf::AzaleaWrite;
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
use azalea_chat::FormattedText;
|
||||
pub use suggestion_provider::SuggestionProvider;
|
||||
pub use suggestions::Suggestions;
|
||||
pub use suggestions_builder::SuggestionsBuilder;
|
||||
|
||||
|
@ -93,7 +97,7 @@ impl Suggestion {
|
|||
}
|
||||
|
||||
impl SuggestionValue {
|
||||
pub fn cmp_ignore_case(&self, other: &Self) -> std::cmp::Ordering {
|
||||
pub fn cmp_ignore_case(&self, other: &Self) -> Ordering {
|
||||
match (self, other) {
|
||||
(SuggestionValue::Text(a), SuggestionValue::Text(b)) => {
|
||||
a.to_lowercase().cmp(&b.to_lowercase())
|
||||
|
@ -118,7 +122,7 @@ impl Display for SuggestionValue {
|
|||
}
|
||||
|
||||
impl Ord for SuggestionValue {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match (self, other) {
|
||||
(SuggestionValue::Text(a), SuggestionValue::Text(b)) => a.cmp(b),
|
||||
(SuggestionValue::Integer(a), SuggestionValue::Integer(b)) => a.cmp(b),
|
||||
|
@ -131,14 +135,14 @@ impl Ord for SuggestionValue {
|
|||
}
|
||||
}
|
||||
impl PartialOrd for SuggestionValue {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
impl AzaleaWrite for Suggestion {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
self.value.to_string().azalea_write(buf)?;
|
||||
self.tooltip
|
||||
.clone()
|
||||
|
|
10
azalea-brigadier/src/suggestion/suggestion_provider.rs
Normal file
10
azalea-brigadier/src/suggestion/suggestion_provider.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
use super::{Suggestions, SuggestionsBuilder};
|
||||
use crate::context::CommandContext;
|
||||
|
||||
pub trait SuggestionProvider<S> {
|
||||
fn get_suggestions(
|
||||
&self,
|
||||
context: CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
#[cfg(feature = "azalea-buf")]
|
||||
use std::io::{Cursor, Write};
|
||||
use std::{collections::HashSet, hash::Hash};
|
||||
use std::{collections::HashSet, hash::Hash, io};
|
||||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
use azalea_buf::{AzBuf, AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
|
||||
|
@ -107,7 +107,7 @@ impl AzaleaRead for Suggestions {
|
|||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
impl AzaleaWrite for Suggestions {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
(self.range.start() as u32).azalea_write_var(buf)?;
|
||||
(self.range.length() as u32).azalea_write_var(buf)?;
|
||||
self.suggestions.azalea_write(buf)?;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
fmt::Debug,
|
||||
hash::Hash,
|
||||
fmt::{self, Debug},
|
||||
hash::{Hash, Hasher},
|
||||
ptr,
|
||||
sync::Arc,
|
||||
};
|
||||
|
@ -14,18 +14,19 @@ use crate::{
|
|||
required_argument_builder::Argument,
|
||||
},
|
||||
context::{CommandContext, CommandContextBuilder, ParsedArgument, StringRange},
|
||||
exceptions::{BuiltInExceptions, CommandSyntaxException},
|
||||
errors::{BuiltInError, CommandSyntaxError},
|
||||
modifier::RedirectModifier,
|
||||
string_reader::StringReader,
|
||||
suggestion::{Suggestions, SuggestionsBuilder},
|
||||
};
|
||||
|
||||
pub type Command<S> = Option<Arc<dyn Fn(&CommandContext<S>) -> i32 + Send + Sync>>;
|
||||
pub type Command<S> =
|
||||
Option<Arc<dyn Fn(&CommandContext<S>) -> Result<i32, CommandSyntaxError> + Send + Sync>>;
|
||||
|
||||
/// An ArgumentBuilder that has been built.
|
||||
#[non_exhaustive]
|
||||
pub struct CommandNode<S> {
|
||||
pub value: ArgumentBuilderType,
|
||||
pub value: ArgumentBuilderType<S>,
|
||||
|
||||
// this is a BTreeMap because children need to be ordered when getting command suggestions
|
||||
pub children: BTreeMap<String, Arc<RwLock<CommandNode<S>>>>,
|
||||
|
@ -66,7 +67,7 @@ impl<S> CommandNode<S> {
|
|||
}
|
||||
/// Gets the argument, or panics. You should use match if you're not certain
|
||||
/// about the type.
|
||||
pub fn argument(&self) -> &Argument {
|
||||
pub fn argument(&self) -> &Argument<S> {
|
||||
match self.value {
|
||||
ArgumentBuilderType::Argument(ref argument) => argument,
|
||||
_ => panic!("CommandNode::argument() called on non-argument node"),
|
||||
|
@ -149,7 +150,7 @@ impl<S> CommandNode<S> {
|
|||
&self,
|
||||
reader: &mut StringReader,
|
||||
context_builder: &mut CommandContextBuilder<S>,
|
||||
) -> Result<(), CommandSyntaxException> {
|
||||
) -> Result<(), CommandSyntaxError> {
|
||||
match self.value {
|
||||
ArgumentBuilderType::Argument(ref argument) => {
|
||||
let start = reader.cursor();
|
||||
|
@ -176,7 +177,7 @@ impl<S> CommandNode<S> {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
Err(BuiltInExceptions::LiteralIncorrect {
|
||||
Err(BuiltInError::LiteralIncorrect {
|
||||
expected: literal.value.clone(),
|
||||
}
|
||||
.create_with_context(reader))
|
||||
|
@ -214,9 +215,7 @@ impl<S> CommandNode<S> {
|
|||
|
||||
pub fn list_suggestions(
|
||||
&self,
|
||||
// context is here because that's how it is in mojang's brigadier, but we haven't
|
||||
// implemented custom suggestions yet so this is unused rn
|
||||
_context: CommandContext<S>,
|
||||
context: CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
match &self.value {
|
||||
|
@ -231,15 +230,15 @@ impl<S> CommandNode<S> {
|
|||
Suggestions::default()
|
||||
}
|
||||
}
|
||||
ArgumentBuilderType::Argument(argument) => argument.list_suggestions(builder),
|
||||
ArgumentBuilderType::Argument(argument) => argument.list_suggestions(context, builder),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Debug for CommandNode<S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("CommandNode")
|
||||
.field("value", &self.value)
|
||||
// .field("value", &self.value)
|
||||
.field("children", &self.children)
|
||||
.field("command", &self.command.is_some())
|
||||
// .field("requirement", &self.requirement)
|
||||
|
@ -269,7 +268,7 @@ impl<S> Default for CommandNode<S> {
|
|||
}
|
||||
|
||||
impl<S> Hash for CommandNode<S> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
// hash the children
|
||||
for (k, v) in &self.children {
|
||||
k.hash(state);
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
use std::{mem, ops::Deref, sync::Arc};
|
||||
|
||||
use azalea_brigadier::{
|
||||
arguments::integer_argument_type::integer,
|
||||
builder::{literal_argument_builder::literal, required_argument_builder::argument},
|
||||
command_dispatcher::CommandDispatcher,
|
||||
context::CommandContext,
|
||||
};
|
||||
use azalea_brigadier::prelude::*;
|
||||
use bevy_app::App;
|
||||
use bevy_ecs::{prelude::*, system::RunSystemOnce};
|
||||
use parking_lot::Mutex;
|
||||
|
@ -145,8 +140,7 @@ impl DispatchStorage {
|
|||
///
|
||||
/// Spawns a number of entities with the [`SpawnedEntity`] component.
|
||||
fn command_spawn_entity_num(context: &CommandContext<WorldAccessor>) -> i32 {
|
||||
let num = context.argument("entities").unwrap();
|
||||
let num = *num.downcast_ref::<i32>().unwrap();
|
||||
let num = get_integer(context, "entities").unwrap();
|
||||
|
||||
for _ in 0..num {
|
||||
context.source.lock().spawn(SpawnedEntity);
|
||||
|
@ -178,7 +172,7 @@ impl WorldAccessor {
|
|||
|
||||
/// Swap the internal [`World`] with the given one.
|
||||
fn swap(&mut self, world: &mut World) {
|
||||
std::mem::swap(&mut *self.lock(), world);
|
||||
mem::swap(&mut *self.lock(), world);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,7 +181,7 @@ impl WorldAccessor {
|
|||
struct SpawnedEntity;
|
||||
|
||||
/// Implemented for convenience.
|
||||
impl std::ops::Deref for WorldAccessor {
|
||||
impl Deref for WorldAccessor {
|
||||
type Target = Arc<Mutex<World>>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.world
|
||||
|
|
|
@ -5,7 +5,7 @@ use azalea_brigadier::{
|
|||
builder::{literal_argument_builder::literal, required_argument_builder::argument},
|
||||
command_dispatcher::CommandDispatcher,
|
||||
context::CommandContext,
|
||||
exceptions::{BuiltInExceptions, CommandSyntaxException},
|
||||
errors::{BuiltInError, CommandSyntaxError},
|
||||
string_reader::StringReader,
|
||||
};
|
||||
|
||||
|
@ -50,10 +50,7 @@ fn execute_unknown_command() {
|
|||
let execute_result = subject.execute("foo", &CommandSource {});
|
||||
|
||||
let err = execute_result.err().unwrap();
|
||||
match err.type_ {
|
||||
BuiltInExceptions::DispatcherUnknownCommand => {}
|
||||
_ => panic!("Unexpected error"),
|
||||
}
|
||||
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownCommand);
|
||||
assert_eq!(err.cursor().unwrap(), 0);
|
||||
}
|
||||
|
||||
|
@ -65,10 +62,7 @@ fn execute_impermissible_command() {
|
|||
let execute_result = subject.execute("foo", &CommandSource {});
|
||||
|
||||
let err = execute_result.err().unwrap();
|
||||
match err.type_ {
|
||||
BuiltInExceptions::DispatcherUnknownCommand => {}
|
||||
_ => panic!("Unexpected error"),
|
||||
}
|
||||
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownCommand);
|
||||
assert_eq!(err.cursor().unwrap(), 0);
|
||||
}
|
||||
|
||||
|
@ -80,10 +74,7 @@ fn execute_empty_command() {
|
|||
let execute_result = subject.execute("", &CommandSource {});
|
||||
|
||||
let err = execute_result.err().unwrap();
|
||||
match err.type_ {
|
||||
BuiltInExceptions::DispatcherUnknownCommand => {}
|
||||
_ => panic!("Unexpected error"),
|
||||
}
|
||||
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownCommand);
|
||||
assert_eq!(err.cursor().unwrap(), 0);
|
||||
}
|
||||
|
||||
|
@ -95,10 +86,7 @@ fn execute_unknown_subcommand() {
|
|||
let execute_result = subject.execute("foo bar", &CommandSource {});
|
||||
|
||||
let err = execute_result.err().unwrap();
|
||||
match err.type_ {
|
||||
BuiltInExceptions::DispatcherUnknownArgument => {}
|
||||
_ => panic!("Unexpected error"),
|
||||
}
|
||||
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownArgument);
|
||||
assert_eq!(err.cursor().unwrap(), 4);
|
||||
}
|
||||
|
||||
|
@ -110,10 +98,7 @@ fn execute_incorrect_literal() {
|
|||
let execute_result = subject.execute("foo baz", &CommandSource {});
|
||||
|
||||
let err = execute_result.err().unwrap();
|
||||
match err.type_ {
|
||||
BuiltInExceptions::DispatcherUnknownArgument => {}
|
||||
_ => panic!("Unexpected error"),
|
||||
}
|
||||
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownArgument);
|
||||
assert_eq!(err.cursor().unwrap(), 4);
|
||||
}
|
||||
|
||||
|
@ -130,10 +115,7 @@ fn execute_ambiguous_incorrect_argument() {
|
|||
let execute_result = subject.execute("foo unknown", &CommandSource {});
|
||||
|
||||
let err = execute_result.err().unwrap();
|
||||
match err.type_ {
|
||||
BuiltInExceptions::DispatcherUnknownArgument => {}
|
||||
_ => panic!("Unexpected error"),
|
||||
}
|
||||
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownArgument);
|
||||
assert_eq!(err.cursor().unwrap(), 4);
|
||||
}
|
||||
|
||||
|
@ -245,7 +227,7 @@ fn execute_redirected_multiple_times() {
|
|||
);
|
||||
assert_eq!(*child2.unwrap().nodes[0].node.read(), *concrete_node.read());
|
||||
|
||||
assert_eq!(CommandDispatcher::execute_parsed(parse).unwrap(), 42);
|
||||
assert_eq!(subject.execute_parsed(parse).unwrap(), 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -255,7 +237,7 @@ fn execute_redirected() {
|
|||
let source1 = Arc::new(CommandSource {});
|
||||
let source2 = Arc::new(CommandSource {});
|
||||
|
||||
let modifier = move |_: &CommandContext<CommandSource>| -> Result<Vec<Arc<CommandSource>>, CommandSyntaxException> {
|
||||
let modifier = move |_: &CommandContext<CommandSource>| -> Result<Vec<Arc<CommandSource>>, CommandSyntaxError> {
|
||||
Ok(vec![source1.clone(), source2.clone()])
|
||||
};
|
||||
|
||||
|
@ -281,7 +263,7 @@ fn execute_redirected() {
|
|||
assert_eq!(*parent.nodes[0].node.read(), *concrete_node.read());
|
||||
assert_eq!(*parent.source, CommandSource {});
|
||||
|
||||
assert_eq!(CommandDispatcher::execute_parsed(parse).unwrap(), 2);
|
||||
assert_eq!(subject.execute_parsed(parse).unwrap(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -297,10 +279,7 @@ fn execute_orphaned_subcommand() {
|
|||
let result = subject.execute("foo 5", &CommandSource {});
|
||||
assert!(result.is_err());
|
||||
let result = result.unwrap_err();
|
||||
assert_eq!(
|
||||
*result.get_type(),
|
||||
BuiltInExceptions::DispatcherUnknownCommand
|
||||
);
|
||||
assert_eq!(*result.kind(), BuiltInError::DispatcherUnknownCommand);
|
||||
assert_eq!(result.cursor(), Some(5));
|
||||
}
|
||||
|
||||
|
@ -327,10 +306,7 @@ fn parse_no_space_separator() {
|
|||
let result = subject.execute("foo$", &CommandSource {});
|
||||
assert!(result.is_err());
|
||||
let result = result.unwrap_err();
|
||||
assert_eq!(
|
||||
*result.get_type(),
|
||||
BuiltInExceptions::DispatcherUnknownCommand
|
||||
);
|
||||
assert_eq!(*result.kind(), BuiltInError::DispatcherUnknownCommand);
|
||||
assert_eq!(result.cursor(), Some(0));
|
||||
}
|
||||
|
||||
|
@ -348,7 +324,7 @@ fn execute_invalid_subcommand() {
|
|||
assert!(result.is_err());
|
||||
let result = result.unwrap_err();
|
||||
// this fails for some reason, i blame mojang
|
||||
// assert_eq!(*result.get_type(), BuiltInExceptions::ReaderExpectedInt);
|
||||
// assert_eq!(*result.get_type(), BuiltInError::ReaderExpectedInt);
|
||||
assert_eq!(result.cursor(), Some(4));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use azalea_brigadier::{exceptions::BuiltInExceptions, string_reader::StringReader};
|
||||
use azalea_brigadier::{errors::BuiltInError, string_reader::StringReader};
|
||||
|
||||
#[test]
|
||||
fn can_read() {
|
||||
|
@ -222,7 +222,7 @@ fn read_quoted_string_no_open() {
|
|||
let result = reader.read_quoted_string();
|
||||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedStartOfQuote);
|
||||
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedStartOfQuote);
|
||||
assert_eq!(e.cursor(), Some(0));
|
||||
}
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ fn read_quoted_string_no_close() {
|
|||
let result = reader.read_quoted_string();
|
||||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedEndOfQuote);
|
||||
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedEndOfQuote);
|
||||
assert_eq!(e.cursor(), Some(12));
|
||||
}
|
||||
}
|
||||
|
@ -245,8 +245,8 @@ fn read_quoted_string_invalid_escape() {
|
|||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert_eq!(
|
||||
e.get_type(),
|
||||
&BuiltInExceptions::ReaderInvalidEscape { character: 'n' }
|
||||
e.kind(),
|
||||
&BuiltInError::ReaderInvalidEscape { character: 'n' }
|
||||
);
|
||||
assert_eq!(e.cursor(), Some(7));
|
||||
}
|
||||
|
@ -259,8 +259,8 @@ fn read_quoted_string_invalid_quote_escape() {
|
|||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert_eq!(
|
||||
e.get_type(),
|
||||
&BuiltInExceptions::ReaderInvalidEscape { character: '"' }
|
||||
e.kind(),
|
||||
&BuiltInError::ReaderInvalidEscape { character: '"' }
|
||||
);
|
||||
assert_eq!(e.cursor(), Some(7));
|
||||
}
|
||||
|
@ -313,8 +313,8 @@ fn read_int_invalid() {
|
|||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert_eq!(
|
||||
e.get_type(),
|
||||
&BuiltInExceptions::ReaderInvalidInt {
|
||||
e.kind(),
|
||||
&BuiltInError::ReaderInvalidInt {
|
||||
value: "12.34".to_string()
|
||||
}
|
||||
);
|
||||
|
@ -328,7 +328,7 @@ fn read_int_none() {
|
|||
let result = reader.read_int();
|
||||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedInt);
|
||||
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedInt);
|
||||
assert_eq!(e.cursor(), Some(0));
|
||||
}
|
||||
}
|
||||
|
@ -372,8 +372,8 @@ fn read_long_invalid() {
|
|||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert_eq!(
|
||||
e.get_type(),
|
||||
&BuiltInExceptions::ReaderInvalidLong {
|
||||
e.kind(),
|
||||
&BuiltInError::ReaderInvalidLong {
|
||||
value: "12.34".to_string()
|
||||
}
|
||||
);
|
||||
|
@ -387,7 +387,7 @@ fn read_long_none() {
|
|||
let result = reader.read_long();
|
||||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedLong);
|
||||
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedLong);
|
||||
assert_eq!(e.cursor(), Some(0));
|
||||
}
|
||||
}
|
||||
|
@ -439,8 +439,8 @@ fn read_double_invalid() {
|
|||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert_eq!(
|
||||
e.get_type(),
|
||||
&BuiltInExceptions::ReaderInvalidDouble {
|
||||
e.kind(),
|
||||
&BuiltInError::ReaderInvalidDouble {
|
||||
value: "12.34.56".to_string()
|
||||
}
|
||||
);
|
||||
|
@ -454,7 +454,7 @@ fn read_double_none() {
|
|||
let result = reader.read_double();
|
||||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedDouble);
|
||||
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedDouble);
|
||||
assert_eq!(e.cursor(), Some(0));
|
||||
}
|
||||
}
|
||||
|
@ -506,8 +506,8 @@ fn read_float_invalid() {
|
|||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert_eq!(
|
||||
e.get_type(),
|
||||
&BuiltInExceptions::ReaderInvalidFloat {
|
||||
e.kind(),
|
||||
&BuiltInError::ReaderInvalidFloat {
|
||||
value: "12.34.56".to_string()
|
||||
}
|
||||
);
|
||||
|
@ -521,7 +521,7 @@ fn read_float_none() {
|
|||
let result = reader.read_float();
|
||||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedFloat);
|
||||
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedFloat);
|
||||
assert_eq!(e.cursor(), Some(0));
|
||||
}
|
||||
}
|
||||
|
@ -556,8 +556,8 @@ fn expect_incorrect() {
|
|||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert_eq!(
|
||||
e.get_type(),
|
||||
&BuiltInExceptions::ReaderExpectedSymbol { symbol: 'a' }
|
||||
e.kind(),
|
||||
&BuiltInError::ReaderExpectedSymbol { symbol: 'a' }
|
||||
);
|
||||
assert_eq!(e.cursor(), Some(0));
|
||||
}
|
||||
|
@ -570,8 +570,8 @@ fn expect_none() {
|
|||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert_eq!(
|
||||
e.get_type(),
|
||||
&BuiltInExceptions::ReaderExpectedSymbol { symbol: 'a' }
|
||||
e.kind(),
|
||||
&BuiltInError::ReaderExpectedSymbol { symbol: 'a' }
|
||||
);
|
||||
assert_eq!(e.cursor(), Some(0));
|
||||
}
|
||||
|
@ -591,8 +591,8 @@ fn read_boolean_incorrect() {
|
|||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert_eq!(
|
||||
e.get_type(),
|
||||
&BuiltInExceptions::ReaderInvalidBool {
|
||||
e.kind(),
|
||||
&BuiltInError::ReaderInvalidBool {
|
||||
value: "tuesday".to_string()
|
||||
}
|
||||
);
|
||||
|
@ -606,7 +606,7 @@ fn read_boolean_none() {
|
|||
let result = reader.read_boolean();
|
||||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedBool);
|
||||
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedBool);
|
||||
assert_eq!(e.cursor(), Some(0));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ fn merge_single() {
|
|||
StringRange::at(5),
|
||||
vec![Suggestion::new(StringRange::at(5), "ar")],
|
||||
);
|
||||
let merged = Suggestions::merge("foo b", &[suggestions.clone()]);
|
||||
let merged = Suggestions::merge("foo b", std::slice::from_ref(&suggestions));
|
||||
assert_eq!(merged, suggestions);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::{
|
|||
backtrace::Backtrace,
|
||||
collections::HashMap,
|
||||
hash::Hash,
|
||||
io::{Cursor, Read},
|
||||
io::{self, Cursor, Read},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
|
@ -30,7 +30,7 @@ pub enum BufReadError {
|
|||
Io {
|
||||
#[from]
|
||||
#[backtrace]
|
||||
source: std::io::Error,
|
||||
source: io::Error,
|
||||
},
|
||||
#[error("Invalid UTF-8: {bytes:?} (lossy: {lossy:?})")]
|
||||
InvalidUtf8 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::io::{Cursor, Write};
|
||||
use std::io::{self, Cursor, Write};
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -46,7 +46,7 @@ impl AzaleaRead for Uuid {
|
|||
}
|
||||
|
||||
impl AzaleaWrite for Uuid {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
let [a, b, c, d] = self.to_int_array();
|
||||
a.azalea_write(buf)?;
|
||||
b.azalea_write(buf)?;
|
||||
|
|
|
@ -8,7 +8,7 @@ use byteorder::{BigEndian, WriteBytesExt};
|
|||
|
||||
use super::{MAX_STRING_LENGTH, UnsizedByteArray};
|
||||
|
||||
fn write_utf_with_len(buf: &mut impl Write, string: &str, len: usize) -> Result<(), io::Error> {
|
||||
fn write_utf_with_len(buf: &mut impl Write, string: &str, len: usize) -> io::Result<()> {
|
||||
if string.len() > len {
|
||||
panic!(
|
||||
"String too big (was {} bytes encoded, max {})",
|
||||
|
@ -21,21 +21,21 @@ fn write_utf_with_len(buf: &mut impl Write, string: &str, len: usize) -> Result<
|
|||
}
|
||||
|
||||
pub trait AzaleaWrite {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error>;
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()>;
|
||||
}
|
||||
|
||||
pub trait AzaleaWriteVar {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error>;
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl AzaleaWrite for i32 {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
WriteBytesExt::write_i32::<BigEndian>(buf, *self)
|
||||
}
|
||||
}
|
||||
|
||||
impl AzaleaWriteVar for i32 {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
let mut buffer = [0];
|
||||
let mut value = *self;
|
||||
if value == 0 {
|
||||
|
@ -54,24 +54,24 @@ impl AzaleaWriteVar for i32 {
|
|||
}
|
||||
|
||||
impl AzaleaWrite for UnsizedByteArray {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
buf.write_all(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AzaleaWrite> AzaleaWrite for Vec<T> {
|
||||
default fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
default fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
self[..].azalea_write(buf)
|
||||
}
|
||||
}
|
||||
impl<T: AzaleaWrite> AzaleaWrite for Box<[T]> {
|
||||
default fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
default fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
self[..].azalea_write(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AzaleaWrite> AzaleaWrite for [T] {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
(self.len() as u32).azalea_write_var(buf)?;
|
||||
for item in self {
|
||||
T::azalea_write(item, buf)?;
|
||||
|
@ -81,7 +81,7 @@ impl<T: AzaleaWrite> AzaleaWrite for [T] {
|
|||
}
|
||||
|
||||
impl<K: AzaleaWrite, V: AzaleaWrite> AzaleaWrite for HashMap<K, V> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
u32::azalea_write_var(&(self.len() as u32), buf)?;
|
||||
for (key, value) in self {
|
||||
key.azalea_write(buf)?;
|
||||
|
@ -93,7 +93,7 @@ impl<K: AzaleaWrite, V: AzaleaWrite> AzaleaWrite for HashMap<K, V> {
|
|||
}
|
||||
|
||||
impl<K: AzaleaWrite, V: AzaleaWriteVar> AzaleaWriteVar for HashMap<K, V> {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
u32::azalea_write_var(&(self.len() as u32), buf)?;
|
||||
for (key, value) in self {
|
||||
key.azalea_write(buf)?;
|
||||
|
@ -105,38 +105,38 @@ impl<K: AzaleaWrite, V: AzaleaWriteVar> AzaleaWriteVar for HashMap<K, V> {
|
|||
}
|
||||
|
||||
impl AzaleaWrite for Vec<u8> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
(self.len() as u32).azalea_write_var(buf)?;
|
||||
buf.write_all(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl AzaleaWrite for String {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
write_utf_with_len(buf, self, MAX_STRING_LENGTH.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl AzaleaWrite for &str {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
write_utf_with_len(buf, self, MAX_STRING_LENGTH.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl AzaleaWrite for u32 {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
i32::azalea_write(&(*self as i32), buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl AzaleaWriteVar for u32 {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
i32::azalea_write_var(&(*self as i32), buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl AzaleaWriteVar for i64 {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
let mut buffer = [0];
|
||||
let mut value = *self;
|
||||
if value == 0 {
|
||||
|
@ -155,25 +155,25 @@ impl AzaleaWriteVar for i64 {
|
|||
}
|
||||
|
||||
impl AzaleaWriteVar for u64 {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
i64::azalea_write_var(&(*self as i64), buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl AzaleaWrite for u16 {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
i16::azalea_write(&(*self as i16), buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl AzaleaWriteVar for u16 {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
i32::azalea_write_var(&(*self as i32), buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AzaleaWriteVar> AzaleaWriteVar for [T] {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
u32::azalea_write_var(&(self.len() as u32), buf)?;
|
||||
for i in self {
|
||||
i.azalea_write_var(buf)?;
|
||||
|
@ -182,67 +182,67 @@ impl<T: AzaleaWriteVar> AzaleaWriteVar for [T] {
|
|||
}
|
||||
}
|
||||
impl<T: AzaleaWriteVar> AzaleaWriteVar for Vec<T> {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
self[..].azalea_write_var(buf)
|
||||
}
|
||||
}
|
||||
impl<T: AzaleaWriteVar> AzaleaWriteVar for Box<[T]> {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
self[..].azalea_write_var(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl AzaleaWrite for u8 {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
WriteBytesExt::write_u8(buf, *self)
|
||||
}
|
||||
}
|
||||
|
||||
impl AzaleaWrite for i16 {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
WriteBytesExt::write_i16::<BigEndian>(buf, *self)
|
||||
}
|
||||
}
|
||||
|
||||
impl AzaleaWrite for i64 {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
WriteBytesExt::write_i64::<BigEndian>(buf, *self)
|
||||
}
|
||||
}
|
||||
|
||||
impl AzaleaWrite for u64 {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
i64::azalea_write(&(*self as i64), buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl AzaleaWrite for bool {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
let byte = u8::from(*self);
|
||||
byte.azalea_write(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl AzaleaWrite for i8 {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
(*self as u8).azalea_write(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl AzaleaWrite for f32 {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
WriteBytesExt::write_f32::<BigEndian>(buf, *self)
|
||||
}
|
||||
}
|
||||
|
||||
impl AzaleaWrite for f64 {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
WriteBytesExt::write_f64::<BigEndian>(buf, *self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AzaleaWrite> AzaleaWrite for Option<T> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
if let Some(s) = self {
|
||||
true.azalea_write(buf)?;
|
||||
s.azalea_write(buf)?;
|
||||
|
@ -254,7 +254,7 @@ impl<T: AzaleaWrite> AzaleaWrite for Option<T> {
|
|||
}
|
||||
|
||||
impl<T: AzaleaWriteVar> AzaleaWriteVar for Option<T> {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
if let Some(s) = self {
|
||||
true.azalea_write(buf)?;
|
||||
s.azalea_write_var(buf)?;
|
||||
|
@ -267,7 +267,7 @@ impl<T: AzaleaWriteVar> AzaleaWriteVar for Option<T> {
|
|||
|
||||
// [T; N]
|
||||
impl<T: AzaleaWrite, const N: usize> AzaleaWrite for [T; N] {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
for i in self {
|
||||
i.azalea_write(buf)?;
|
||||
}
|
||||
|
@ -276,7 +276,7 @@ impl<T: AzaleaWrite, const N: usize> AzaleaWrite for [T; N] {
|
|||
}
|
||||
|
||||
impl AzaleaWrite for simdnbt::owned::NbtTag {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
let mut data = Vec::new();
|
||||
self.write(&mut data);
|
||||
buf.write_all(&data)
|
||||
|
@ -284,7 +284,7 @@ impl AzaleaWrite for simdnbt::owned::NbtTag {
|
|||
}
|
||||
|
||||
impl AzaleaWrite for simdnbt::owned::NbtCompound {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
let mut data = Vec::new();
|
||||
simdnbt::owned::NbtTag::Compound(self.clone()).write(&mut data);
|
||||
buf.write_all(&data)
|
||||
|
@ -292,7 +292,7 @@ impl AzaleaWrite for simdnbt::owned::NbtCompound {
|
|||
}
|
||||
|
||||
impl AzaleaWrite for simdnbt::owned::Nbt {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
let mut data = Vec::new();
|
||||
self.write_unnamed(&mut data);
|
||||
buf.write_all(&data)
|
||||
|
@ -303,20 +303,20 @@ impl<T> AzaleaWrite for Box<T>
|
|||
where
|
||||
T: AzaleaWrite,
|
||||
{
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
T::azalea_write(&**self, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: AzaleaWrite, B: AzaleaWrite> AzaleaWrite for (A, B) {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
self.0.azalea_write(buf)?;
|
||||
self.1.azalea_write(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AzaleaWrite> AzaleaWrite for Arc<T> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
T::azalea_write(&**self, buf)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
use std::{fmt::Display, sync::LazyLock};
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
io::{self, Cursor, Write},
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError};
|
||||
|
@ -269,13 +273,12 @@ impl<'de> Deserialize<'de> for FormattedText {
|
|||
// string to with_array otherwise add the component
|
||||
// to the array
|
||||
let c = FormattedText::deserialize(item).map_err(de::Error::custom)?;
|
||||
if let FormattedText::Text(text_component) = c {
|
||||
if text_component.base.siblings.is_empty()
|
||||
&& text_component.base.style.is_empty()
|
||||
{
|
||||
with_array.push(StringOrComponent::String(text_component.text));
|
||||
continue;
|
||||
}
|
||||
if let FormattedText::Text(text_component) = c
|
||||
&& text_component.base.siblings.is_empty()
|
||||
&& text_component.base.style.is_empty()
|
||||
{
|
||||
with_array.push(StringOrComponent::String(text_component.text));
|
||||
continue;
|
||||
}
|
||||
with_array.push(StringOrComponent::FormattedText(
|
||||
FormattedText::deserialize(item).map_err(de::Error::custom)?,
|
||||
|
@ -465,13 +468,12 @@ impl FormattedText {
|
|||
with_array.push(StringOrComponent::String("?".to_string()));
|
||||
}
|
||||
} else if let Some(c) = FormattedText::from_nbt_compound(item) {
|
||||
if let FormattedText::Text(text_component) = c {
|
||||
if text_component.base.siblings.is_empty()
|
||||
&& text_component.base.style.is_empty()
|
||||
{
|
||||
with_array.push(StringOrComponent::String(text_component.text));
|
||||
continue;
|
||||
}
|
||||
if let FormattedText::Text(text_component) = c
|
||||
&& text_component.base.siblings.is_empty()
|
||||
&& text_component.base.style.is_empty()
|
||||
{
|
||||
with_array.push(StringOrComponent::String(text_component.text));
|
||||
continue;
|
||||
}
|
||||
with_array.push(StringOrComponent::FormattedText(
|
||||
FormattedText::from_nbt_compound(item)?,
|
||||
|
@ -547,7 +549,7 @@ impl From<&simdnbt::Mutf8Str> for FormattedText {
|
|||
#[cfg(feature = "azalea-buf")]
|
||||
#[cfg(feature = "simdnbt")]
|
||||
impl AzaleaRead for FormattedText {
|
||||
fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
let nbt = simdnbt::borrow::read_optional_tag(buf)?;
|
||||
match nbt {
|
||||
Some(nbt) => FormattedText::from_nbt_tag(nbt.as_tag()).ok_or(BufReadError::Custom(
|
||||
|
@ -561,7 +563,7 @@ impl AzaleaRead for FormattedText {
|
|||
#[cfg(feature = "azalea-buf")]
|
||||
#[cfg(feature = "simdnbt")]
|
||||
impl AzaleaWrite for FormattedText {
|
||||
fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
let mut out = Vec::new();
|
||||
simdnbt::owned::BaseNbt::write_unnamed(&(self.clone().to_compound().into()), &mut out);
|
||||
buf.write_all(&out)
|
||||
|
@ -583,7 +585,7 @@ impl From<&str> for FormattedText {
|
|||
}
|
||||
|
||||
impl Display for FormattedText {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
FormattedText::Text(c) => c.fmt(f),
|
||||
FormattedText::Translatable(c) => c.fmt(f),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Contains a few ways to style numbers. At the time of writing, Minecraft only
|
||||
//! uses this for rendering scoreboard objectives.
|
||||
|
||||
use std::io::{Cursor, Write};
|
||||
use std::io::{self, Cursor, Write};
|
||||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
use azalea_buf::{AzaleaRead, AzaleaWrite};
|
||||
|
@ -35,7 +35,7 @@ impl AzaleaRead for NumberFormat {
|
|||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
impl AzaleaWrite for NumberFormat {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
match self {
|
||||
NumberFormat::Blank => NumberFormatKind::Blank.azalea_write(buf)?,
|
||||
NumberFormat::Styled { style } => {
|
||||
|
|
|
@ -268,7 +268,7 @@ impl TextColor {
|
|||
|
||||
fn serialize(&self) -> String {
|
||||
if let Some(name) = &self.name {
|
||||
name.clone()
|
||||
name.clone().to_ascii_lowercase()
|
||||
} else {
|
||||
self.format_value()
|
||||
}
|
||||
|
@ -595,29 +595,29 @@ impl Style {
|
|||
pub fn get_html_style(&self) -> String {
|
||||
let mut style = String::new();
|
||||
if let Some(color) = &self.color {
|
||||
style.push_str(&format!("color: {};", color.format_value()));
|
||||
style.push_str(&format!("color:{};", color.format_value()));
|
||||
}
|
||||
if let Some(bold) = self.bold {
|
||||
style.push_str(&format!(
|
||||
"font-weight: {};",
|
||||
"font-weight:{};",
|
||||
if bold { "bold" } else { "normal" }
|
||||
));
|
||||
}
|
||||
if let Some(italic) = self.italic {
|
||||
style.push_str(&format!(
|
||||
"font-style: {};",
|
||||
"font-style:{};",
|
||||
if italic { "italic" } else { "normal" }
|
||||
));
|
||||
}
|
||||
if let Some(underlined) = self.underlined {
|
||||
style.push_str(&format!(
|
||||
"text-decoration: {};",
|
||||
"text-decoration:{};",
|
||||
if underlined { "underline" } else { "none" }
|
||||
));
|
||||
}
|
||||
if let Some(strikethrough) = self.strikethrough {
|
||||
style.push_str(&format!(
|
||||
"text-decoration: {};",
|
||||
"text-decoration:{};",
|
||||
if strikethrough {
|
||||
"line-through"
|
||||
} else {
|
||||
|
@ -625,10 +625,10 @@ impl Style {
|
|||
}
|
||||
));
|
||||
}
|
||||
if let Some(obfuscated) = self.obfuscated {
|
||||
if obfuscated {
|
||||
style.push_str("filter: blur(2px);");
|
||||
}
|
||||
if let Some(obfuscated) = self.obfuscated
|
||||
&& obfuscated
|
||||
{
|
||||
style.push_str("filter:blur(2px);");
|
||||
}
|
||||
|
||||
style
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::fmt::Display;
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
use serde::{__private::ser::FlatMapSerializer, Serialize, Serializer, ser::SerializeMap};
|
||||
|
||||
|
@ -142,7 +142,7 @@ impl TextComponent {
|
|||
}
|
||||
|
||||
impl Display for TextComponent {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// this contains the final string will all the ansi escape codes
|
||||
for component in FormattedText::Text(self.clone()).into_iter() {
|
||||
let component_text = match &component {
|
||||
|
@ -191,9 +191,9 @@ mod tests {
|
|||
format!(
|
||||
"{GREEN}Hypixel Network {END_SPAN}{RED}[1.8-1.18]<br>{END_SPAN}{BOLD_AQUA}HAPPY HOLIDAYS{END_SPAN}",
|
||||
END_SPAN = "</span>",
|
||||
GREEN = "<span style=\"color: #55FF55;\">",
|
||||
RED = "<span style=\"color: #FF5555;\">",
|
||||
BOLD_AQUA = "<span style=\"color: #55FFFF;font-weight: bold;\">",
|
||||
GREEN = "<span style=\"color:#55FF55;\">",
|
||||
RED = "<span style=\"color:#FF5555;\">",
|
||||
BOLD_AQUA = "<span style=\"color:#55FFFF;font-weight:bold;\">",
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -207,8 +207,8 @@ mod tests {
|
|||
format!(
|
||||
"{GREEN}<b>&<br>{END_SPAN}{AQUA}</b>{END_SPAN}",
|
||||
END_SPAN = "</span>",
|
||||
GREEN = "<span style=\"color: #55FF55;\">",
|
||||
AQUA = "<span style=\"color: #55FFFF;\">",
|
||||
GREEN = "<span style=\"color:#55FF55;\">",
|
||||
AQUA = "<span style=\"color:#55FFFF;\">",
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::fmt::{self, Display, Formatter};
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
use serde::{__private::ser::FlatMapSerializer, Serialize, Serializer, ser::SerializeMap};
|
||||
#[cfg(feature = "simdnbt")]
|
||||
|
@ -189,7 +189,7 @@ impl TranslatableComponent {
|
|||
}
|
||||
|
||||
impl Display for TranslatableComponent {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// this contains the final string will all the ansi escape codes
|
||||
for component in FormattedText::Translatable(self.clone()).into_iter() {
|
||||
let component_text = match &component {
|
||||
|
@ -208,7 +208,7 @@ impl Display for TranslatableComponent {
|
|||
}
|
||||
|
||||
impl Display for StringOrComponent {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
StringOrComponent::String(s) => write!(f, "{s}"),
|
||||
StringOrComponent::FormattedText(c) => write!(f, "{c}"),
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
use std::sync::Arc;
|
||||
|
||||
use azalea_auth::AccessTokenResponse;
|
||||
use azalea_auth::certs::{Certificates, FetchCertificatesError};
|
||||
use azalea_auth::{
|
||||
AccessTokenResponse,
|
||||
certs::{Certificates, FetchCertificatesError},
|
||||
};
|
||||
use bevy_ecs::component::Component;
|
||||
use parking_lot::Mutex;
|
||||
use thiserror::Error;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::Debug,
|
||||
io, mem,
|
||||
mem,
|
||||
net::SocketAddr,
|
||||
sync::Arc,
|
||||
thread,
|
||||
|
@ -9,7 +9,6 @@ use std::{
|
|||
};
|
||||
|
||||
use azalea_auth::game_profile::GameProfile;
|
||||
use azalea_chat::FormattedText;
|
||||
use azalea_core::{
|
||||
data_registry::ResolvableDataRegistry, position::Vec3, resource_location::ResourceLocation,
|
||||
tick::GameTick,
|
||||
|
@ -22,9 +21,9 @@ use azalea_entity::{
|
|||
use azalea_protocol::{
|
||||
ServerAddress,
|
||||
common::client_information::ClientInformation,
|
||||
connect::{ConnectionError, Proxy},
|
||||
connect::Proxy,
|
||||
packets::{
|
||||
self, Packet,
|
||||
Packet,
|
||||
game::{self, ServerboundGamePacket},
|
||||
},
|
||||
resolver,
|
||||
|
@ -46,7 +45,7 @@ use tracing::{debug, error, info, warn};
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
Account, DefaultPlugins, PlayerInfo,
|
||||
Account, DefaultPlugins,
|
||||
attack::{self},
|
||||
chunks::ChunkBatchInfo,
|
||||
connection::RawConnection,
|
||||
|
@ -54,14 +53,12 @@ use crate::{
|
|||
events::Event,
|
||||
interact::CurrentSequenceNumber,
|
||||
inventory::Inventory,
|
||||
join::{ConnectOpts, StartJoinCallback, StartJoinServerEvent},
|
||||
local_player::{
|
||||
GameProfileComponent, Hunger, InstanceHolder, PermissionLevel, PlayerAbilities, TabList,
|
||||
},
|
||||
join::{ConnectOpts, StartJoinServerEvent},
|
||||
local_player::{Hunger, InstanceHolder, PermissionLevel, PlayerAbilities, TabList},
|
||||
mining::{self},
|
||||
movement::{LastSentLookDirection, PhysicsState},
|
||||
packet::game::SendPacketEvent,
|
||||
player::retroactively_add_game_profile_component,
|
||||
player::{GameProfileComponent, PlayerInfo, retroactively_add_game_profile_component},
|
||||
};
|
||||
|
||||
/// `Client` has the things that a user interacting with the library will want.
|
||||
|
@ -89,22 +86,8 @@ pub struct Client {
|
|||
pub enum JoinError {
|
||||
#[error("{0}")]
|
||||
Resolver(#[from] resolver::ResolverError),
|
||||
#[error("{0}")]
|
||||
Connection(#[from] ConnectionError),
|
||||
#[error("{0}")]
|
||||
ReadPacket(#[from] Box<azalea_protocol::read::ReadPacketError>),
|
||||
#[error("{0}")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("Failed to encrypt the challenge from the server for {0:?}")]
|
||||
EncryptionError(packets::login::ClientboundHello),
|
||||
#[error("{0}")]
|
||||
SessionServer(#[from] azalea_auth::sessionserver::ClientSessionServerError),
|
||||
#[error("The given address could not be parsed into a ServerAddress")]
|
||||
InvalidAddress,
|
||||
#[error("Couldn't refresh access token: {0}")]
|
||||
Auth(#[from] azalea_auth::AuthError),
|
||||
#[error("Disconnected: {reason}")]
|
||||
Disconnect { reason: FormattedText },
|
||||
}
|
||||
|
||||
pub struct StartClientOpts {
|
||||
|
@ -168,7 +151,7 @@ impl Client {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use azalea_client::{Client, Account};
|
||||
/// use azalea_client::{Account, Client};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
@ -193,7 +176,7 @@ impl Client {
|
|||
resolved_address,
|
||||
Some(tx),
|
||||
))
|
||||
.await?;
|
||||
.await;
|
||||
Ok((client, rx))
|
||||
}
|
||||
|
||||
|
@ -209,7 +192,7 @@ impl Client {
|
|||
let client = Self::start_client(
|
||||
StartClientOpts::new(account, address, resolved_address, Some(tx)).proxy(proxy),
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
Ok((client, rx))
|
||||
}
|
||||
|
||||
|
@ -222,25 +205,24 @@ impl Client {
|
|||
connect_opts,
|
||||
event_sender,
|
||||
}: StartClientOpts,
|
||||
) -> Result<Self, JoinError> {
|
||||
) -> Self {
|
||||
// send a StartJoinServerEvent
|
||||
|
||||
let (start_join_callback_tx, mut start_join_callback_rx) =
|
||||
mpsc::unbounded_channel::<Result<Entity, JoinError>>();
|
||||
mpsc::unbounded_channel::<Entity>();
|
||||
|
||||
ecs_lock.lock().send_event(StartJoinServerEvent {
|
||||
account,
|
||||
connect_opts,
|
||||
event_sender,
|
||||
start_join_callback_tx: Some(StartJoinCallback(start_join_callback_tx)),
|
||||
start_join_callback_tx: Some(start_join_callback_tx),
|
||||
});
|
||||
|
||||
let entity = start_join_callback_rx.recv().await.expect(
|
||||
"StartJoinCallback should not be dropped before sending a message, this is a bug in Azalea",
|
||||
)?;
|
||||
"start_join_callback should not be dropped before sending a message, this is a bug in Azalea",
|
||||
);
|
||||
|
||||
let client = Client::new(entity, ecs_lock.clone());
|
||||
Ok(client)
|
||||
Client::new(entity, ecs_lock)
|
||||
}
|
||||
|
||||
/// Write a packet directly to the server.
|
||||
|
@ -340,7 +322,7 @@ impl Client {
|
|||
/// This will panic if the component doesn't exist on the client.
|
||||
///
|
||||
/// ```
|
||||
/// # use azalea_client::{Client, Hunger};
|
||||
/// # use azalea_client::{Client, local_player::Hunger};
|
||||
/// # fn example(bot: &Client) {
|
||||
/// let hunger = bot.map_component::<Hunger, _>(|h| h.food);
|
||||
/// # }
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use std::sync::Arc;
|
||||
use std::{any, sync::Arc};
|
||||
|
||||
use azalea_world::InstanceName;
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
query::QueryData,
|
||||
query::{QueryFilter, ROQueryItem},
|
||||
query::{QueryData, QueryFilter, ROQueryItem},
|
||||
world::World,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
|
@ -29,23 +29,23 @@ impl Client {
|
|||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"Our client is missing a required component {:?}",
|
||||
std::any::type_name::<D>()
|
||||
any::type_name::<D>()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Return a lightweight [`Entity`] for the entity that matches the given
|
||||
/// predicate function.
|
||||
/// Return a lightweight [`Entity`] for the first entity that matches the
|
||||
/// given predicate function that is in the same [`Instance`] as the
|
||||
/// client.
|
||||
///
|
||||
/// You can then use [`Self::entity_component`] to get components from this
|
||||
/// entity.
|
||||
///
|
||||
/// # Example
|
||||
/// Note that this will very likely change in the future.
|
||||
/// ```
|
||||
/// use azalea_client::{Client, GameProfileComponent};
|
||||
/// use bevy_ecs::query::With;
|
||||
/// use azalea_client::{Client, player::GameProfileComponent};
|
||||
/// use azalea_entity::{Position, metadata::Player};
|
||||
/// use bevy_ecs::query::With;
|
||||
///
|
||||
/// # fn example(mut bot: Client, sender_name: String) {
|
||||
/// let entity = bot.entity_by::<With<Player>, (&GameProfileComponent,)>(
|
||||
|
@ -59,11 +59,25 @@ impl Client {
|
|||
/// ```
|
||||
///
|
||||
/// [`Entity`]: bevy_ecs::entity::Entity
|
||||
/// [`Instance`]: azalea_world::Instance
|
||||
pub fn entity_by<F: QueryFilter, Q: QueryData>(
|
||||
&self,
|
||||
predicate: impl EntityPredicate<Q, F>,
|
||||
) -> Option<Entity> {
|
||||
predicate.find(self.ecs.clone())
|
||||
let instance_name = self.get_component::<InstanceName>()?;
|
||||
predicate.find(self.ecs.clone(), &instance_name)
|
||||
}
|
||||
|
||||
/// Same as [`Self::entity_by`] but returns a `Vec<Entity>` of all entities
|
||||
/// in our instance that match the predicate.
|
||||
pub fn entities_by<F: QueryFilter, Q: QueryData>(
|
||||
&self,
|
||||
predicate: impl EntityPredicate<Q, F>,
|
||||
) -> Vec<Entity> {
|
||||
let Some(instance_name) = self.get_component::<InstanceName>() else {
|
||||
return vec![];
|
||||
};
|
||||
predicate.find_all(self.ecs.clone(), &instance_name)
|
||||
}
|
||||
|
||||
/// Get a component from an entity. Note that this will return an owned type
|
||||
|
@ -77,7 +91,7 @@ impl Client {
|
|||
let components = q.get(&ecs, entity).unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"Entity is missing a required component {:?}",
|
||||
std::any::type_name::<Q>()
|
||||
any::type_name::<Q>()
|
||||
)
|
||||
});
|
||||
components.clone()
|
||||
|
@ -95,35 +109,29 @@ impl Client {
|
|||
}
|
||||
|
||||
pub trait EntityPredicate<Q: QueryData, Filter: QueryFilter> {
|
||||
fn find(&self, ecs_lock: Arc<Mutex<World>>) -> Option<Entity>;
|
||||
fn find(&self, ecs_lock: Arc<Mutex<World>>, instance_name: &InstanceName) -> Option<Entity>;
|
||||
fn find_all(&self, ecs_lock: Arc<Mutex<World>>, instance_name: &InstanceName) -> Vec<Entity>;
|
||||
}
|
||||
impl<F, Q, Filter> EntityPredicate<Q, Filter> for F
|
||||
impl<F, Q: QueryData, Filter: QueryFilter> EntityPredicate<Q, Filter> for F
|
||||
where
|
||||
F: Fn(&ROQueryItem<Q>) -> bool,
|
||||
Q: QueryData,
|
||||
Filter: QueryFilter,
|
||||
{
|
||||
fn find(&self, ecs_lock: Arc<Mutex<World>>) -> Option<Entity> {
|
||||
fn find(&self, ecs_lock: Arc<Mutex<World>>, instance_name: &InstanceName) -> Option<Entity> {
|
||||
let mut ecs = ecs_lock.lock();
|
||||
let mut query = ecs.query_filtered::<(Entity, Q), Filter>();
|
||||
query.iter(&ecs).find(|(_, q)| (self)(q)).map(|(e, _)| e)
|
||||
let mut query = ecs.query_filtered::<(Entity, &InstanceName, Q), Filter>();
|
||||
query
|
||||
.iter(&ecs)
|
||||
.find(|(_, e_instance_name, q)| *e_instance_name == instance_name && (self)(q))
|
||||
.map(|(e, _, _)| e)
|
||||
}
|
||||
|
||||
fn find_all(&self, ecs_lock: Arc<Mutex<World>>, instance_name: &InstanceName) -> Vec<Entity> {
|
||||
let mut ecs = ecs_lock.lock();
|
||||
let mut query = ecs.query_filtered::<(Entity, &InstanceName, Q), Filter>();
|
||||
query
|
||||
.iter(&ecs)
|
||||
.filter(|(_, e_instance_name, q)| *e_instance_name == instance_name && (self)(q))
|
||||
.map(|(e, _, _)| e)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
// impl<'a, F, Q1, Q2> EntityPredicate<'a, (Q1, Q2)> for F
|
||||
// where
|
||||
// F: Fn(&<Q1 as WorldQuery>::Item<'_>, &<Q2 as WorldQuery>::Item<'_>) ->
|
||||
// bool, Q1: QueryFilter,
|
||||
// Q2: QueryFilter,
|
||||
// {
|
||||
// fn find(&self, ecs: &mut Ecs) -> Option<Entity> {
|
||||
// // (self)(query)
|
||||
// let mut query = ecs.query_filtered::<(Entity, Q1, Q2), ()>();
|
||||
// let entity = query
|
||||
// .iter(ecs)
|
||||
// .find(|(_, q1, q2)| (self)(q1, q2))
|
||||
// .map(|(e, _, _)| e);
|
||||
|
||||
// entity
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
mod account;
|
||||
mod client;
|
||||
mod entity_query;
|
||||
mod local_player;
|
||||
pub mod local_player;
|
||||
pub mod ping;
|
||||
mod player;
|
||||
pub mod player;
|
||||
mod plugins;
|
||||
|
||||
#[doc(hidden)]
|
||||
|
@ -29,9 +29,7 @@ pub use client::{
|
|||
StartClientOpts, start_ecs_runner,
|
||||
};
|
||||
pub use events::Event;
|
||||
pub use local_player::{GameProfileComponent, Hunger, InstanceHolder, TabList};
|
||||
pub use movement::{
|
||||
PhysicsState, SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection,
|
||||
};
|
||||
pub use player::PlayerInfo;
|
||||
pub use plugins::*;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use std::{collections::HashMap, io, sync::Arc};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
error, io,
|
||||
sync::{Arc, PoisonError},
|
||||
};
|
||||
|
||||
use azalea_auth::game_profile::GameProfile;
|
||||
use azalea_core::game_type::GameMode;
|
||||
use azalea_protocol::packets::game::c_player_abilities::ClientboundPlayerAbilities;
|
||||
use azalea_world::{Instance, PartialInstance};
|
||||
|
@ -12,7 +15,7 @@ use tokio::sync::mpsc;
|
|||
use tracing::error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{ClientInformation, PlayerInfo, events::Event as AzaleaEvent};
|
||||
use crate::{ClientInformation, events::Event as AzaleaEvent, player::PlayerInfo};
|
||||
|
||||
/// A component that keeps strong references to our [`PartialInstance`] and
|
||||
/// [`Instance`] for local players.
|
||||
|
@ -36,14 +39,6 @@ pub struct InstanceHolder {
|
|||
pub instance: Arc<RwLock<Instance>>,
|
||||
}
|
||||
|
||||
/// A component only present in players that contains the [`GameProfile`] (which
|
||||
/// you can use to get a player's name).
|
||||
///
|
||||
/// Note that it's possible for this to be missing in a player if the server
|
||||
/// never sent the player info for them (though this is uncommon).
|
||||
#[derive(Component, Clone, Debug, Deref, DerefMut)]
|
||||
pub struct GameProfileComponent(pub GameProfile);
|
||||
|
||||
/// The gamemode of a local player. For a non-local player, you can look up the
|
||||
/// player in the [`TabList`].
|
||||
#[derive(Component, Clone, Debug, Copy)]
|
||||
|
@ -51,6 +46,14 @@ pub struct LocalGameMode {
|
|||
pub current: GameMode,
|
||||
pub previous: Option<GameMode>,
|
||||
}
|
||||
impl From<GameMode> for LocalGameMode {
|
||||
fn from(current: GameMode) -> Self {
|
||||
LocalGameMode {
|
||||
current,
|
||||
previous: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A component that contains the abilities the player has, like flying
|
||||
/// or instantly breaking blocks. This is only present on local players.
|
||||
|
@ -88,7 +91,7 @@ pub struct PermissionLevel(pub u8);
|
|||
/// tab list.
|
||||
///
|
||||
/// ```
|
||||
/// # use azalea_client::TabList;
|
||||
/// # use azalea_client::local_player::TabList;
|
||||
/// # fn example(client: &azalea_client::Client) {
|
||||
/// let tab_list = client.component::<TabList>();
|
||||
/// println!("Online players:");
|
||||
|
@ -169,13 +172,13 @@ pub enum HandlePacketError {
|
|||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
#[error(transparent)]
|
||||
Other(#[from] Box<dyn std::error::Error + Send + Sync>),
|
||||
Other(#[from] Box<dyn error::Error + Send + Sync>),
|
||||
#[error("{0}")]
|
||||
Send(#[from] mpsc::error::SendError<AzaleaEvent>),
|
||||
}
|
||||
|
||||
impl<T> From<std::sync::PoisonError<T>> for HandlePacketError {
|
||||
fn from(e: std::sync::PoisonError<T>) -> Self {
|
||||
impl<T> From<PoisonError<T>> for HandlePacketError {
|
||||
fn from(e: PoisonError<T>) -> Self {
|
||||
HandlePacketError::Poison(e.to_string())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,14 @@ use azalea_chat::FormattedText;
|
|||
use azalea_core::game_type::GameMode;
|
||||
use azalea_entity::indexing::EntityUuidIndex;
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
event::EventReader,
|
||||
system::{Commands, Res},
|
||||
};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{GameProfileComponent, packet::game::AddPlayerEvent};
|
||||
use crate::packet::game::AddPlayerEvent;
|
||||
|
||||
/// A player in the tab list.
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -29,6 +31,14 @@ pub struct PlayerInfo {
|
|||
pub display_name: Option<Box<FormattedText>>,
|
||||
}
|
||||
|
||||
/// A component only present in players that contains the [`GameProfile`] (which
|
||||
/// you can use to get a player's name).
|
||||
///
|
||||
/// Note that it's possible for this to be missing in a player if the server
|
||||
/// never sent the player info for them (though this is uncommon).
|
||||
#[derive(Component, Clone, Debug, Deref, DerefMut)]
|
||||
pub struct GameProfileComponent(pub GameProfile);
|
||||
|
||||
/// Add a [`GameProfileComponent`] when an [`AddPlayerEvent`] is received.
|
||||
/// Usually the `GameProfileComponent` will be added from the
|
||||
/// `ClientboundGamePacket::AddPlayer` handler though.
|
||||
|
|
|
@ -70,11 +70,17 @@ fn get_delay(
|
|||
auto_reconnect_delay_query: Query<&AutoReconnectDelay>,
|
||||
entity: Entity,
|
||||
) -> Option<Duration> {
|
||||
if let Ok(c) = auto_reconnect_delay_query.get(entity) {
|
||||
let delay = if let Ok(c) = auto_reconnect_delay_query.get(entity) {
|
||||
Some(c.delay)
|
||||
} else {
|
||||
auto_reconnect_delay_res.as_ref().map(|r| r.delay)
|
||||
};
|
||||
|
||||
if delay == Some(Duration::MAX) {
|
||||
// if the duration is set to max, treat that as autoreconnect being disabled
|
||||
return None;
|
||||
}
|
||||
delay
|
||||
}
|
||||
|
||||
pub fn rejoin_after_delay(
|
||||
|
|
|
@ -124,9 +124,9 @@ impl ChatPacket {
|
|||
}))
|
||||
}
|
||||
|
||||
/// Whether this message was sent with /msg (or aliases). It works by
|
||||
/// checking the translation key, so it won't work on servers that use their
|
||||
/// own whisper system.
|
||||
/// Whether this message is an incoming whisper message (i.e. someone else
|
||||
/// dm'd the bot with /msg). It works by checking the translation key, so it
|
||||
/// won't work on servers that use their own whisper system.
|
||||
pub fn is_whisper(&self) -> bool {
|
||||
match self.message() {
|
||||
FormattedText::Text(_) => false,
|
||||
|
|
|
@ -102,10 +102,10 @@ pub fn request_certs_if_needed(
|
|||
>,
|
||||
) {
|
||||
for (entity, account, only_refresh_certs_after, chat_signing_session) in query.iter_mut() {
|
||||
if let Some(only_refresh_certs_after) = only_refresh_certs_after {
|
||||
if only_refresh_certs_after.refresh_at > Instant::now() {
|
||||
continue;
|
||||
}
|
||||
if let Some(only_refresh_certs_after) = only_refresh_certs_after
|
||||
&& only_refresh_certs_after.refresh_at > Instant::now()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let certs = account.certs.lock();
|
||||
|
@ -124,20 +124,18 @@ pub fn request_certs_if_needed(
|
|||
};
|
||||
drop(certs);
|
||||
|
||||
if should_refresh {
|
||||
if let Some(access_token) = &account.access_token {
|
||||
let task_pool = IoTaskPool::get();
|
||||
if should_refresh && let Some(access_token) = &account.access_token {
|
||||
let task_pool = IoTaskPool::get();
|
||||
|
||||
let access_token = access_token.lock().clone();
|
||||
debug!("Started task to fetch certs");
|
||||
let task = task_pool.spawn(async_compat::Compat::new(async move {
|
||||
azalea_auth::certs::fetch_certificates(&access_token).await
|
||||
}));
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert(RequestCertsTask(task))
|
||||
.remove::<OnlyRefreshCertsAfter>();
|
||||
}
|
||||
let access_token = access_token.lock().clone();
|
||||
debug!("Started task to fetch certs");
|
||||
let task = task_pool.spawn(async_compat::Compat::new(async move {
|
||||
azalea_auth::certs::fetch_certificates(&access_token).await
|
||||
}));
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert(RequestCertsTask(task))
|
||||
.remove::<OnlyRefreshCertsAfter>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ use bevy_ecs::prelude::*;
|
|||
use tracing::{error, trace};
|
||||
|
||||
use crate::{
|
||||
InstanceHolder, interact::handle_start_use_item_queued, inventory::InventorySet,
|
||||
interact::handle_start_use_item_queued, inventory::InventorySet, local_player::InstanceHolder,
|
||||
packet::game::SendPacketEvent, respawn::perform_respawn,
|
||||
};
|
||||
|
||||
|
@ -84,14 +84,12 @@ pub fn handle_receive_chunk_events(
|
|||
let shared_chunk = instance.chunks.get(&pos);
|
||||
let this_client_has_chunk = partial_instance.chunks.limited_get(&pos).is_some();
|
||||
|
||||
if !this_client_has_chunk {
|
||||
if let Some(shared_chunk) = shared_chunk {
|
||||
trace!("Skipping parsing chunk {pos:?} because we already know about it");
|
||||
partial_instance
|
||||
.chunks
|
||||
.limited_set(&pos, Some(shared_chunk));
|
||||
continue;
|
||||
}
|
||||
if !this_client_has_chunk && let Some(shared_chunk) = shared_chunk {
|
||||
trace!("Skipping parsing chunk {pos:?} because we already know about it");
|
||||
partial_instance
|
||||
.chunks
|
||||
.limited_set(&pos, Some(shared_chunk));
|
||||
continue;
|
||||
}
|
||||
|
||||
let heightmaps = &event.packet.chunk_data.heightmaps;
|
||||
|
|
|
@ -169,9 +169,9 @@ pub struct RawConnection {
|
|||
///
|
||||
/// To check if we haven't disconnected from the server, use
|
||||
/// [`Self::is_alive`].
|
||||
network: Option<NetworkConnection>,
|
||||
pub(crate) network: Option<NetworkConnection>,
|
||||
pub state: ConnectionProtocol,
|
||||
is_alive: bool,
|
||||
pub(crate) is_alive: bool,
|
||||
|
||||
/// This exists for internal testing purposes and probably shouldn't be used
|
||||
/// for normal bots. It's basically a way to make our client think it
|
||||
|
|
|
@ -2,13 +2,17 @@
|
|||
|
||||
use azalea_chat::FormattedText;
|
||||
use azalea_entity::{EntityBundle, InLoadedChunk, LocalEntity, metadata::PlayerMetadataBundle};
|
||||
use azalea_world::MinecraftEntityId;
|
||||
use bevy_app::{App, Plugin, PostUpdate};
|
||||
use bevy_ecs::prelude::*;
|
||||
use derive_more::Deref;
|
||||
use tracing::info;
|
||||
|
||||
use super::login::IsAuthenticated;
|
||||
use crate::{InstanceHolder, chat_signing, client::JoinedClientBundle, connection::RawConnection};
|
||||
use crate::{
|
||||
chat_signing, client::JoinedClientBundle, connection::RawConnection,
|
||||
local_player::InstanceHolder,
|
||||
};
|
||||
|
||||
pub struct DisconnectPlugin;
|
||||
impl Plugin for DisconnectPlugin {
|
||||
|
@ -53,6 +57,7 @@ pub struct DisconnectEvent {
|
|||
pub struct RemoveOnDisconnectBundle {
|
||||
pub joined_client: JoinedClientBundle,
|
||||
pub entity: EntityBundle,
|
||||
pub minecraft_entity_id: MinecraftEntityId,
|
||||
pub instance_holder: InstanceHolder,
|
||||
pub player_metadata: PlayerMetadataBundle,
|
||||
pub in_loaded_chunk: InLoadedChunk,
|
||||
|
|
|
@ -14,12 +14,12 @@ use derive_more::{Deref, DerefMut};
|
|||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::{
|
||||
PlayerInfo,
|
||||
chat::{ChatPacket, ChatReceivedEvent},
|
||||
disconnect::DisconnectEvent,
|
||||
packet::game::{
|
||||
AddPlayerEvent, DeathEvent, KeepAliveEvent, RemovePlayerEvent, UpdatePlayerEvent,
|
||||
},
|
||||
player::PlayerInfo,
|
||||
};
|
||||
|
||||
// (for contributors):
|
||||
|
|
|
@ -24,7 +24,7 @@ use bevy_ecs::prelude::*;
|
|||
use derive_more::{Deref, DerefMut};
|
||||
use tracing::warn;
|
||||
|
||||
use super::mining::{Mining, MiningSet};
|
||||
use super::mining::Mining;
|
||||
use crate::{
|
||||
Client,
|
||||
attack::handle_attack_event,
|
||||
|
@ -58,12 +58,7 @@ impl Plugin for InteractPlugin {
|
|||
.after(MoveEventsSet),
|
||||
),
|
||||
)
|
||||
.add_systems(
|
||||
GameTick,
|
||||
handle_start_use_item_queued
|
||||
.after(MiningSet)
|
||||
.before(PhysicsSet),
|
||||
)
|
||||
.add_systems(GameTick, handle_start_use_item_queued.before(PhysicsSet))
|
||||
.add_observer(handle_swing_arm_trigger);
|
||||
}
|
||||
}
|
||||
|
@ -131,7 +126,7 @@ pub struct HitResultComponent(HitResult);
|
|||
pub struct StartUseItemEvent {
|
||||
pub entity: Entity,
|
||||
pub hand: InteractionHand,
|
||||
/// See [`QueuedStartUseItem::force_block`].
|
||||
/// See [`StartUseItemQueued::force_block`].
|
||||
pub force_block: Option<BlockPos>,
|
||||
}
|
||||
pub fn handle_start_use_item_event(
|
||||
|
|
|
@ -170,17 +170,17 @@ impl Inventory {
|
|||
}
|
||||
if let QuickCraftStatus::Add { slot } = quick_craft.status {
|
||||
let slot_item = self.menu().slot(slot as usize);
|
||||
if let Some(slot_item) = slot_item {
|
||||
if let ItemStack::Present(carried) = &self.carried {
|
||||
// minecraft also checks slot.may_place(carried) and
|
||||
// menu.can_drag_to(slot)
|
||||
// but they always return true so they're not relevant for us
|
||||
if can_item_quick_replace(slot_item, &self.carried, true)
|
||||
&& (self.quick_craft_kind == QuickCraftKind::Right
|
||||
|| carried.count as usize > self.quick_craft_slots.len())
|
||||
{
|
||||
self.quick_craft_slots.insert(slot);
|
||||
}
|
||||
if let Some(slot_item) = slot_item
|
||||
&& let ItemStack::Present(carried) = &self.carried
|
||||
{
|
||||
// minecraft also checks slot.may_place(carried) and
|
||||
// menu.can_drag_to(slot)
|
||||
// but they always return true so they're not relevant for us
|
||||
if can_item_quick_replace(slot_item, &self.carried, true)
|
||||
&& (self.quick_craft_kind == QuickCraftKind::Right
|
||||
|| carried.count as usize > self.quick_craft_slots.len())
|
||||
{
|
||||
self.quick_craft_slots.insert(slot);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
@ -468,26 +468,23 @@ impl Inventory {
|
|||
for i in iterator {
|
||||
if target_slot_item.count < target_slot_item.kind.max_stack_size() {
|
||||
let checking_slot = self.menu().slot(i).unwrap();
|
||||
if let ItemStack::Present(checking_item) = checking_slot {
|
||||
if can_item_quick_replace(checking_slot, &target_slot, true)
|
||||
&& self.menu().may_pickup(i)
|
||||
&& (round != 0
|
||||
|| checking_item.count
|
||||
!= checking_item.kind.max_stack_size())
|
||||
{
|
||||
// get the checking_slot and checking_item again but mutable
|
||||
let checking_slot = self.menu_mut().slot_mut(i).unwrap();
|
||||
if let ItemStack::Present(checking_item) = checking_slot
|
||||
&& can_item_quick_replace(checking_slot, &target_slot, true)
|
||||
&& self.menu().may_pickup(i)
|
||||
&& (round != 0
|
||||
|| checking_item.count != checking_item.kind.max_stack_size())
|
||||
{
|
||||
// get the checking_slot and checking_item again but mutable
|
||||
let checking_slot = self.menu_mut().slot_mut(i).unwrap();
|
||||
|
||||
let taken_item =
|
||||
checking_slot.split(checking_slot.count() as u32);
|
||||
let taken_item = checking_slot.split(checking_slot.count() as u32);
|
||||
|
||||
// now extend the carried item
|
||||
let target_slot = &mut self.carried;
|
||||
let ItemStack::Present(target_slot_item) = target_slot else {
|
||||
unreachable!("target slot is not empty but is not present");
|
||||
};
|
||||
target_slot_item.count += taken_item.count();
|
||||
}
|
||||
// now extend the carried item
|
||||
let target_slot = &mut self.carried;
|
||||
let ItemStack::Present(target_slot_item) = target_slot else {
|
||||
unreachable!("target slot is not empty but is not present");
|
||||
};
|
||||
target_slot_item.count += taken_item.count();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{io, net::SocketAddr, sync::Arc};
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
|
||||
use azalea_entity::{LocalEntity, indexing::EntityUuidIndex};
|
||||
use azalea_protocol::{
|
||||
|
@ -20,7 +20,7 @@ use tracing::{debug, warn};
|
|||
|
||||
use super::events::LocalPlayerEvents;
|
||||
use crate::{
|
||||
Account, JoinError, LocalPlayerBundle,
|
||||
Account, LocalPlayerBundle,
|
||||
connection::RawConnection,
|
||||
packet::login::{InLoginState, SendLoginPacketEvent},
|
||||
};
|
||||
|
@ -36,7 +36,6 @@ impl Plugin for JoinPlugin {
|
|||
(
|
||||
handle_start_join_server_event.before(super::login::poll_auth_task),
|
||||
poll_create_connection_task,
|
||||
handle_connection_failed_events,
|
||||
)
|
||||
.chain(),
|
||||
);
|
||||
|
@ -53,7 +52,8 @@ pub struct StartJoinServerEvent {
|
|||
pub connect_opts: ConnectOpts,
|
||||
pub event_sender: Option<mpsc::UnboundedSender<crate::Event>>,
|
||||
|
||||
pub start_join_callback_tx: Option<StartJoinCallback>,
|
||||
// this is mpsc instead of oneshot so it can be cloned (since it's sent in an event)
|
||||
pub start_join_callback_tx: Option<mpsc::UnboundedSender<Entity>>,
|
||||
}
|
||||
|
||||
/// Options for how the connection to the server will be made. These are
|
||||
|
@ -79,11 +79,6 @@ pub struct ConnectionFailedEvent {
|
|||
pub error: ConnectionError,
|
||||
}
|
||||
|
||||
// this is mpsc instead of oneshot so it can be cloned (since it's sent in an
|
||||
// event)
|
||||
#[derive(Component, Debug, Clone)]
|
||||
pub struct StartJoinCallback(pub mpsc::UnboundedSender<Result<Entity, JoinError>>);
|
||||
|
||||
pub fn handle_start_join_server_event(
|
||||
mut commands: Commands,
|
||||
mut events: EventReader<StartJoinServerEvent>,
|
||||
|
@ -96,20 +91,20 @@ pub fn handle_start_join_server_event(
|
|||
debug!("Reusing entity {entity:?} for client");
|
||||
|
||||
// check if it's already connected
|
||||
if let Ok(conn) = connection_query.get(entity) {
|
||||
if conn.is_alive() {
|
||||
if let Some(start_join_callback_tx) = &event.start_join_callback_tx {
|
||||
warn!(
|
||||
"Received StartJoinServerEvent for {entity:?} but it's already connected. Ignoring the event but replying with Ok."
|
||||
);
|
||||
let _ = start_join_callback_tx.0.send(Ok(entity));
|
||||
} else {
|
||||
warn!(
|
||||
"Received StartJoinServerEvent for {entity:?} but it's already connected. Ignoring the event."
|
||||
);
|
||||
}
|
||||
return;
|
||||
if let Ok(conn) = connection_query.get(entity)
|
||||
&& conn.is_alive()
|
||||
{
|
||||
if let Some(start_join_callback_tx) = &event.start_join_callback_tx {
|
||||
warn!(
|
||||
"Received StartJoinServerEvent for {entity:?} but it's already connected. Ignoring the event but replying with Ok."
|
||||
);
|
||||
let _ = start_join_callback_tx.send(entity);
|
||||
} else {
|
||||
warn!(
|
||||
"Received StartJoinServerEvent for {entity:?} but it's already connected. Ignoring the event."
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
entity
|
||||
|
@ -121,6 +116,10 @@ pub fn handle_start_join_server_event(
|
|||
entity
|
||||
};
|
||||
|
||||
if let Some(start_join_callback) = &event.start_join_callback_tx {
|
||||
let _ = start_join_callback.send(entity);
|
||||
}
|
||||
|
||||
let mut entity_mut = commands.entity(entity);
|
||||
|
||||
entity_mut.insert((
|
||||
|
@ -141,9 +140,6 @@ pub fn handle_start_join_server_event(
|
|||
// handle receiving packets
|
||||
entity_mut.insert(LocalPlayerEvents(event_sender.clone()));
|
||||
}
|
||||
if let Some(start_join_callback) = &event.start_join_callback_tx {
|
||||
entity_mut.insert(start_join_callback.clone());
|
||||
}
|
||||
|
||||
let task_pool = IoTaskPool::get();
|
||||
let connect_opts = event.connect_opts.clone();
|
||||
|
@ -184,15 +180,10 @@ pub struct CreateConnectionTask(pub Task<Result<LoginConn, ConnectionError>>);
|
|||
|
||||
pub fn poll_create_connection_task(
|
||||
mut commands: Commands,
|
||||
mut query: Query<(
|
||||
Entity,
|
||||
&mut CreateConnectionTask,
|
||||
&Account,
|
||||
Option<&StartJoinCallback>,
|
||||
)>,
|
||||
mut query: Query<(Entity, &mut CreateConnectionTask, &Account)>,
|
||||
mut connection_failed_events: EventWriter<ConnectionFailedEvent>,
|
||||
) {
|
||||
for (entity, mut task, account, mut start_join_callback) in query.iter_mut() {
|
||||
for (entity, mut task, account) in query.iter_mut() {
|
||||
if let Some(poll_res) = future::block_on(future::poll_once(&mut task.0)) {
|
||||
let mut entity_mut = commands.entity(entity);
|
||||
entity_mut.remove::<CreateConnectionTask>();
|
||||
|
@ -238,29 +229,6 @@ pub fn poll_create_connection_task(
|
|||
profile_id: account.uuid_or_offline(),
|
||||
},
|
||||
));
|
||||
|
||||
if let Some(cb) = start_join_callback.take() {
|
||||
let _ = cb.0.send(Ok(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_connection_failed_events(
|
||||
mut events: EventReader<ConnectionFailedEvent>,
|
||||
query: Query<&StartJoinCallback>,
|
||||
) {
|
||||
for event in events.read() {
|
||||
let Ok(start_join_callback) = query.get(event.entity) else {
|
||||
// the StartJoinCallback isn't required to be present, so this is fine
|
||||
continue;
|
||||
};
|
||||
|
||||
// io::Error isn't clonable, so we create a new one based on the `kind` and
|
||||
// `to_string`,
|
||||
let ConnectionError::Io(err) = &event.error;
|
||||
let cloned_err = ConnectionError::Io(io::Error::new(err.kind(), err.to_string()));
|
||||
|
||||
let _ = start_join_callback.0.send(Err(cloned_err.into()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,14 @@ use azalea_protocol::packets::login::{
|
|||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_tasks::{IoTaskPool, Task, futures_lite::future};
|
||||
use thiserror::Error;
|
||||
use tracing::{debug, error, trace};
|
||||
|
||||
use super::{
|
||||
connection::RawConnection,
|
||||
packet::login::{ReceiveCustomQueryEvent, ReceiveHelloEvent, SendLoginPacketEvent},
|
||||
};
|
||||
use crate::{Account, JoinError};
|
||||
use crate::Account;
|
||||
|
||||
/// Some systems that run during the `login` state.
|
||||
pub struct LoginPlugin;
|
||||
|
@ -73,14 +74,24 @@ pub fn poll_auth_task(
|
|||
type PrivateKey = [u8; 16];
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct AuthTask(Task<Result<(ServerboundKey, PrivateKey), JoinError>>);
|
||||
pub struct AuthTask(Task<Result<(ServerboundKey, PrivateKey), AuthWithAccountError>>);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum AuthWithAccountError {
|
||||
#[error("Failed to encrypt the challenge from the server for {0:?}")]
|
||||
Encryption(ClientboundHello),
|
||||
#[error("{0}")]
|
||||
SessionServer(#[from] ClientSessionServerError),
|
||||
#[error("Couldn't refresh access token: {0}")]
|
||||
Auth(#[from] azalea_auth::AuthError),
|
||||
}
|
||||
|
||||
pub async fn auth_with_account(
|
||||
account: Account,
|
||||
packet: ClientboundHello,
|
||||
) -> Result<(ServerboundKey, PrivateKey), JoinError> {
|
||||
) -> Result<(ServerboundKey, PrivateKey), AuthWithAccountError> {
|
||||
let Ok(encrypt_res) = azalea_crypto::encrypt(&packet.public_key, &packet.challenge) else {
|
||||
return Err(JoinError::EncryptionError(packet));
|
||||
return Err(AuthWithAccountError::Encryption(packet));
|
||||
};
|
||||
let key_packet = ServerboundKey {
|
||||
key_bytes: encrypt_res.encrypted_public_key,
|
||||
|
|
|
@ -8,15 +8,16 @@ use azalea_world::{InstanceContainer, InstanceName};
|
|||
use bevy_app::{App, Plugin, Update};
|
||||
use bevy_ecs::prelude::*;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use tracing::{info, trace};
|
||||
|
||||
use crate::{
|
||||
Client, InstanceHolder,
|
||||
Client,
|
||||
interact::{
|
||||
CurrentSequenceNumber, HitResultComponent, SwingArmEvent, can_use_game_master_blocks,
|
||||
check_is_interaction_restricted,
|
||||
},
|
||||
inventory::{Inventory, InventorySet},
|
||||
local_player::{LocalGameMode, PermissionLevel, PlayerAbilities},
|
||||
local_player::{InstanceHolder, LocalGameMode, PermissionLevel, PlayerAbilities},
|
||||
movement::MoveEventsSet,
|
||||
packet::game::SendPacketEvent,
|
||||
};
|
||||
|
@ -39,14 +40,15 @@ impl Plugin for MiningPlugin {
|
|||
handle_mining_queued,
|
||||
)
|
||||
.chain()
|
||||
.before(PhysicsSet)
|
||||
.after(PhysicsSet)
|
||||
.after(super::movement::send_position)
|
||||
.after(super::attack::handle_attack_queued)
|
||||
.in_set(MiningSet),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
handle_start_mining_block_event,
|
||||
handle_finish_mining_block_event,
|
||||
handle_stop_mining_block_event,
|
||||
)
|
||||
.chain()
|
||||
|
@ -59,7 +61,8 @@ impl Plugin for MiningPlugin {
|
|||
.after(crate::attack::handle_attack_event)
|
||||
.after(crate::interact::handle_start_use_item_queued)
|
||||
.before(crate::interact::handle_swing_arm_event),
|
||||
);
|
||||
)
|
||||
.add_observer(handle_finish_mining_block_observer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,17 +151,19 @@ fn handle_auto_mine(
|
|||
|
||||
/// Information about the block we're currently mining. This is only present if
|
||||
/// we're currently mining a block.
|
||||
#[derive(Component)]
|
||||
#[derive(Component, Debug, Clone)]
|
||||
pub struct Mining {
|
||||
pub pos: BlockPos,
|
||||
pub dir: Direction,
|
||||
/// See [`MiningQueued::force`].
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
/// Start mining the block at the given position.
|
||||
///
|
||||
/// If we're looking at the block then the correct direction will be used,
|
||||
/// otherwise it'll be [`Direction::Down`].
|
||||
#[derive(Event)]
|
||||
#[derive(Event, Debug)]
|
||||
pub struct StartMiningBlockEvent {
|
||||
pub entity: Entity,
|
||||
pub position: BlockPos,
|
||||
|
@ -169,33 +174,37 @@ fn handle_start_mining_block_event(
|
|||
mut query: Query<&HitResultComponent>,
|
||||
) {
|
||||
for event in events.read() {
|
||||
trace!("{event:?}");
|
||||
let hit_result = query.get_mut(event.entity).unwrap();
|
||||
let direction = if let Some(block_hit_result) = hit_result.as_block_hit_result_if_not_miss()
|
||||
let (direction, force) = if let Some(block_hit_result) =
|
||||
hit_result.as_block_hit_result_if_not_miss()
|
||||
&& block_hit_result.block_pos == event.position
|
||||
{
|
||||
// we're looking at the block
|
||||
block_hit_result.direction
|
||||
(block_hit_result.direction, false)
|
||||
} else {
|
||||
// we're not looking at the block, arbitrary direction
|
||||
Direction::Down
|
||||
(Direction::Down, true)
|
||||
};
|
||||
commands.entity(event.entity).insert(MiningQueued {
|
||||
position: event.position,
|
||||
direction,
|
||||
force,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Present on entities when they're going to start mining a block next tick.
|
||||
#[derive(Component)]
|
||||
#[derive(Component, Debug, Clone)]
|
||||
pub struct MiningQueued {
|
||||
pub position: BlockPos,
|
||||
pub direction: Direction,
|
||||
/// Whether we should mine the block regardless of whether it's reachable.
|
||||
pub force: bool,
|
||||
}
|
||||
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
||||
fn handle_mining_queued(
|
||||
mut commands: Commands,
|
||||
mut finish_mining_events: EventWriter<FinishMiningBlockEvent>,
|
||||
mut attack_block_events: EventWriter<AttackBlockEvent>,
|
||||
mut mine_block_progress_events: EventWriter<MineBlockProgressEvent>,
|
||||
query: Query<(
|
||||
|
@ -232,6 +241,7 @@ fn handle_mining_queued(
|
|||
mut current_mining_pos,
|
||||
) in query
|
||||
{
|
||||
info!("mining_queued: {mining_queued:?}");
|
||||
commands.entity(entity).remove::<MiningQueued>();
|
||||
|
||||
let instance = instance_holder.instance.read();
|
||||
|
@ -247,10 +257,12 @@ fn handle_mining_queued(
|
|||
// is outside of the worldborder
|
||||
|
||||
if game_mode.current == GameMode::Creative {
|
||||
finish_mining_events.write(FinishMiningBlockEvent {
|
||||
commands.trigger_targets(
|
||||
FinishMiningBlockEvent {
|
||||
position: mining_queued.position,
|
||||
},
|
||||
entity,
|
||||
position: mining_queued.position,
|
||||
});
|
||||
);
|
||||
**mine_delay = 5;
|
||||
} else if mining.is_none()
|
||||
|| !is_same_mining_target(
|
||||
|
@ -303,15 +315,20 @@ fn handle_mining_queued(
|
|||
) >= 1.
|
||||
{
|
||||
// block was broken instantly
|
||||
finish_mining_events.write(FinishMiningBlockEvent {
|
||||
commands.trigger_targets(
|
||||
FinishMiningBlockEvent {
|
||||
position: mining_queued.position,
|
||||
},
|
||||
entity,
|
||||
position: mining_queued.position,
|
||||
});
|
||||
);
|
||||
} else {
|
||||
commands.entity(entity).insert(Mining {
|
||||
let mining = Mining {
|
||||
pos: mining_queued.position,
|
||||
dir: mining_queued.direction,
|
||||
});
|
||||
force: mining_queued.force,
|
||||
};
|
||||
trace!("inserting mining component {mining:?} for entity {entity:?}");
|
||||
commands.entity(entity).insert(mining);
|
||||
**current_mining_pos = Some(mining_queued.position);
|
||||
**current_mining_item = held_item;
|
||||
**mine_progress = 0.;
|
||||
|
@ -332,6 +349,7 @@ fn handle_mining_queued(
|
|||
sequence: sequence_number.get_and_increment(),
|
||||
},
|
||||
));
|
||||
// vanilla really does send two swing arm packets
|
||||
commands.trigger(SwingArmEvent { entity });
|
||||
commands.trigger(SwingArmEvent { entity });
|
||||
}
|
||||
|
@ -366,7 +384,7 @@ fn is_same_mining_target(
|
|||
}
|
||||
|
||||
/// A component bundle for players that can mine blocks.
|
||||
#[derive(Bundle, Default)]
|
||||
#[derive(Bundle, Default, Clone)]
|
||||
pub struct MineBundle {
|
||||
pub delay: MineDelay,
|
||||
pub progress: MineProgress,
|
||||
|
@ -376,12 +394,12 @@ pub struct MineBundle {
|
|||
}
|
||||
|
||||
/// A component that counts down until we start mining the next block.
|
||||
#[derive(Component, Debug, Default, Deref, DerefMut)]
|
||||
#[derive(Component, Debug, Default, Deref, DerefMut, Clone)]
|
||||
pub struct MineDelay(pub u32);
|
||||
|
||||
/// A component that stores the progress of the current mining operation. This
|
||||
/// is a value between 0 and 1.
|
||||
#[derive(Component, Debug, Default, Deref, DerefMut)]
|
||||
#[derive(Component, Debug, Default, Deref, DerefMut, Clone)]
|
||||
pub struct MineProgress(pub f32);
|
||||
|
||||
impl MineProgress {
|
||||
|
@ -409,15 +427,14 @@ pub struct MineBlockPos(pub Option<BlockPos>);
|
|||
#[derive(Component, Clone, Debug, Default, Deref, DerefMut)]
|
||||
pub struct MineItem(pub ItemStack);
|
||||
|
||||
/// Sent when we completed mining a block.
|
||||
/// A trigger that's sent when we completed mining a block.
|
||||
#[derive(Event)]
|
||||
pub struct FinishMiningBlockEvent {
|
||||
pub entity: Entity,
|
||||
pub position: BlockPos,
|
||||
}
|
||||
|
||||
pub fn handle_finish_mining_block_event(
|
||||
mut events: EventReader<FinishMiningBlockEvent>,
|
||||
pub fn handle_finish_mining_block_observer(
|
||||
trigger: Trigger<FinishMiningBlockEvent>,
|
||||
mut query: Query<(
|
||||
&InstanceName,
|
||||
&LocalGameMode,
|
||||
|
@ -428,53 +445,48 @@ pub fn handle_finish_mining_block_event(
|
|||
)>,
|
||||
instances: Res<InstanceContainer>,
|
||||
) {
|
||||
for event in events.read() {
|
||||
let (instance_name, game_mode, inventory, abilities, permission_level, _sequence_number) =
|
||||
query.get_mut(event.entity).unwrap();
|
||||
let instance_lock = instances.get(instance_name).unwrap();
|
||||
let instance = instance_lock.read();
|
||||
if check_is_interaction_restricted(
|
||||
&instance,
|
||||
&event.position,
|
||||
&game_mode.current,
|
||||
inventory,
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let event = trigger.event();
|
||||
|
||||
if game_mode.current == GameMode::Creative {
|
||||
let held_item = inventory.held_item().kind();
|
||||
if matches!(
|
||||
held_item,
|
||||
azalea_registry::Item::Trident | azalea_registry::Item::DebugStick
|
||||
) || azalea_registry::tags::items::SWORDS.contains(&held_item)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let Some(block_state) = instance.get_block_state(&event.position) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let registry_block = Box::<dyn Block>::from(block_state).as_registry_block();
|
||||
if !can_use_game_master_blocks(abilities, permission_level)
|
||||
&& matches!(
|
||||
registry_block,
|
||||
azalea_registry::Block::CommandBlock | azalea_registry::Block::StructureBlock
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if block_state == BlockState::AIR {
|
||||
continue;
|
||||
}
|
||||
|
||||
// when we break a waterlogged block we want to keep the water there
|
||||
let fluid_state = FluidState::from(block_state);
|
||||
let block_state_for_fluid = BlockState::from(fluid_state);
|
||||
instance.set_block_state(&event.position, block_state_for_fluid);
|
||||
let (instance_name, game_mode, inventory, abilities, permission_level, _sequence_number) =
|
||||
query.get_mut(trigger.target()).unwrap();
|
||||
let instance_lock = instances.get(instance_name).unwrap();
|
||||
let instance = instance_lock.read();
|
||||
if check_is_interaction_restricted(&instance, &event.position, &game_mode.current, inventory) {
|
||||
return;
|
||||
}
|
||||
|
||||
if game_mode.current == GameMode::Creative {
|
||||
let held_item = inventory.held_item().kind();
|
||||
if matches!(
|
||||
held_item,
|
||||
azalea_registry::Item::Trident | azalea_registry::Item::DebugStick
|
||||
) || azalea_registry::tags::items::SWORDS.contains(&held_item)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let Some(block_state) = instance.get_block_state(&event.position) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let registry_block = Box::<dyn Block>::from(block_state).as_registry_block();
|
||||
if !can_use_game_master_blocks(abilities, permission_level)
|
||||
&& matches!(
|
||||
registry_block,
|
||||
azalea_registry::Block::CommandBlock | azalea_registry::Block::StructureBlock
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if block_state == BlockState::AIR {
|
||||
return;
|
||||
}
|
||||
|
||||
// when we break a waterlogged block we want to keep the water there
|
||||
let fluid_state = FluidState::from(block_state);
|
||||
let block_state_for_fluid = BlockState::from(fluid_state);
|
||||
instance.set_block_state(&event.position, block_state_for_fluid);
|
||||
}
|
||||
|
||||
/// Abort mining a block.
|
||||
|
@ -531,7 +543,6 @@ pub fn continue_mining_block(
|
|||
)>,
|
||||
mut commands: Commands,
|
||||
mut mine_block_progress_events: EventWriter<MineBlockProgressEvent>,
|
||||
mut finish_mining_events: EventWriter<FinishMiningBlockEvent>,
|
||||
instances: Res<InstanceContainer>,
|
||||
) {
|
||||
for (
|
||||
|
@ -558,10 +569,12 @@ pub fn continue_mining_block(
|
|||
if game_mode.current == GameMode::Creative {
|
||||
// TODO: worldborder check
|
||||
**mine_delay = 5;
|
||||
finish_mining_events.write(FinishMiningBlockEvent {
|
||||
commands.trigger_targets(
|
||||
FinishMiningBlockEvent {
|
||||
position: mining.pos,
|
||||
},
|
||||
entity,
|
||||
position: mining.pos,
|
||||
});
|
||||
);
|
||||
commands.trigger(SendPacketEvent::new(
|
||||
entity,
|
||||
ServerboundPlayerAction {
|
||||
|
@ -572,18 +585,20 @@ pub fn continue_mining_block(
|
|||
},
|
||||
));
|
||||
commands.trigger(SwingArmEvent { entity });
|
||||
} else if is_same_mining_target(
|
||||
mining.pos,
|
||||
inventory,
|
||||
current_mining_pos,
|
||||
current_mining_item,
|
||||
) {
|
||||
println!("continue mining block at {:?}", mining.pos);
|
||||
} else if mining.force
|
||||
|| is_same_mining_target(
|
||||
mining.pos,
|
||||
inventory,
|
||||
current_mining_pos,
|
||||
current_mining_item,
|
||||
)
|
||||
{
|
||||
trace!("continue mining block at {:?}", mining.pos);
|
||||
let instance_lock = instances.get(instance_name).unwrap();
|
||||
let instance = instance_lock.read();
|
||||
let target_block_state = instance.get_block_state(&mining.pos).unwrap_or_default();
|
||||
|
||||
println!("target_block_state: {target_block_state:?}");
|
||||
trace!("target_block_state: {target_block_state:?}");
|
||||
|
||||
if target_block_state.is_air() {
|
||||
commands.entity(entity).remove::<Mining>();
|
||||
|
@ -604,12 +619,16 @@ pub fn continue_mining_block(
|
|||
**mine_ticks += 1.;
|
||||
|
||||
if **mine_progress >= 1. {
|
||||
commands.entity(entity).remove::<Mining>();
|
||||
println!("finished mining block at {:?}", mining.pos);
|
||||
finish_mining_events.write(FinishMiningBlockEvent {
|
||||
// MiningQueued is removed in case we were doing an infinite loop that
|
||||
// repeatedly inserts MiningQueued
|
||||
commands.entity(entity).remove::<(Mining, MiningQueued)>();
|
||||
trace!("finished mining block at {:?}", mining.pos);
|
||||
commands.trigger_targets(
|
||||
FinishMiningBlockEvent {
|
||||
position: mining.pos,
|
||||
},
|
||||
entity,
|
||||
position: mining.pos,
|
||||
});
|
||||
);
|
||||
commands.trigger(SendPacketEvent::new(
|
||||
entity,
|
||||
ServerboundPlayerAction {
|
||||
|
@ -631,10 +650,11 @@ pub fn continue_mining_block(
|
|||
});
|
||||
commands.trigger(SwingArmEvent { entity });
|
||||
} else {
|
||||
println!("switching mining target to {:?}", mining.pos);
|
||||
trace!("switching mining target to {:?}", mining.pos);
|
||||
commands.entity(entity).insert(MiningQueued {
|
||||
position: mining.pos,
|
||||
direction: mining.dir,
|
||||
force: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use std::backtrace::Backtrace;
|
||||
use std::{backtrace::Backtrace, io};
|
||||
|
||||
use azalea_core::position::Vec3;
|
||||
use azalea_core::tick::GameTick;
|
||||
use azalea_entity::{Attributes, Jumping, metadata::Sprinting};
|
||||
use azalea_entity::{InLoadedChunk, LastSentPosition, LookDirection, Physics, Position};
|
||||
use azalea_core::{position::Vec3, tick::GameTick};
|
||||
use azalea_entity::{
|
||||
Attributes, InLoadedChunk, Jumping, LastSentPosition, LookDirection, Physics, Position,
|
||||
metadata::Sprinting,
|
||||
};
|
||||
use azalea_physics::{PhysicsSet, ai_step};
|
||||
use azalea_protocol::packets::game::{ServerboundPlayerCommand, ServerboundPlayerInput};
|
||||
use azalea_protocol::packets::{
|
||||
Packet,
|
||||
game::{
|
||||
ServerboundPlayerCommand, ServerboundPlayerInput,
|
||||
s_move_player_pos::ServerboundMovePlayerPos,
|
||||
s_move_player_pos_rot::ServerboundMovePlayerPosRot,
|
||||
s_move_player_rot::ServerboundMovePlayerRot,
|
||||
|
@ -20,15 +21,14 @@ use bevy_app::{App, Plugin, Update};
|
|||
use bevy_ecs::prelude::*;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::client::Client;
|
||||
use crate::packet::game::SendPacketEvent;
|
||||
use crate::{client::Client, packet::game::SendPacketEvent};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MovePlayerError {
|
||||
#[error("Player is not in world")]
|
||||
PlayerNotInWorld(Backtrace),
|
||||
#[error("{0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
Io(#[from] io::Error),
|
||||
}
|
||||
|
||||
impl From<MoveEntityError> for MovePlayerError {
|
||||
|
|
|
@ -3,21 +3,23 @@ mod events;
|
|||
use std::io::Cursor;
|
||||
|
||||
use azalea_entity::LocalEntity;
|
||||
use azalea_protocol::packets::ConnectionProtocol;
|
||||
use azalea_protocol::packets::config::*;
|
||||
use azalea_protocol::read::ReadPacketError;
|
||||
use azalea_protocol::read::deserialize_packet;
|
||||
use azalea_protocol::{
|
||||
packets::{ConnectionProtocol, config::*},
|
||||
read::{ReadPacketError, deserialize_packet},
|
||||
};
|
||||
use bevy_ecs::prelude::*;
|
||||
pub use events::*;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use super::as_system;
|
||||
use crate::client::InConfigState;
|
||||
use crate::connection::RawConnection;
|
||||
use crate::disconnect::DisconnectEvent;
|
||||
use crate::packet::game::KeepAliveEvent;
|
||||
use crate::packet::game::ResourcePackEvent;
|
||||
use crate::{InstanceHolder, declare_packet_handlers};
|
||||
use crate::{
|
||||
client::InConfigState,
|
||||
connection::RawConnection,
|
||||
declare_packet_handlers,
|
||||
disconnect::DisconnectEvent,
|
||||
local_player::InstanceHolder,
|
||||
packet::game::{KeepAliveEvent, ResourcePackEvent},
|
||||
};
|
||||
|
||||
pub fn process_raw_packet(
|
||||
ecs: &mut World,
|
||||
|
|
|
@ -12,7 +12,7 @@ use parking_lot::RwLock;
|
|||
use tracing::{error, trace};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{PlayerInfo, client::InGameState, connection::RawConnection};
|
||||
use crate::{client::InGameState, connection::RawConnection, player::PlayerInfo};
|
||||
|
||||
/// An event that's sent when we receive a packet.
|
||||
/// ```
|
||||
|
@ -21,10 +21,7 @@ use crate::{PlayerInfo, client::InGameState, connection::RawConnection};
|
|||
/// # use bevy_ecs::event::EventReader;
|
||||
///
|
||||
/// fn handle_packets(mut events: EventReader<ReceiveGamePacketEvent>) {
|
||||
/// for ReceiveGamePacketEvent {
|
||||
/// entity,
|
||||
/// packet,
|
||||
/// } in events.read() {
|
||||
/// for ReceiveGamePacketEvent { entity, packet } in events.read() {
|
||||
/// match packet.as_ref() {
|
||||
/// ClientboundGamePacket::LevelParticles(p) => {
|
||||
/// // ...
|
||||
|
|
|
@ -20,7 +20,7 @@ pub use events::*;
|
|||
use tracing::{debug, error, trace, warn};
|
||||
|
||||
use crate::{
|
||||
ClientInformation, PlayerInfo,
|
||||
ClientInformation,
|
||||
chat::{ChatPacket, ChatReceivedEvent},
|
||||
chunks,
|
||||
connection::RawConnection,
|
||||
|
@ -29,11 +29,10 @@ use crate::{
|
|||
inventory::{
|
||||
ClientSideCloseContainerEvent, Inventory, MenuOpenedEvent, SetContainerContentEvent,
|
||||
},
|
||||
local_player::{
|
||||
GameProfileComponent, Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, TabList,
|
||||
},
|
||||
local_player::{Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, TabList},
|
||||
movement::{KnockbackEvent, KnockbackType},
|
||||
packet::as_system,
|
||||
player::{GameProfileComponent, PlayerInfo},
|
||||
};
|
||||
|
||||
pub fn process_packet(ecs: &mut World, player: Entity, packet: &ClientboundGamePacket) {
|
||||
|
@ -1236,7 +1235,7 @@ impl GamePacketHandler<'_> {
|
|||
// TODO: handle ContainerSetData packet
|
||||
// this is used for various things like the furnace progress
|
||||
// bar
|
||||
// see https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol#Set_Container_Property
|
||||
// see https://minecraft.wiki/w/Java_Edition_protocol/Packets#Set_Container_Property
|
||||
|
||||
// as_system::<Query<&mut Inventory>>(self.ecs, |mut query| {
|
||||
// let inventory = query.get_mut(self.player).unwrap();
|
||||
|
@ -1423,8 +1422,12 @@ impl GamePacketHandler<'_> {
|
|||
)>(
|
||||
self.ecs,
|
||||
|(mut commands, mut query, mut events, mut instance_container, mut loaded_by_query)| {
|
||||
let (mut instance_holder, game_profile, client_information, instance_name) =
|
||||
query.get_mut(self.player).unwrap();
|
||||
let Ok((mut instance_holder, game_profile, client_information, instance_name)) =
|
||||
query.get_mut(self.player)
|
||||
else {
|
||||
warn!("Got respawn packet but player doesn't have the required components");
|
||||
return;
|
||||
};
|
||||
|
||||
let new_instance_name = p.common.dimension.clone();
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ use tracing::{debug, error};
|
|||
|
||||
use super::as_system;
|
||||
use crate::{
|
||||
Account, GameProfileComponent, InConfigState, connection::RawConnection,
|
||||
declare_packet_handlers, disconnect::DisconnectEvent,
|
||||
Account, InConfigState, connection::RawConnection, declare_packet_handlers,
|
||||
disconnect::DisconnectEvent, player::GameProfileComponent,
|
||||
};
|
||||
|
||||
pub fn process_packet(ecs: &mut World, player: Entity, packet: &ClientboundLoginPacket) {
|
||||
|
|
|
@ -1,35 +1,37 @@
|
|||
use std::{fmt::Debug, sync::Arc};
|
||||
|
||||
use azalea_auth::game_profile::GameProfile;
|
||||
use azalea_block::BlockState;
|
||||
use azalea_buf::AzaleaWrite;
|
||||
use azalea_core::delta::PositionDelta8;
|
||||
use azalea_core::game_type::{GameMode, OptionalGameType};
|
||||
use azalea_core::position::{ChunkPos, Vec3};
|
||||
use azalea_core::resource_location::ResourceLocation;
|
||||
use azalea_core::tick::GameTick;
|
||||
use azalea_entity::metadata::PlayerMetadataBundle;
|
||||
use azalea_protocol::packets::common::CommonPlayerSpawnInfo;
|
||||
use azalea_protocol::packets::config::{ClientboundFinishConfiguration, ClientboundRegistryData};
|
||||
use azalea_protocol::packets::game::c_level_chunk_with_light::ClientboundLevelChunkPacketData;
|
||||
use azalea_protocol::packets::game::c_light_update::ClientboundLightUpdatePacketData;
|
||||
use azalea_protocol::packets::game::{
|
||||
ClientboundAddEntity, ClientboundLevelChunkWithLight, ClientboundLogin, ClientboundRespawn,
|
||||
use azalea_core::{
|
||||
delta::PositionDelta8,
|
||||
game_type::{GameMode, OptionalGameType},
|
||||
position::{ChunkPos, Vec3},
|
||||
resource_location::ResourceLocation,
|
||||
tick::GameTick,
|
||||
};
|
||||
use azalea_protocol::packets::{ConnectionProtocol, Packet, ProtocolPacket};
|
||||
use azalea_registry::{DimensionType, EntityKind};
|
||||
use azalea_world::palette::{PalettedContainer, PalettedContainerKind};
|
||||
use azalea_world::{Chunk, Instance, MinecraftEntityId, Section};
|
||||
use azalea_entity::metadata::PlayerMetadataBundle;
|
||||
use azalea_protocol::packets::{
|
||||
ConnectionProtocol, Packet, ProtocolPacket,
|
||||
common::CommonPlayerSpawnInfo,
|
||||
config::{ClientboundFinishConfiguration, ClientboundRegistryData},
|
||||
game::{
|
||||
ClientboundAddEntity, ClientboundLevelChunkWithLight, ClientboundLogin, ClientboundRespawn,
|
||||
c_level_chunk_with_light::ClientboundLevelChunkPacketData,
|
||||
c_light_update::ClientboundLightUpdatePacketData,
|
||||
},
|
||||
};
|
||||
use azalea_registry::{Biome, DimensionType, EntityKind};
|
||||
use azalea_world::{Chunk, Instance, MinecraftEntityId, Section, palette::PalettedContainer};
|
||||
use bevy_app::App;
|
||||
use bevy_ecs::component::Mutable;
|
||||
use bevy_ecs::{prelude::*, schedule::ExecutorKind};
|
||||
use bevy_ecs::{component::Mutable, prelude::*, schedule::ExecutorKind};
|
||||
use parking_lot::RwLock;
|
||||
use simdnbt::owned::{NbtCompound, NbtTag};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::connection::RawConnection;
|
||||
use crate::disconnect::DisconnectEvent;
|
||||
use crate::{
|
||||
ClientInformation, GameProfileComponent, InConfigState, InstanceHolder, LocalPlayerBundle,
|
||||
ClientInformation, InConfigState, LocalPlayerBundle, connection::RawConnection,
|
||||
disconnect::DisconnectEvent, local_player::InstanceHolder, player::GameProfileComponent,
|
||||
};
|
||||
|
||||
/// A way to simulate a client in a server, used for some internal tests.
|
||||
|
@ -257,8 +259,8 @@ pub fn make_basic_empty_chunk(
|
|||
for _ in 0..section_count {
|
||||
sections.push(Section {
|
||||
block_count: 0,
|
||||
states: PalettedContainer::new(PalettedContainerKind::BlockStates),
|
||||
biomes: PalettedContainer::new(PalettedContainerKind::Biomes),
|
||||
states: PalettedContainer::<BlockState>::new(),
|
||||
biomes: PalettedContainer::<Biome>::new(),
|
||||
});
|
||||
}
|
||||
sections.azalea_write(&mut chunk_bytes).unwrap();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use azalea_client::{InConfigState, InGameState, InstanceHolder, test_simulation::*};
|
||||
use azalea_client::{InConfigState, InGameState, local_player::InstanceHolder, test_simulation::*};
|
||||
use azalea_core::{position::ChunkPos, resource_location::ResourceLocation};
|
||||
use azalea_entity::LocalEntity;
|
||||
use azalea_protocol::packets::{
|
||||
|
|
|
@ -28,11 +28,11 @@ fn reply_to_ping_with_pong() {
|
|||
simulation
|
||||
.app
|
||||
.add_observer(move |trigger: Trigger<SendConfigPacketEvent>| {
|
||||
if trigger.sent_by == simulation.entity {
|
||||
if let ServerboundConfigPacket::Pong(packet) = &trigger.packet {
|
||||
assert_eq!(packet.id, 321);
|
||||
*reply_count_clone.lock() += 1;
|
||||
}
|
||||
if trigger.sent_by == simulation.entity
|
||||
&& let ServerboundConfigPacket::Pong(packet) = &trigger.packet
|
||||
{
|
||||
assert_eq!(packet.id, 321);
|
||||
*reply_count_clone.lock() += 1;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -63,11 +63,11 @@ fn reply_to_ping_with_pong() {
|
|||
simulation
|
||||
.app
|
||||
.add_observer(move |trigger: Trigger<SendPacketEvent>| {
|
||||
if trigger.sent_by == simulation.entity {
|
||||
if let ServerboundGamePacket::Pong(packet) = &trigger.packet {
|
||||
assert_eq!(packet.id, 123);
|
||||
*reply_count_clone.lock() += 1;
|
||||
}
|
||||
if trigger.sent_by == simulation.entity
|
||||
&& let ServerboundGamePacket::Pong(packet) = &trigger.packet
|
||||
{
|
||||
assert_eq!(packet.id, 123);
|
||||
*reply_count_clone.lock() += 1;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::io::{Cursor, Write};
|
||||
use std::io::{self, Cursor, Write};
|
||||
|
||||
use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite, BufReadError};
|
||||
|
||||
|
@ -124,26 +124,31 @@ impl From<Vec<u8>> for BitSet {
|
|||
}
|
||||
}
|
||||
|
||||
/// A list of bits with a known fixed size.
|
||||
/// A compact fixed-size array of bits.
|
||||
///
|
||||
/// The `N` is the number of bytes reserved for the bitset. You're encouraged to
|
||||
/// use it like `FixedBitSet<{ 20_usize.div_ceil(8) }>` if you need 20 bits.
|
||||
/// The `N` is the number of bits reserved for the bitset. You're encouraged to
|
||||
/// use it like `FixedBitSet<20>` if you need 20 bits.
|
||||
///
|
||||
/// TODO: this should be changed back to bits once this is resolved:
|
||||
/// <https://github.com/rust-lang/rust/issues/133199#issuecomment-2531645526>
|
||||
///
|
||||
/// Note that this is primarily meant for fast serialization and deserialization
|
||||
/// for Minecraft, if you don't need that you should use the `fixedbitset` crate
|
||||
/// since it's approximately 20% faster (since it stores the data as usizes
|
||||
/// instead of u8s).
|
||||
/// Note that this is optimized for fast serialization and deserialization for
|
||||
/// Minecraft, and may not be as performant as it could be for other purposes.
|
||||
/// Notably, the internal representation is an array of `u8`s even though
|
||||
/// `usize` would be slightly faster.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct FixedBitSet<const N: usize> {
|
||||
data: [u8; N],
|
||||
pub struct FixedBitSet<const N: usize>
|
||||
where
|
||||
[u8; bits_to_bytes(N)]: Sized,
|
||||
{
|
||||
data: [u8; bits_to_bytes(N)],
|
||||
}
|
||||
|
||||
impl<const N: usize> FixedBitSet<N> {
|
||||
pub fn new() -> Self {
|
||||
FixedBitSet { data: [0; N] }
|
||||
impl<const N: usize> FixedBitSet<N>
|
||||
where
|
||||
[u8; bits_to_bytes(N)]: Sized,
|
||||
{
|
||||
pub const fn new() -> Self {
|
||||
FixedBitSet {
|
||||
data: [0; bits_to_bytes(N)],
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -157,29 +162,42 @@ impl<const N: usize> FixedBitSet<N> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> AzaleaRead for FixedBitSet<N> {
|
||||
impl<const N: usize> AzaleaRead for FixedBitSet<N>
|
||||
where
|
||||
[u8; bits_to_bytes(N)]: Sized,
|
||||
{
|
||||
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
let mut data = [0; N];
|
||||
for item in data.iter_mut().take(N) {
|
||||
let mut data = [0; bits_to_bytes(N)];
|
||||
for item in data.iter_mut().take(bits_to_bytes(N)) {
|
||||
*item = u8::azalea_read(buf)?;
|
||||
}
|
||||
Ok(FixedBitSet { data })
|
||||
}
|
||||
}
|
||||
impl<const N: usize> AzaleaWrite for FixedBitSet<N> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
for i in 0..N {
|
||||
impl<const N: usize> AzaleaWrite for FixedBitSet<N>
|
||||
where
|
||||
[u8; bits_to_bytes(N)]: Sized,
|
||||
{
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
for i in 0..bits_to_bytes(N) {
|
||||
self.data[i].azalea_write(buf)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl<const N: usize> Default for FixedBitSet<N> {
|
||||
impl<const N: usize> Default for FixedBitSet<N>
|
||||
where
|
||||
[u8; bits_to_bytes(N)]: Sized,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn bits_to_bytes(n: usize) -> usize {
|
||||
n.div_ceil(8)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{
|
||||
fmt::{Debug, Error, Formatter},
|
||||
io::{Cursor, Write},
|
||||
fmt::{self, Debug},
|
||||
io::{self, Cursor, Write},
|
||||
};
|
||||
|
||||
use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError};
|
||||
|
@ -18,7 +18,7 @@ pub enum Err {
|
|||
}
|
||||
|
||||
impl Debug for Err {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Err::InvalidDifficulty(s) => write!(f, "Invalid difficulty: {s}"),
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ impl AzaleaRead for Difficulty {
|
|||
}
|
||||
|
||||
impl AzaleaWrite for Difficulty {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
u8::azalea_write(&self.id(), buf)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::io::Cursor;
|
||||
use std::io::{self, Cursor, Write};
|
||||
|
||||
use azalea_buf::{AzaleaRead, AzaleaReadLimited, AzaleaReadVar, AzaleaWrite};
|
||||
|
||||
|
@ -9,7 +9,7 @@ pub struct Filterable<T> {
|
|||
}
|
||||
|
||||
impl<T: AzaleaWrite> azalea_buf::AzaleaWrite for Filterable<T> {
|
||||
fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
self.raw.azalea_write(buf)?;
|
||||
self.filtered.azalea_write(buf)?;
|
||||
Ok(())
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::io::{Cursor, Write};
|
||||
use std::io::{self, Cursor, Write};
|
||||
|
||||
use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, BufReadError};
|
||||
use azalea_chat::translatable_component::TranslatableComponent;
|
||||
|
@ -108,7 +108,7 @@ impl AzaleaRead for GameMode {
|
|||
}
|
||||
|
||||
impl AzaleaWrite for GameMode {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
u8::azalea_write(&self.to_id(), buf)
|
||||
}
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ impl AzaleaRead for OptionalGameType {
|
|||
}
|
||||
|
||||
impl AzaleaWrite for OptionalGameType {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
GameMode::to_optional_id(*self).azalea_write(buf)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(incomplete_features)]
|
||||
#![feature(generic_const_exprs)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
pub mod aabb;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::{
|
||||
fmt::{self, Display, Formatter},
|
||||
fmt::{self, Display},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
|
@ -12,7 +12,7 @@ pub enum ObjectiveCriteria {
|
|||
}
|
||||
|
||||
impl Display for ObjectiveCriteria {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ObjectiveCriteria::Integer => write!(f, "integer"),
|
||||
ObjectiveCriteria::Hearts => write!(f, "hearts"),
|
||||
|
|
|
@ -3,19 +3,18 @@
|
|||
//! The most common ones are [`Vec3`] and [`BlockPos`], which are usually used
|
||||
//! for entity positions and block positions, respectively.
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::{
|
||||
fmt,
|
||||
hash::Hash,
|
||||
hash::{Hash, Hasher},
|
||||
io,
|
||||
io::{Cursor, Write},
|
||||
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite, BufReadError};
|
||||
|
||||
use crate::direction::Direction;
|
||||
use crate::math;
|
||||
use crate::resource_location::ResourceLocation;
|
||||
use crate::{direction::Direction, math, resource_location::ResourceLocation};
|
||||
|
||||
macro_rules! vec3_impl {
|
||||
($name:ident, $type:ty) => {
|
||||
|
@ -377,11 +376,8 @@ impl BlockPos {
|
|||
/// ```
|
||||
/// # use azalea_core::position::BlockPos;
|
||||
/// assert_eq!(
|
||||
/// BlockPos::min(
|
||||
/// &BlockPos::new(1, 20, 300),
|
||||
/// &BlockPos::new(50, 40, 30),
|
||||
/// ),
|
||||
/// BlockPos::new(1, 20, 30),
|
||||
/// BlockPos::min(&BlockPos::new(1, 20, 300), &BlockPos::new(50, 40, 30),),
|
||||
/// BlockPos::new(1, 20, 30),
|
||||
/// );
|
||||
/// ```
|
||||
pub fn min(&self, other: &Self) -> Self {
|
||||
|
@ -397,11 +393,8 @@ impl BlockPos {
|
|||
/// ```
|
||||
/// # use azalea_core::position::BlockPos;
|
||||
/// assert_eq!(
|
||||
/// BlockPos::max(
|
||||
/// &BlockPos::new(1, 20, 300),
|
||||
/// &BlockPos::new(50, 40, 30),
|
||||
/// ),
|
||||
/// BlockPos::new(50, 40, 300),
|
||||
/// BlockPos::max(&BlockPos::new(1, 20, 300), &BlockPos::new(50, 40, 30),),
|
||||
/// BlockPos::new(50, 40, 300),
|
||||
/// );
|
||||
/// ```
|
||||
pub fn max(&self, other: &Self) -> Self {
|
||||
|
@ -477,7 +470,7 @@ impl AzaleaRead for ChunkPos {
|
|||
}
|
||||
}
|
||||
impl AzaleaWrite for ChunkPos {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
u64::from(*self).azalea_write(buf)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -485,7 +478,7 @@ impl AzaleaWrite for ChunkPos {
|
|||
|
||||
impl Hash for ChunkPos {
|
||||
#[inline]
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
// optimized hash that only calls hash once
|
||||
u64::from(*self).hash(state);
|
||||
}
|
||||
|
@ -526,7 +519,7 @@ impl ChunkBlockPos {
|
|||
impl Hash for ChunkBlockPos {
|
||||
// optimized hash that only calls hash once
|
||||
#[inline]
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
u64::from(*self).hash(state);
|
||||
}
|
||||
}
|
||||
|
@ -546,8 +539,8 @@ impl From<ChunkBlockPos> for u64 {
|
|||
}
|
||||
impl nohash_hasher::IsEnabled for ChunkBlockPos {}
|
||||
|
||||
/// The coordinates of a block inside a chunk section. Each coordinate must be
|
||||
/// in the range [0, 15].
|
||||
/// The coordinates of a block inside a chunk section. Each coordinate should be
|
||||
/// in the range 0..=15.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct ChunkSectionBlockPos {
|
||||
pub x: u8,
|
||||
|
@ -556,6 +549,50 @@ pub struct ChunkSectionBlockPos {
|
|||
}
|
||||
vec3_impl!(ChunkSectionBlockPos, u8);
|
||||
|
||||
/// The coordinates of a chunk inside a chunk section. Each coordinate should be
|
||||
/// in the range 0..=3.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct ChunkSectionBiomePos {
|
||||
pub x: u8,
|
||||
pub y: u8,
|
||||
pub z: u8,
|
||||
}
|
||||
impl From<&ChunkBiomePos> for ChunkSectionBiomePos {
|
||||
#[inline]
|
||||
fn from(pos: &ChunkBiomePos) -> Self {
|
||||
ChunkSectionBiomePos {
|
||||
x: pos.x,
|
||||
y: (pos.y & 0b11) as u8,
|
||||
z: pos.z,
|
||||
}
|
||||
}
|
||||
}
|
||||
vec3_impl!(ChunkSectionBiomePos, u8);
|
||||
|
||||
/// The coordinates of a biome inside a chunk. Biomes are 4x4 blocks.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct ChunkBiomePos {
|
||||
pub x: u8,
|
||||
pub y: i32,
|
||||
pub z: u8,
|
||||
}
|
||||
impl From<&BlockPos> for ChunkBiomePos {
|
||||
#[inline]
|
||||
fn from(pos: &BlockPos) -> Self {
|
||||
ChunkBiomePos::from(&ChunkBlockPos::from(pos))
|
||||
}
|
||||
}
|
||||
impl From<&ChunkBlockPos> for ChunkBiomePos {
|
||||
#[inline]
|
||||
fn from(pos: &ChunkBlockPos) -> Self {
|
||||
ChunkBiomePos {
|
||||
x: pos.x >> 2,
|
||||
y: pos.y >> 2,
|
||||
z: pos.z >> 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<ChunkSectionBlockPos> for ChunkSectionPos {
|
||||
type Output = BlockPos;
|
||||
|
||||
|
@ -570,7 +607,7 @@ impl Add<ChunkSectionBlockPos> for ChunkSectionPos {
|
|||
impl Hash for ChunkSectionBlockPos {
|
||||
// optimized hash that only calls hash once
|
||||
#[inline]
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
u16::from(*self).hash(state);
|
||||
}
|
||||
}
|
||||
|
@ -782,7 +819,7 @@ impl AzaleaRead for ChunkSectionPos {
|
|||
}
|
||||
|
||||
impl AzaleaWrite for BlockPos {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
let mut val: u64 = 0;
|
||||
val |= ((self.x as u64) & PACKED_X_MASK) << X_OFFSET;
|
||||
val |= (self.y as u64) & PACKED_Y_MASK;
|
||||
|
@ -792,7 +829,7 @@ impl AzaleaWrite for BlockPos {
|
|||
}
|
||||
|
||||
impl AzaleaWrite for GlobalPos {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
ResourceLocation::azalea_write(&self.world, buf)?;
|
||||
BlockPos::azalea_write(&self.pos, buf)?;
|
||||
|
||||
|
@ -801,7 +838,7 @@ impl AzaleaWrite for GlobalPos {
|
|||
}
|
||||
|
||||
impl AzaleaWrite for ChunkSectionPos {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
let long = (((self.x & 0x3FFFFF) as i64) << 42)
|
||||
| (self.y & 0xFFFFF) as i64
|
||||
| (((self.z & 0x3FFFFF) as i64) << 20);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use std::{
|
||||
fmt,
|
||||
io::{Cursor, Write},
|
||||
io::{self, Cursor, Write},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
|
@ -67,7 +67,7 @@ impl AzaleaRead for ResourceLocation {
|
|||
}
|
||||
}
|
||||
impl AzaleaWrite for ResourceLocation {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
self.to_string().azalea_write(buf)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,9 @@
|
|||
|
||||
mod signing;
|
||||
|
||||
use aes::cipher::inout::InOutBuf;
|
||||
use aes::{
|
||||
Aes128,
|
||||
cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit},
|
||||
cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit, inout::InOutBuf},
|
||||
};
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
use sha1::{Digest, Sha1};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! See <https://minecraft.fandom.com/wiki/Attribute>.
|
||||
//! See <https://minecraft.wiki/w/Attribute>.
|
||||
|
||||
use std::collections::{HashMap, hash_map};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Define some types needed for entity metadata.
|
||||
|
||||
use std::io::{Cursor, Write};
|
||||
use std::io::{self, Cursor, Write};
|
||||
|
||||
use azalea_buf::{AzBuf, AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
|
||||
use azalea_chat::FormattedText;
|
||||
|
@ -43,7 +43,7 @@ impl AzaleaRead for EntityMetadataItems {
|
|||
}
|
||||
|
||||
impl AzaleaWrite for EntityMetadataItems {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
for item in &self.0 {
|
||||
item.index.azalea_write(buf)?;
|
||||
item.value.azalea_write(buf)?;
|
||||
|
@ -128,7 +128,7 @@ impl AzaleaRead for OptionalUnsignedInt {
|
|||
}
|
||||
}
|
||||
impl AzaleaWrite for OptionalUnsignedInt {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
match self.0 {
|
||||
Some(val) => (val + 1).azalea_write_var(buf),
|
||||
None => 0u32.azalea_write_var(buf),
|
||||
|
|
|
@ -12,7 +12,7 @@ mod plugin;
|
|||
pub mod vec_delta_codec;
|
||||
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
fmt::{self, Debug},
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
|
@ -133,7 +133,7 @@ impl EntityUuid {
|
|||
}
|
||||
}
|
||||
impl Debug for EntityUuid {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
(self.0).fmt(f)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#![allow(clippy::single_match)]
|
||||
|
||||
// This file is generated from codegen/lib/code/entity.py.
|
||||
// This file is @generated from codegen/lib/code/entity.py.
|
||||
// Don't change it manually!
|
||||
|
||||
use azalea_chat::FormattedText;
|
||||
|
|
|
@ -3,6 +3,7 @@ use azalea_buf::AzBuf;
|
|||
use azalea_core::{color::RgbColor, position::BlockPos};
|
||||
use azalea_inventory::ItemStack;
|
||||
use azalea_registry::ParticleKind;
|
||||
use azalea_world::MinecraftEntityId;
|
||||
use bevy_ecs::component::Component;
|
||||
|
||||
// the order of this enum must be kept in sync with ParticleKind, otherwise
|
||||
|
@ -290,15 +291,27 @@ pub struct ItemParticle {
|
|||
|
||||
#[derive(Debug, Clone, AzBuf, Default)]
|
||||
pub struct VibrationParticle {
|
||||
pub origin: BlockPos,
|
||||
pub position_type: String,
|
||||
pub block_position: BlockPos,
|
||||
#[var]
|
||||
pub entity_id: u32,
|
||||
pub position: PositionSource,
|
||||
#[var]
|
||||
pub ticks: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, AzBuf)]
|
||||
pub enum PositionSource {
|
||||
Block(BlockPos),
|
||||
Entity {
|
||||
#[var]
|
||||
id: MinecraftEntityId,
|
||||
y_offset: f32,
|
||||
},
|
||||
}
|
||||
impl Default for PositionSource {
|
||||
fn default() -> Self {
|
||||
// bad default but hopefully it never gets used anyways
|
||||
Self::Block(BlockPos::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, AzBuf, Default)]
|
||||
pub struct SculkChargeParticle {
|
||||
pub roll: f32,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::Debug,
|
||||
fmt::{self, Debug},
|
||||
};
|
||||
|
||||
use azalea_core::position::ChunkPos;
|
||||
|
@ -115,7 +115,7 @@ impl EntityIdIndex {
|
|||
}
|
||||
|
||||
impl Debug for EntityUuidIndex {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("EntityUuidIndex").finish()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
use core::f64;
|
||||
use std::{any::Any, collections::HashMap, io::Cursor};
|
||||
use std::{
|
||||
any::Any,
|
||||
collections::HashMap,
|
||||
io::{self, Cursor},
|
||||
};
|
||||
|
||||
use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite, BufReadError};
|
||||
use azalea_chat::FormattedText;
|
||||
|
@ -23,11 +27,11 @@ pub trait DataComponent: Send + Sync + Any {
|
|||
}
|
||||
|
||||
pub trait EncodableDataComponent: Send + Sync + Any {
|
||||
fn encode(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error>;
|
||||
fn encode(&self, buf: &mut Vec<u8>) -> io::Result<()>;
|
||||
// using the Clone trait makes it not be object-safe, so we have our own clone
|
||||
// function instead
|
||||
fn clone(&self) -> Box<dyn EncodableDataComponent>;
|
||||
// same deal here
|
||||
// same thing here
|
||||
fn eq(&self, other: Box<dyn EncodableDataComponent>) -> bool;
|
||||
}
|
||||
|
||||
|
@ -35,7 +39,7 @@ impl<T> EncodableDataComponent for T
|
|||
where
|
||||
T: DataComponent + Clone + AzaleaWrite + AzaleaRead + PartialEq,
|
||||
{
|
||||
fn encode(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
|
||||
fn encode(&self, buf: &mut Vec<u8>) -> io::Result<()> {
|
||||
self.azalea_write(buf)
|
||||
}
|
||||
fn clone(&self) -> Box<dyn EncodableDataComponent> {
|
||||
|
@ -897,7 +901,9 @@ pub struct DamageResistant {
|
|||
// in the vanilla code this is
|
||||
// ```
|
||||
// StreamCodec.composite(
|
||||
// TagKey.streamCodec(Registries.DAMAGE_TYPE), DamageResistant::types, DamageResistant::new
|
||||
// TagKey.streamCodec(Registries.DAMAGE_TYPE),
|
||||
// DamageResistant::types,
|
||||
// DamageResistant::new,
|
||||
// );
|
||||
// ```
|
||||
// i'm not entirely sure if this is meant to be a vec or something, i just made it a
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::{
|
||||
any::Any,
|
||||
fmt,
|
||||
io::{Cursor, Write},
|
||||
io::{self, Cursor, Write},
|
||||
};
|
||||
|
||||
use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
|
||||
|
@ -73,10 +73,10 @@ impl ItemStack {
|
|||
|
||||
/// Update whether this slot is empty, based on the count.
|
||||
pub fn update_empty(&mut self) {
|
||||
if let ItemStack::Present(i) = self {
|
||||
if i.is_empty() {
|
||||
*self = ItemStack::Empty;
|
||||
}
|
||||
if let ItemStack::Present(i) = self
|
||||
&& i.is_empty()
|
||||
{
|
||||
*self = ItemStack::Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,14 +122,14 @@ impl ItemStackData {
|
|||
/// # use azalea_inventory::ItemStackData;
|
||||
/// # use azalea_registry::Item;
|
||||
/// let mut a = ItemStackData {
|
||||
/// kind: Item::Stone,
|
||||
/// count: 1,
|
||||
/// components: Default::default(),
|
||||
/// kind: Item::Stone,
|
||||
/// count: 1,
|
||||
/// components: Default::default(),
|
||||
/// };
|
||||
/// let mut b = ItemStackData {
|
||||
/// kind: Item::Stone,
|
||||
/// count: 2,
|
||||
/// components: Default::default(),
|
||||
/// kind: Item::Stone,
|
||||
/// count: 2,
|
||||
/// components: Default::default(),
|
||||
/// };
|
||||
/// assert!(a.is_same_item_and_components(&b));
|
||||
///
|
||||
|
@ -159,7 +159,7 @@ impl AzaleaRead for ItemStack {
|
|||
}
|
||||
|
||||
impl AzaleaWrite for ItemStack {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
match self {
|
||||
ItemStack::Empty => 0_i32.azalea_write_var(buf)?,
|
||||
ItemStack::Present(i) => {
|
||||
|
@ -256,7 +256,7 @@ impl AzaleaRead for DataComponentPatch {
|
|||
}
|
||||
|
||||
impl AzaleaWrite for DataComponentPatch {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
let mut components_with_data_count: u32 = 0;
|
||||
let mut components_without_data_count: u32 = 0;
|
||||
for component in self.components.values() {
|
||||
|
|
|
@ -151,12 +151,11 @@ fn clip_with_interaction_override(
|
|||
// compostors, hoppers, and scaffolding.
|
||||
let interaction_shape = &*EMPTY_SHAPE;
|
||||
let interaction_hit_result = interaction_shape.clip(from, to, block_pos);
|
||||
if let Some(interaction_hit_result) = interaction_hit_result {
|
||||
if interaction_hit_result.location.distance_squared_to(from)
|
||||
if let Some(interaction_hit_result) = interaction_hit_result
|
||||
&& interaction_hit_result.location.distance_squared_to(from)
|
||||
< block_hit_result.location.distance_squared_to(from)
|
||||
{
|
||||
return Some(block_hit_result.with_direction(interaction_hit_result.direction));
|
||||
}
|
||||
{
|
||||
return Some(block_hit_result.with_direction(interaction_hit_result.direction));
|
||||
}
|
||||
|
||||
Some(block_hit_result)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Autogenerated block collisions for every block
|
||||
|
||||
// This file is generated from codegen/lib/code/shapes.py. If you want to
|
||||
// This file is @generated from codegen/lib/code/shapes.py. If you want to
|
||||
// modify it, change that file.
|
||||
|
||||
#![allow(clippy::explicit_auto_deref)]
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::cmp;
|
||||
|
||||
use azalea_core::{
|
||||
bitset::BitSet,
|
||||
direction::{Axis, AxisCycle},
|
||||
|
@ -146,12 +148,12 @@ impl BitSetDiscreteVoxelShape {
|
|||
fn fill_update_bounds(&mut self, x: u32, y: u32, z: u32, update: bool) {
|
||||
self.storage.set(self.get_index(x, y, z));
|
||||
if update {
|
||||
self.x_min = std::cmp::min(self.x_min, x as i32);
|
||||
self.y_min = std::cmp::min(self.y_min, y as i32);
|
||||
self.z_min = std::cmp::min(self.z_min, z as i32);
|
||||
self.x_max = std::cmp::max(self.x_max, (x + 1) as i32);
|
||||
self.y_max = std::cmp::max(self.y_max, (y + 1) as i32);
|
||||
self.z_max = std::cmp::max(self.z_max, (z + 1) as i32);
|
||||
self.x_min = cmp::min(self.x_min, x as i32);
|
||||
self.y_min = cmp::min(self.y_min, y as i32);
|
||||
self.z_min = cmp::min(self.z_min, z as i32);
|
||||
self.x_max = cmp::max(self.x_max, (x + 1) as i32);
|
||||
self.y_max = cmp::max(self.y_max, (y + 1) as i32);
|
||||
self.z_max = cmp::max(self.z_max, (z + 1) as i32);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,24 +203,24 @@ impl BitSetDiscreteVoxelShape {
|
|||
var12.try_into().unwrap(),
|
||||
var14.try_into().unwrap(),
|
||||
));
|
||||
var7[2] = std::cmp::min(var7[2], var14);
|
||||
var7[5] = std::cmp::max(var7[5], var14);
|
||||
var7[2] = cmp::min(var7[2], var14);
|
||||
var7[5] = cmp::max(var7[5], var14);
|
||||
var13[0] = true;
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
if var13[0] {
|
||||
var7[1] = std::cmp::min(var7[1], var12);
|
||||
var7[4] = std::cmp::max(var7[4], var12);
|
||||
var7[1] = cmp::min(var7[1], var12);
|
||||
var7[4] = cmp::max(var7[4], var12);
|
||||
var10[0] = true;
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
if var10[0] {
|
||||
var7[0] = std::cmp::min(var7[0], var9);
|
||||
var7[3] = std::cmp::max(var7[3], var9);
|
||||
var7[0] = cmp::min(var7[0], var9);
|
||||
var7[3] = cmp::max(var7[3], var9);
|
||||
}
|
||||
|
||||
true
|
||||
|
|
|
@ -86,7 +86,7 @@ impl<'a> BlockCollisionsState<'a> {
|
|||
let block_state: BlockState = if item_chunk_pos == initial_chunk_pos {
|
||||
match &initial_chunk {
|
||||
Some(initial_chunk) => initial_chunk
|
||||
.get(&ChunkBlockPos::from(item.pos), self.world.chunks.min_y)
|
||||
.get_block_state(&ChunkBlockPos::from(item.pos), self.world.chunks.min_y)
|
||||
.unwrap_or(BlockState::AIR),
|
||||
_ => BlockState::AIR,
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ impl<'a> BlockCollisionsState<'a> {
|
|||
|
||||
for (cached_section_pos, cached_section) in &self.cached_sections {
|
||||
if section_pos == *cached_section_pos {
|
||||
return cached_section.get(section_block_pos);
|
||||
return cached_section.get_block_state(section_block_pos);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,7 +211,7 @@ impl<'a> BlockCollisionsState<'a> {
|
|||
// println!("chunk section data: {:?}", section.states.storage.data);
|
||||
// println!("biome length: {}", section.biomes.storage.data.len());
|
||||
|
||||
section.get(section_block_pos)
|
||||
section.get_block_state(section_block_pos)
|
||||
}
|
||||
|
||||
fn get_block_shape(&mut self, block_state: BlockState) -> &'static VoxelShape {
|
||||
|
|
|
@ -23,7 +23,7 @@ fn as_packet_derive(input: TokenStream, state: proc_macro2::TokenStream) -> Toke
|
|||
|
||||
let contents = quote! {
|
||||
impl #ident {
|
||||
pub fn write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
|
||||
pub fn write(&self, buf: &mut impl std::io::Write) -> std::io::Result<()> {
|
||||
azalea_buf::AzaleaWrite::azalea_write(self, buf)
|
||||
}
|
||||
|
||||
|
@ -361,7 +361,7 @@ pub fn declare_state_packets(input: TokenStream) -> TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
fn write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
|
||||
fn write(&self, buf: &mut impl std::io::Write) -> std::io::Result<()> {
|
||||
match self {
|
||||
#serverbound_write_match_contents
|
||||
}
|
||||
|
@ -405,7 +405,7 @@ pub fn declare_state_packets(input: TokenStream) -> TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
fn write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
|
||||
fn write(&self, buf: &mut impl std::io::Write) -> std::io::Result<()> {
|
||||
match self {
|
||||
#clientbound_write_match_contents
|
||||
}
|
||||
|
|
|
@ -25,8 +25,7 @@ use tokio::{
|
|||
io::{self, AsyncWriteExt},
|
||||
net::{TcpListener, TcpStream},
|
||||
};
|
||||
use tracing::Level;
|
||||
use tracing::{error, info, warn};
|
||||
use tracing::{Level, error, info, warn};
|
||||
|
||||
const LISTEN_ADDR: &str = "127.0.0.1:25566";
|
||||
const PROXY_ADDR: &str = "127.0.0.1:25565";
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::io::{self, Cursor};
|
||||
|
||||
use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite};
|
||||
use azalea_core::bitset::FixedBitSet;
|
||||
use bevy_ecs::component::Component;
|
||||
|
@ -96,8 +98,8 @@ impl Default for ModelCustomization {
|
|||
}
|
||||
|
||||
impl AzaleaRead for ModelCustomization {
|
||||
fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> {
|
||||
let set = FixedBitSet::<{ 7_usize.div_ceil(8) }>::azalea_read(buf)?;
|
||||
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> {
|
||||
let set = FixedBitSet::<7>::azalea_read(buf)?;
|
||||
Ok(Self {
|
||||
cape: set.index(0),
|
||||
jacket: set.index(1),
|
||||
|
@ -111,8 +113,8 @@ impl AzaleaRead for ModelCustomization {
|
|||
}
|
||||
|
||||
impl AzaleaWrite for ModelCustomization {
|
||||
fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
|
||||
let mut set = FixedBitSet::<{ 7_usize.div_ceil(8) }>::new();
|
||||
fn azalea_write(&self, buf: &mut impl io::Write) -> io::Result<()> {
|
||||
let mut set = FixedBitSet::<7>::new();
|
||||
if self.cape {
|
||||
set.set(0);
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ pub struct RelativeMovements {
|
|||
impl AzaleaRead for RelativeMovements {
|
||||
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
// yes minecraft seriously wastes that many bits, smh
|
||||
let set = FixedBitSet::<{ 32_usize.div_ceil(8) }>::azalea_read(buf)?;
|
||||
let set = FixedBitSet::<32>::azalea_read(buf)?;
|
||||
Ok(RelativeMovements {
|
||||
x: set.index(0),
|
||||
y: set.index(1),
|
||||
|
@ -48,8 +48,8 @@ impl AzaleaRead for RelativeMovements {
|
|||
}
|
||||
|
||||
impl AzaleaWrite for RelativeMovements {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
let mut set = FixedBitSet::<{ 32_usize.div_ceil(8) }>::new();
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
let mut set = FixedBitSet::<32>::new();
|
||||
let mut set_bit = |index: usize, value: bool| {
|
||||
if value {
|
||||
set.set(index);
|
||||
|
|
|
@ -1,29 +1,40 @@
|
|||
//! Connect to remote servers/clients.
|
||||
|
||||
use std::fmt::{self, Debug, Display};
|
||||
use std::io::{self, Cursor};
|
||||
use std::marker::PhantomData;
|
||||
use std::net::SocketAddr;
|
||||
use std::{
|
||||
fmt::{self, Debug, Display},
|
||||
io::{self, Cursor},
|
||||
marker::PhantomData,
|
||||
net::SocketAddr,
|
||||
};
|
||||
|
||||
use azalea_auth::game_profile::GameProfile;
|
||||
use azalea_auth::sessionserver::{ClientSessionServerError, ServerSessionServerError};
|
||||
use azalea_auth::{
|
||||
game_profile::GameProfile,
|
||||
sessionserver::{ClientSessionServerError, ServerSessionServerError},
|
||||
};
|
||||
use azalea_crypto::{Aes128CfbDec, Aes128CfbEnc};
|
||||
use thiserror::Error;
|
||||
use tokio::io::{AsyncWriteExt, BufStream};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf, ReuniteError};
|
||||
use tokio::{
|
||||
io::{AsyncWriteExt, BufStream},
|
||||
net::{
|
||||
TcpStream,
|
||||
tcp::{OwnedReadHalf, OwnedWriteHalf, ReuniteError},
|
||||
},
|
||||
};
|
||||
use tracing::{error, info};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::packets::ProtocolPacket;
|
||||
use crate::packets::config::{ClientboundConfigPacket, ServerboundConfigPacket};
|
||||
use crate::packets::game::{ClientboundGamePacket, ServerboundGamePacket};
|
||||
use crate::packets::handshake::{ClientboundHandshakePacket, ServerboundHandshakePacket};
|
||||
use crate::packets::login::c_hello::ClientboundHello;
|
||||
use crate::packets::login::{ClientboundLoginPacket, ServerboundLoginPacket};
|
||||
use crate::packets::status::{ClientboundStatusPacket, ServerboundStatusPacket};
|
||||
use crate::read::{ReadPacketError, deserialize_packet, read_raw_packet, try_read_raw_packet};
|
||||
use crate::write::{serialize_packet, write_raw_packet};
|
||||
use crate::{
|
||||
packets::{
|
||||
ProtocolPacket,
|
||||
config::{ClientboundConfigPacket, ServerboundConfigPacket},
|
||||
game::{ClientboundGamePacket, ServerboundGamePacket},
|
||||
handshake::{ClientboundHandshakePacket, ServerboundHandshakePacket},
|
||||
login::{ClientboundLoginPacket, ServerboundLoginPacket, c_hello::ClientboundHello},
|
||||
status::{ClientboundStatusPacket, ServerboundStatusPacket},
|
||||
},
|
||||
read::{ReadPacketError, deserialize_packet, read_raw_packet, try_read_raw_packet},
|
||||
write::{serialize_packet, write_raw_packet},
|
||||
};
|
||||
|
||||
pub struct RawReadConnection {
|
||||
pub read_stream: OwnedReadHalf,
|
||||
|
@ -57,18 +68,13 @@ pub struct WriteConnection<W: ProtocolPacket> {
|
|||
/// Join an offline-mode server and go through the handshake.
|
||||
/// ```rust,no_run
|
||||
/// use azalea_protocol::{
|
||||
/// resolver,
|
||||
/// connect::Connection,
|
||||
/// packets::{
|
||||
/// self,
|
||||
/// ClientIntention, PROTOCOL_VERSION,
|
||||
/// login::{
|
||||
/// ClientboundLoginPacket,
|
||||
/// ServerboundHello,
|
||||
/// ServerboundKey
|
||||
/// },
|
||||
/// handshake::ServerboundIntention
|
||||
/// }
|
||||
/// self, ClientIntention, PROTOCOL_VERSION,
|
||||
/// handshake::ServerboundIntention,
|
||||
/// login::{ClientboundLoginPacket, ServerboundHello, ServerboundKey},
|
||||
/// },
|
||||
/// resolver,
|
||||
/// };
|
||||
///
|
||||
/// #[tokio::main]
|
||||
|
@ -82,7 +88,8 @@ pub struct WriteConnection<W: ProtocolPacket> {
|
|||
/// hostname: resolved_address.ip().to_string(),
|
||||
/// port: resolved_address.port(),
|
||||
/// intention: ClientIntention::Login,
|
||||
/// }).await?;
|
||||
/// })
|
||||
/// .await?;
|
||||
///
|
||||
/// let mut conn = conn.login();
|
||||
///
|
||||
|
@ -90,7 +97,8 @@ pub struct WriteConnection<W: ProtocolPacket> {
|
|||
/// conn.write(ServerboundHello {
|
||||
/// name: "bot".to_string(),
|
||||
/// profile_id: uuid::Uuid::nil(),
|
||||
/// }).await?;
|
||||
/// })
|
||||
/// .await?;
|
||||
///
|
||||
/// let (conn, game_profile) = loop {
|
||||
/// let packet = conn.read().await?;
|
||||
|
@ -101,7 +109,8 @@ pub struct WriteConnection<W: ProtocolPacket> {
|
|||
/// conn.write(ServerboundKey {
|
||||
/// key_bytes: e.encrypted_public_key,
|
||||
/// encrypted_challenge: e.encrypted_challenge,
|
||||
/// }).await?;
|
||||
/// })
|
||||
/// .await?;
|
||||
/// conn.set_encryption_key(e.secret_key);
|
||||
/// }
|
||||
/// ClientboundLoginPacket::LoginCompression(p) => {
|
||||
|
@ -391,19 +400,20 @@ impl Connection<ClientboundLoginPacket, ServerboundLoginPacket> {
|
|||
///
|
||||
/// ```rust,no_run
|
||||
/// use azalea_auth::AuthResult;
|
||||
/// use azalea_protocol::connect::Connection;
|
||||
/// use azalea_protocol::packets::login::{
|
||||
/// ClientboundLoginPacket,
|
||||
/// ServerboundKey
|
||||
/// use azalea_protocol::{
|
||||
/// connect::Connection,
|
||||
/// packets::login::{ClientboundLoginPacket, ServerboundKey},
|
||||
/// };
|
||||
/// use uuid::Uuid;
|
||||
/// # use azalea_protocol::ServerAddress;
|
||||
///
|
||||
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let AuthResult { access_token, profile } = azalea_auth::auth(
|
||||
/// "example@example.com",
|
||||
/// azalea_auth::AuthOpts::default()
|
||||
/// ).await.expect("Couldn't authenticate");
|
||||
/// let AuthResult {
|
||||
/// access_token,
|
||||
/// profile,
|
||||
/// } = azalea_auth::auth("example@example.com", azalea_auth::AuthOpts::default())
|
||||
/// .await
|
||||
/// .expect("Couldn't authenticate");
|
||||
/// #
|
||||
/// # let address = ServerAddress::try_from("example@example.com").unwrap();
|
||||
/// # let resolved_address = azalea_protocol::resolver::resolve_address(&address).await?;
|
||||
|
@ -417,16 +427,13 @@ impl Connection<ClientboundLoginPacket, ServerboundLoginPacket> {
|
|||
/// ClientboundLoginPacket::Hello(p) => {
|
||||
/// // tell Mojang we're joining the server & enable encryption
|
||||
/// let e = azalea_crypto::encrypt(&p.public_key, &p.challenge).unwrap();
|
||||
/// conn.authenticate(
|
||||
/// &access_token,
|
||||
/// &profile.id,
|
||||
/// e.secret_key,
|
||||
/// &p
|
||||
/// ).await?;
|
||||
/// conn.authenticate(&access_token, &profile.id, e.secret_key, &p)
|
||||
/// .await?;
|
||||
/// conn.write(ServerboundKey {
|
||||
/// key_bytes: e.encrypted_public_key,
|
||||
/// encrypted_challenge: e.encrypted_challenge,
|
||||
/// }).await?;
|
||||
/// })
|
||||
/// .await?;
|
||||
/// conn.set_encryption_key(e.secret_key);
|
||||
/// }
|
||||
/// _ => {}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue