aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2025-12-12 01:29:49 -0600
committerGitHub <noreply@github.com>2025-12-12 01:29:49 -0600
commitf4a3c53eee7d29bade0c074f402c4a45aa98eca8 (patch)
treef25ec1d1390c5e96aba858141206a05812135b95
parent7f761df3e7b72ce75be21ab9b3a533d0a5a938a5 (diff)
downloadazalea-drasl-f4a3c53eee7d29bade0c074f402c4a45aa98eca8.tar.xz
Delete `StartError` and `JoinError` (#296)
* delete StartError and JoinError * update changelog
-rw-r--r--CHANGELOG.md2
-rw-r--r--azalea-client/src/client.rs47
-rw-r--r--azalea-client/src/lib.rs4
-rw-r--r--azalea-client/src/ping.rs24
-rw-r--r--azalea-client/src/plugins/join.rs23
-rw-r--r--azalea-protocol/src/address.rs157
-rw-r--r--azalea-protocol/src/lib.rs99
-rw-r--r--azalea-protocol/src/resolve.rs10
-rw-r--r--azalea/README.md3
-rw-r--r--azalea/examples/echo.rs3
-rw-r--r--azalea/examples/nearest_entity.rs4
-rw-r--r--azalea/examples/steal.rs3
-rw-r--r--azalea/examples/testbot/main.rs3
-rw-r--r--azalea/examples/todo/craft_dig_straight_down.rs3
-rw-r--r--azalea/examples/todo/mine_a_chunk.rs3
-rw-r--r--azalea/examples/todo/pvp.rs3
-rw-r--r--azalea/src/lib.rs67
-rw-r--r--azalea/src/prelude.rs1
-rw-r--r--azalea/src/swarm/mod.rs117
19 files changed, 309 insertions, 267 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce03fcf3..4616e8e8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,9 +27,9 @@ is breaking anyways, semantic versioning is not followed.
- Moved `azalea_client::inventory::Inventory` to `azalea_entity::inventory::Inventory`.
- Add `Client::open_container_at_with_timeout_ticks`, and `Client::open_container_at` now times out after 5 seconds.
- Rename `ResourceLocation` to `Identifier` to match Minecraft's new internal naming.
-- Rename `azalea_protocol::resolver` to `resolve` and `ResolverError` to `ResolveError`.
- Refactor `RegistryHolder` to pre-deserialize some registries.
- The handler function is now automatically single-threaded, making `#[tokio::main(flavor = "current_thread")]` unnecessary.
+- Improve APIs related to resolving server addresses.
- Mojang's sessionserver is now requested using the SOCKS5 proxy given in `JoinOpts::proxy`.
- Refactor `azalea-registry`. Notably, `Item` and `Block` are now named `ItemKind` and `BlockKind`.
diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs
index aa2da1d5..579f5d26 100644
--- a/azalea-client/src/client.rs
+++ b/azalea-client/src/client.rs
@@ -2,7 +2,6 @@ use std::{
collections::HashMap,
fmt::Debug,
mem,
- net::SocketAddr,
sync::Arc,
thread,
time::{Duration, Instant},
@@ -23,10 +22,10 @@ use azalea_entity::{
};
use azalea_physics::local_player::PhysicsState;
use azalea_protocol::{
- ServerAddress,
+ address::{ResolvableAddr, ResolvedAddr},
connect::Proxy,
packets::{Packet, game::ServerboundGamePacket},
- resolve,
+ resolve::ResolveError,
};
use azalea_registry::{DataRegistryKeyRef, identifier::Identifier};
use azalea_world::{Instance, InstanceContainer, InstanceName, MinecraftEntityId, PartialInstance};
@@ -37,7 +36,6 @@ use bevy_ecs::{
schedule::{InternedScheduleLabel, LogLevel, ScheduleBuildSettings},
};
use parking_lot::{Mutex, RwLock};
-use thiserror::Error;
use tokio::{
sync::{
mpsc::{self},
@@ -87,15 +85,6 @@ pub struct Client {
pub ecs: Arc<Mutex<World>>,
}
-/// An error that happened while joining the server.
-#[derive(Error, Debug)]
-pub enum JoinError {
- #[error(transparent)]
- Resolver(#[from] resolve::ResolveError),
- #[error("The given address could not be parsed into a ServerAddress")]
- InvalidAddress,
-}
-
pub struct StartClientOpts {
pub ecs_lock: Arc<Mutex<World>>,
pub account: Account,
@@ -106,8 +95,7 @@ pub struct StartClientOpts {
impl StartClientOpts {
pub fn new(
account: Account,
- address: ServerAddress,
- resolved_address: SocketAddr,
+ address: ResolvedAddr,
event_sender: Option<mpsc::UnboundedSender<Event>>,
) -> StartClientOpts {
let mut app = App::new();
@@ -123,7 +111,6 @@ impl StartClientOpts {
account,
connect_opts: ConnectOpts {
address,
- resolved_address,
server_proxy: None,
sessionserver_proxy: None,
},
@@ -197,35 +184,25 @@ impl Client {
/// ```
pub async fn join(
account: Account,
- address: impl TryInto<ServerAddress>,
- ) -> Result<(Self, mpsc::UnboundedReceiver<Event>), JoinError> {
- let address: ServerAddress = address.try_into().map_err(|_| JoinError::InvalidAddress)?;
- let resolved_address = resolve::resolve_address(&address).await?;
+ address: impl ResolvableAddr,
+ ) -> Result<(Self, mpsc::UnboundedReceiver<Event>), ResolveError> {
+ let address = address.resolve().await?;
let (tx, rx) = mpsc::unbounded_channel();
- let client = Self::start_client(StartClientOpts::new(
- account,
- address,
- resolved_address,
- Some(tx),
- ))
- .await;
+ let client = Self::start_client(StartClientOpts::new(account, address, Some(tx))).await;
Ok((client, rx))
}
pub async fn join_with_proxy(
account: Account,
- address: impl TryInto<ServerAddress>,
+ address: impl ResolvableAddr,
proxy: Proxy,
- ) -> Result<(Self, mpsc::UnboundedReceiver<Event>), JoinError> {
- let address: ServerAddress = address.try_into().map_err(|_| JoinError::InvalidAddress)?;
- let resolved_address = resolve::resolve_address(&address).await?;
+ ) -> Result<(Self, mpsc::UnboundedReceiver<Event>), ResolveError> {
+ let address = address.resolve().await?;
let (tx, rx) = mpsc::unbounded_channel();
- let client = Self::start_client(
- StartClientOpts::new(account, address, resolved_address, Some(tx)).proxy(proxy),
- )
- .await;
+ let client =
+ Self::start_client(StartClientOpts::new(account, address, Some(tx)).proxy(proxy)).await;
Ok((client, rx))
}
diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs
index c818d96a..6d216b2b 100644
--- a/azalea-client/src/lib.rs
+++ b/azalea-client/src/lib.rs
@@ -27,8 +27,8 @@ pub use azalea_protocol::common::client_information::ClientInformation;
// version.
pub use bevy_tasks;
pub use client::{
- Client, InConfigState, InGameState, JoinError, JoinedClientBundle, LocalPlayerBundle,
- StartClientOpts, start_ecs_runner,
+ Client, InConfigState, InGameState, JoinedClientBundle, LocalPlayerBundle, StartClientOpts,
+ start_ecs_runner,
};
pub use events::Event;
pub use movement::{StartSprintEvent, StartWalkEvent};
diff --git a/azalea-client/src/ping.rs b/azalea-client/src/ping.rs
index 2c3ddfcf..08f909e4 100644
--- a/azalea-client/src/ping.rs
+++ b/azalea-client/src/ping.rs
@@ -3,7 +3,7 @@
use std::io;
use azalea_protocol::{
- ServerAddress,
+ address::{ResolvableAddr, ServerAddr},
connect::{Connection, ConnectionError, Proxy},
packets::{
ClientIntention, PROTOCOL_VERSION,
@@ -23,7 +23,7 @@ use thiserror::Error;
#[derive(Error, Debug)]
pub enum PingError {
#[error("{0}")]
- Resolver(#[from] resolve::ResolveError),
+ Resolve(#[from] resolve::ResolveError),
#[error("{0}")]
Connection(#[from] ConnectionError),
#[error("{0}")]
@@ -48,23 +48,21 @@ pub enum PingError {
/// }
/// ```
pub async fn ping_server(
- address: impl TryInto<ServerAddress>,
+ address: impl ResolvableAddr,
) -> Result<ClientboundStatusResponse, PingError> {
- let address: ServerAddress = address.try_into().map_err(|_| PingError::InvalidAddress)?;
- let resolved_address = resolve::resolve_address(&address).await?;
- let conn = Connection::new(&resolved_address).await?;
- ping_server_with_connection(address, conn).await
+ let address = address.resolve().await?;
+ let conn = Connection::new(&address.socket).await?;
+ ping_server_with_connection(address.server, conn).await
}
/// Ping a Minecraft server through a SOCKS5 proxy.
pub async fn ping_server_with_proxy(
- address: impl TryInto<ServerAddress>,
+ address: impl ResolvableAddr,
proxy: Proxy,
) -> Result<ClientboundStatusResponse, PingError> {
- let address: ServerAddress = address.try_into().map_err(|_| PingError::InvalidAddress)?;
- let resolved_address = resolve::resolve_address(&address).await?;
- let conn = Connection::new_with_proxy(&resolved_address, proxy).await?;
- ping_server_with_connection(address, conn).await
+ let address = address.resolve().await?;
+ let conn = Connection::new_with_proxy(&address.socket, proxy).await?;
+ ping_server_with_connection(address.server, conn).await
}
/// Ping a Minecraft server after we've already created a [`Connection`].
@@ -72,7 +70,7 @@ pub async fn ping_server_with_proxy(
/// The `Connection` must still be in the handshake state (which is the state
/// it's in immediately after it's created).
pub async fn ping_server_with_connection(
- address: ServerAddress,
+ address: ServerAddr,
mut conn: Connection<ClientboundHandshakePacket, ServerboundHandshakePacket>,
) -> Result<ClientboundStatusResponse, PingError> {
// send the client intention packet and switch to the status state
diff --git a/azalea-client/src/plugins/join.rs b/azalea-client/src/plugins/join.rs
index 538369b0..f1e27165 100644
--- a/azalea-client/src/plugins/join.rs
+++ b/azalea-client/src/plugins/join.rs
@@ -1,8 +1,8 @@
-use std::{net::SocketAddr, sync::Arc};
+use std::sync::Arc;
use azalea_entity::{LocalEntity, indexing::EntityUuidIndex};
use azalea_protocol::{
- ServerAddress,
+ address::ResolvedAddr,
common::client_information::ClientInformation,
connect::{Connection, ConnectionError, Proxy},
packets::{
@@ -59,16 +59,11 @@ pub struct StartJoinServerEvent {
/// Options for how the connection to the server will be made.
///
-/// These are persisted on reconnects.
-///
-/// This is inserted as a component on clients to make auto-reconnecting work.
+/// These are persisted on reconnects. This is inserted as a component on
+/// clients to make auto-reconnecting work.
#[derive(Debug, Clone, Component)]
pub struct ConnectOpts {
- /// The unresolved address that we're going to tell the server that we used
- /// to connect.
- pub address: ServerAddress,
- /// The actual IP and port that we're going to make a connection to.
- pub resolved_address: SocketAddr,
+ pub address: ResolvedAddr,
/// The SOCKS5 proxy used for connecting to the Minecraft server.
pub server_proxy: Option<Proxy>,
/// The SOCKS5 proxy that will be used when authenticating our server join
@@ -172,15 +167,15 @@ async fn create_conn_and_send_intention_packet(
opts: ConnectOpts,
) -> Result<LoginConn, ConnectionError> {
let mut conn = if let Some(proxy) = opts.server_proxy {
- Connection::new_with_proxy(&opts.resolved_address, proxy).await?
+ Connection::new_with_proxy(&opts.address.socket, proxy).await?
} else {
- Connection::new(&opts.resolved_address).await?
+ Connection::new(&opts.address.socket).await?
};
conn.write(ServerboundIntention {
protocol_version: PROTOCOL_VERSION,
- hostname: opts.address.host.clone(),
- port: opts.address.port,
+ hostname: opts.address.server.host.clone(),
+ port: opts.address.server.port,
intention: ClientIntention::Login,
})
.await?;
diff --git a/azalea-protocol/src/address.rs b/azalea-protocol/src/address.rs
new file mode 100644
index 00000000..a3e487a1
--- /dev/null
+++ b/azalea-protocol/src/address.rs
@@ -0,0 +1,157 @@
+use std::{
+ fmt::{self, Debug, Display},
+ net::SocketAddr,
+ str::FromStr,
+};
+
+use hickory_resolver::ResolveError;
+
+use crate::resolve::resolve_address;
+
+/// Something that might be able to be parsed and looked up as a server address.
+///
+/// This is typically used by Azalea as a generic argument, so the user can
+/// choose to pass either a string or an already-resolved address.
+pub trait ResolvableAddr: Debug + Clone {
+ fn server_addr(self) -> Result<ServerAddr, ResolveError>;
+ fn resolve(self) -> impl Future<Output = Result<ResolvedAddr, ResolveError>> + Send;
+}
+impl<T: TryInto<ServerAddr, Error = ServerAddrParseError> + Debug + Send + Clone> ResolvableAddr
+ for T
+{
+ fn server_addr(self) -> Result<ServerAddr, ResolveError> {
+ self.try_into()
+ .map_err(|_| "failed to parse address".into())
+ }
+
+ async fn resolve(self) -> Result<ResolvedAddr, ResolveError> {
+ ResolvedAddr::new(self.server_addr()?).await
+ }
+}
+
+impl ResolvableAddr for &ResolvedAddr {
+ fn server_addr(self) -> Result<ServerAddr, ResolveError> {
+ Ok(self.server.clone())
+ }
+
+ async fn resolve(self) -> Result<ResolvedAddr, ResolveError> {
+ Ok(self.clone())
+ }
+}
+
+/// A host and port. It's possible that the port doesn't resolve to anything.
+///
+/// # Examples
+///
+/// `ServerAddr` implements TryFrom<&str>, so you can use it like this:
+/// ```
+/// use azalea_protocol::address::ServerAddr;
+///
+/// let addr = ServerAddr::try_from("localhost:25565").unwrap();
+/// assert_eq!(addr.host, "localhost");
+/// assert_eq!(addr.port, 25565);
+/// ```
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ServerAddr {
+ pub host: String,
+ pub port: u16,
+}
+
+/// An empty error type that's used when we fail to convert a type to a
+/// `ServerAddr`.
+///
+/// You usually want to use the [`ResolvableAddr`] type instead, which works
+/// with [`ResolveError`]s.
+#[derive(Debug)]
+pub struct ServerAddrParseError;
+
+impl TryFrom<&str> for ServerAddr {
+ type Error = ServerAddrParseError;
+
+ /// Convert a Minecraft server address (`host:port`, the port is optional)
+ /// to a `ServerAddress`
+ fn try_from(string: &str) -> Result<Self, Self::Error> {
+ if string.is_empty() {
+ return Err(ServerAddrParseError);
+ }
+ let mut parts = string.split(':');
+ let host = parts.next().ok_or(ServerAddrParseError)?.to_string();
+ // default the port to 25565
+ let port = parts.next().unwrap_or("25565");
+ let port = u16::from_str(port).ok().ok_or(ServerAddrParseError)?;
+ Ok(ServerAddr { host, port })
+ }
+}
+impl TryFrom<String> for ServerAddr {
+ type Error = ServerAddrParseError;
+
+ fn try_from(string: String) -> Result<Self, Self::Error> {
+ ServerAddr::try_from(string.as_str())
+ }
+}
+
+impl From<SocketAddr> for ServerAddr {
+ /// Convert an existing `SocketAddr` into a `ServerAddress`.
+ ///
+ /// This just converts the IP to a string and passes along the port. The
+ /// resolver will realize it's already an IP address and not do any DNS
+ /// requests.
+ fn from(addr: SocketAddr) -> Self {
+ ServerAddr {
+ host: addr.ip().to_string(),
+ port: addr.port(),
+ }
+ }
+}
+
+impl Display for ServerAddr {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}:{}", self.host, self.port)
+ }
+}
+
+/// Serde deserialization for ServerAddress.
+///
+/// This is useful if you're storing the server address in a config file.
+impl<'de> serde::Deserialize<'de> for ServerAddr {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let string = String::deserialize(deserializer)?;
+ ServerAddr::try_from(string.as_str())
+ .map_err(|_| serde::de::Error::custom("failed to parse address"))
+ }
+}
+
+/// Serde serialization for ServerAddress.
+///
+/// This uses the Display impl, so it will serialize to a string.
+impl serde::Serialize for ServerAddr {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ serializer.serialize_str(&self.to_string())
+ }
+}
+
+/// An address that may be used to connect to a Minecraft server.
+#[derive(Debug, Clone)]
+pub struct ResolvedAddr {
+ /// The initial address that we passed when trying to connect.
+ ///
+ /// This is necessary because clients send this to the server when they
+ /// connect.
+ pub server: ServerAddr,
+ /// The IP and port that we will actually connect to.
+ pub socket: SocketAddr,
+}
+
+impl ResolvedAddr {
+ pub async fn new(server: impl Into<ServerAddr>) -> Result<Self, ResolveError> {
+ let server = server.into();
+ let socket = resolve_address(&server).await?;
+ Ok(Self { server, socket })
+ }
+}
diff --git a/azalea-protocol/src/lib.rs b/azalea-protocol/src/lib.rs
index b481afb8..803f8630 100644
--- a/azalea-protocol/src/lib.rs
+++ b/azalea-protocol/src/lib.rs
@@ -12,12 +12,7 @@
// this is necessary for thiserror backtraces
#![feature(error_generic_member_access)]
-use std::{
- fmt::{self, Display},
- net::SocketAddr,
- str::FromStr,
-};
-
+pub mod address;
pub mod common;
#[cfg(feature = "connecting")]
pub mod connect;
@@ -27,98 +22,14 @@ pub mod resolve;
pub mod write;
#[doc(hidden)]
-#[deprecated(note = "Renamed to resolve")]
+#[deprecated(note = "renamed to `resolve`.")]
pub mod resolver {
pub use super::resolve::*;
}
-/// A host and port. It's possible that the port doesn't resolve to anything.
-///
-/// # Examples
-///
-/// `ServerAddress` implements TryFrom<&str>, so you can use it like this:
-/// ```
-/// use azalea_protocol::ServerAddress;
-///
-/// let addr = ServerAddress::try_from("localhost:25565").unwrap();
-/// assert_eq!(addr.host, "localhost");
-/// assert_eq!(addr.port, 25565);
-/// ```
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct ServerAddress {
- pub host: String,
- pub port: u16,
-}
-
-impl TryFrom<&str> for ServerAddress {
- type Error = String;
-
- /// Convert a Minecraft server address (host:port, the port is optional) to
- /// a `ServerAddress`
- fn try_from(string: &str) -> Result<Self, Self::Error> {
- if string.is_empty() {
- return Err("Empty string".to_string());
- }
- let mut parts = string.split(':');
- let host = parts.next().ok_or("No host specified")?.to_string();
- // default the port to 25565
- let port = parts.next().unwrap_or("25565");
- let port = u16::from_str(port).map_err(|_| "Invalid port specified")?;
- Ok(ServerAddress { host, port })
- }
-}
-impl TryFrom<String> for ServerAddress {
- type Error = String;
-
- fn try_from(string: String) -> Result<Self, Self::Error> {
- ServerAddress::try_from(string.as_str())
- }
-}
-
-impl From<SocketAddr> for ServerAddress {
- /// Convert an existing `SocketAddr` into a `ServerAddress`.
- ///
- /// This just converts the IP to a string and passes along the port. The
- /// resolver will realize it's already an IP address and not do any DNS
- /// requests.
- fn from(addr: SocketAddr) -> Self {
- ServerAddress {
- host: addr.ip().to_string(),
- port: addr.port(),
- }
- }
-}
-
-impl Display for ServerAddress {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{}:{}", self.host, self.port)
- }
-}
-
-/// Serde deserialization for ServerAddress.
-///
-/// This is useful if you're storing the server address in a config file.
-impl<'de> serde::Deserialize<'de> for ServerAddress {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: serde::Deserializer<'de>,
- {
- let string = String::deserialize(deserializer)?;
- ServerAddress::try_from(string.as_str()).map_err(serde::de::Error::custom)
- }
-}
-
-/// Serde serialization for ServerAddress.
-///
-/// This uses the Display impl, so it will serialize to a string.
-impl serde::Serialize for ServerAddress {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: serde::Serializer,
- {
- serializer.serialize_str(&self.to_string())
- }
-}
+#[doc(hidden)]
+#[deprecated(note = "moved to `address::ServerAddr`.")]
+pub type ServerAddress = address::ServerAddr;
#[cfg(test)]
mod tests {
diff --git a/azalea-protocol/src/resolve.rs b/azalea-protocol/src/resolve.rs
index 468371aa..92939c8c 100644
--- a/azalea-protocol/src/resolve.rs
+++ b/azalea-protocol/src/resolve.rs
@@ -8,7 +8,7 @@ use std::{
pub use hickory_resolver::ResolveError;
use hickory_resolver::{Name, TokioResolver, name_server::TokioConnectionProvider};
-use crate::ServerAddress;
+use crate::address::ServerAddr;
#[doc(hidden)]
#[deprecated(note = "Renamed to ResolveError")]
@@ -23,7 +23,7 @@ static RESOLVER: LazyLock<TokioResolver> = LazyLock::new(|| {
/// Resolve a Minecraft server address into an IP address and port.
///
/// If it's already an IP address, it's returned as-is.
-pub async fn resolve_address(mut address: &ServerAddress) -> Result<SocketAddr, ResolveError> {
+pub async fn resolve_address(mut address: &ServerAddr) -> Result<SocketAddr, ResolveError> {
let redirect = resolve_srv_redirect(address).await;
if let Ok(redirect_target) = &redirect {
address = redirect_target;
@@ -32,7 +32,7 @@ pub async fn resolve_address(mut address: &ServerAddress) -> Result<SocketAddr,
resolve_ip_without_redirects(address).await
}
-async fn resolve_ip_without_redirects(address: &ServerAddress) -> Result<SocketAddr, ResolveError> {
+async fn resolve_ip_without_redirects(address: &ServerAddr) -> Result<SocketAddr, ResolveError> {
if let Ok(ip) = address.host.parse::<IpAddr>() {
// no need to do a lookup
return Ok(SocketAddr::new(ip, address.port));
@@ -51,7 +51,7 @@ async fn resolve_ip_without_redirects(address: &ServerAddress) -> Result<SocketA
Ok(SocketAddr::new(ip, address.port))
}
-async fn resolve_srv_redirect(address: &ServerAddress) -> Result<ServerAddress, ResolveError> {
+async fn resolve_srv_redirect(address: &ServerAddr) -> Result<ServerAddr, ResolveError> {
if address.port != 25565 {
return Err(ResolveError::from("Port must be 25565 to do a SRV lookup"));
}
@@ -63,7 +63,7 @@ async fn resolve_srv_redirect(address: &ServerAddress) -> Result<ServerAddress,
.iter()
.next()
.ok_or(ResolveError::from("No SRV record found"))?;
- Ok(ServerAddress {
+ Ok(ServerAddr {
host: srv.target().to_ascii(),
port: srv.port(),
})
diff --git a/azalea/README.md b/azalea/README.md
index 3055b0c0..6b5c2601 100644
--- a/azalea/README.md
+++ b/azalea/README.md
@@ -43,7 +43,7 @@ use azalea::prelude::*;
use parking_lot::Mutex;
#[tokio::main]
-async fn main() {
+async fn main() -> AppExit {
let account = Account::offline("bot");
// or Account::microsoft("example@example.com").await.unwrap();
@@ -51,7 +51,6 @@ async fn main() {
.set_handler(handle)
.start(account, "localhost")
.await
- .unwrap();
}
#[derive(Default, Clone, Component)]
diff --git a/azalea/examples/echo.rs b/azalea/examples/echo.rs
index a2219008..ed013a66 100644
--- a/azalea/examples/echo.rs
+++ b/azalea/examples/echo.rs
@@ -3,7 +3,7 @@
use azalea::prelude::*;
#[tokio::main]
-async fn main() {
+async fn main() -> AppExit {
let account = Account::offline("bot");
// or let account = Account::microsoft("email").await.unwrap();
@@ -11,7 +11,6 @@ async fn main() {
.set_handler(handle)
.start(account, "localhost")
.await
- .unwrap();
}
#[derive(Default, Clone, Component)]
diff --git a/azalea/examples/nearest_entity.rs b/azalea/examples/nearest_entity.rs
index 19223589..a0385917 100644
--- a/azalea/examples/nearest_entity.rs
+++ b/azalea/examples/nearest_entity.rs
@@ -2,6 +2,7 @@ use azalea::{
ClientBuilder,
bot::{Bot, LookAtEvent},
nearest_entity::EntityFinder,
+ prelude::*,
};
use azalea_client::Account;
use azalea_core::tick::GameTick;
@@ -18,14 +19,13 @@ use bevy_ecs::{
};
#[tokio::main]
-async fn main() {
+async fn main() -> AppExit {
let account = Account::offline("bot");
ClientBuilder::new()
.add_plugins(LookAtStuffPlugin)
.start(account, "localhost")
.await
- .unwrap();
}
pub struct LookAtStuffPlugin;
diff --git a/azalea/examples/steal.rs b/azalea/examples/steal.rs
index c928545f..99254d51 100644
--- a/azalea/examples/steal.rs
+++ b/azalea/examples/steal.rs
@@ -8,7 +8,7 @@ use azalea_registry::builtin::{BlockKind, ItemKind};
use parking_lot::Mutex;
#[tokio::main]
-async fn main() {
+async fn main() -> AppExit {
let account = Account::offline("bot");
// or let bot = Account::microsoft("email").await.unwrap();
@@ -16,7 +16,6 @@ async fn main() {
.set_handler(handle)
.start(account, "localhost")
.await
- .unwrap();
}
#[derive(Default, Clone, Component)]
diff --git a/azalea/examples/testbot/main.rs b/azalea/examples/testbot/main.rs
index 6adb782c..2c282f07 100644
--- a/azalea/examples/testbot/main.rs
+++ b/azalea/examples/testbot/main.rs
@@ -33,7 +33,7 @@ use commands::{CommandSource, register_commands};
use parking_lot::Mutex;
#[tokio::main]
-async fn main() {
+async fn main() -> AppExit {
let args = parse_args();
thread::spawn(deadlock_detection_thread);
@@ -65,7 +65,6 @@ async fn main() {
})
.start(join_address)
.await
- .unwrap();
}
/// Runs a loop that checks for deadlocks every 10 seconds.
diff --git a/azalea/examples/todo/craft_dig_straight_down.rs b/azalea/examples/todo/craft_dig_straight_down.rs
index 0d9961d4..a76c1ffe 100644
--- a/azalea/examples/todo/craft_dig_straight_down.rs
+++ b/azalea/examples/todo/craft_dig_straight_down.rs
@@ -9,7 +9,7 @@ struct State {
}
#[tokio::main]
-async fn main() {
+async fn main() -> AppExit {
let account = Account::offline("bot");
// or let bot = Account::microsoft("email").await;
@@ -17,7 +17,6 @@ async fn main() {
.set_handler(handle)
.start(account, "localhost")
.await
- .unwrap();
}
async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> {
diff --git a/azalea/examples/todo/mine_a_chunk.rs b/azalea/examples/todo/mine_a_chunk.rs
index 0c439f26..6f6606f4 100644
--- a/azalea/examples/todo/mine_a_chunk.rs
+++ b/azalea/examples/todo/mine_a_chunk.rs
@@ -1,7 +1,7 @@
use azalea::{prelude::*, swarm::prelude::*};
#[tokio::main]
-async fn main() {
+async fn main() -> AppExit {
let mut accounts = Vec::new();
let mut states = Vec::new();
@@ -16,7 +16,6 @@ async fn main() {
.set_swarm_handler(swarm_handle)
.start("localhost")
.await
- .unwrap();
}
#[derive(Default, Clone, Component)]
diff --git a/azalea/examples/todo/pvp.rs b/azalea/examples/todo/pvp.rs
index d85278e8..9121444c 100644
--- a/azalea/examples/todo/pvp.rs
+++ b/azalea/examples/todo/pvp.rs
@@ -6,7 +6,7 @@ use azalea::{
};
#[tokio::main]
-async fn main() {
+async fn main() -> AppExit {
let mut accounts = Vec::new();
let mut states = Vec::new();
@@ -22,7 +22,6 @@ async fn main() {
.join_delay(Duration::from_millis(1000))
.start("localhost")
.await
- .unwrap();
}
#[derive(Component, Default, Clone)]
diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs
index 8440e31f..45c0114d 100644
--- a/azalea/src/lib.rs
+++ b/azalea/src/lib.rs
@@ -37,6 +37,7 @@ pub use azalea_core::position::{BlockPos, Vec3};
pub use azalea_entity as entity;
pub use azalea_physics as physics;
pub use azalea_protocol as protocol;
+use azalea_protocol::address::{ResolvableAddr, ServerAddr};
pub use azalea_registry as registry;
#[doc(hidden)]
#[deprecated(note = "renamed to `Identifier`.")]
@@ -48,9 +49,8 @@ use bevy_app::AppExit;
pub use bevy_ecs as ecs;
use ecs::component::Component;
use futures::{Future, future::BoxFuture};
-use protocol::{ServerAddress, connect::Proxy, resolve::ResolveError};
+use protocol::connect::Proxy;
use swarm::SwarmBuilder;
-use thiserror::Error;
use crate::bot::DefaultBotPlugins;
@@ -58,15 +58,6 @@ pub type BoxHandleFn<S, R> =
Box<dyn Fn(Client, azalea_client::Event, S) -> BoxFuture<'static, R> + Send>;
pub type HandleFn<S, Fut> = fn(Client, azalea_client::Event, S) -> Fut;
-/// An error related to resolving the server address when starting a client.
-#[derive(Error, Debug)]
-pub enum StartError {
- #[error("Invalid address")]
- InvalidAddress,
- #[error(transparent)]
- ResolveAddress(#[from] ResolveError),
-}
-
/// A builder for creating new [`Client`]s. This is the recommended way of
/// making a bot.
///
@@ -217,20 +208,17 @@ where
///
/// If the client can't join, it'll keep retrying forever until it can.
///
- /// The `address` argument can be a `&str`, [`ServerAddress`], or anything
- /// that implements `TryInto<ServerAddress>`.
+ /// The `address` argument can be a `&str`, [`ServerAddr`],
+ /// [`ResolvedAddr`], or anything else that implements [`ResolvableAddr`].
///
/// # Errors
///
/// This will error if the given address is invalid or couldn't be resolved
/// to a Minecraft server.
///
- /// [`ServerAddress`]: azalea_protocol::ServerAddress
- pub async fn start(
- mut self,
- account: Account,
- address: impl TryInto<ServerAddress>,
- ) -> Result<AppExit, StartError> {
+ /// [`ServerAddr`]: azalea_protocol::address::ServerAddr
+ /// [`ResolvedAddr`]: azalea_protocol::address::ResolvedAddr
+ pub async fn start(mut self, account: Account, address: impl ResolvableAddr) -> AppExit {
self.swarm.accounts = vec![(account, JoinOpts::default())];
if self.swarm.states.is_empty() {
self.swarm.states = vec![S::default()];
@@ -243,14 +231,14 @@ where
pub async fn start_with_opts(
mut self,
account: Account,
- address: impl TryInto<ServerAddress>,
+ address: impl ResolvableAddr,
opts: JoinOpts,
- ) -> Result<AppExit, StartError> {
+ ) -> AppExit {
self.swarm.accounts = vec![(account, opts.clone())];
if self.swarm.states.is_empty() {
self.swarm.states = vec![S::default()];
}
- self.swarm.start_with_default_opts(address, opts).await
+ self.swarm.start_with_opts(address, opts).await
}
}
impl Default for ClientBuilder<NoState, ()> {
@@ -287,10 +275,12 @@ pub struct JoinOpts {
pub sessionserver_proxy: Option<Proxy>,
/// Override the server address that this specific bot will send in the
/// handshake packet.
- pub custom_address: Option<ServerAddress>,
- /// Override the socket address that this specific bot will use to connect
+ #[doc(alias = "custom_address")]
+ pub custom_server_addr: Option<ServerAddr>,
+ /// Override the IP and port that this specific bot will use to connect
/// to the server.
- pub custom_resolved_address: Option<SocketAddr>,
+ #[doc(alias = "custom_resolved_address")]
+ pub custom_socket_addr: Option<SocketAddr>,
}
impl JoinOpts {
@@ -305,11 +295,11 @@ impl JoinOpts {
if let Some(proxy) = other.sessionserver_proxy.clone() {
self.sessionserver_proxy = Some(proxy);
}
- if let Some(custom_address) = other.custom_address.clone() {
- self.custom_address = Some(custom_address);
+ if let Some(custom_server_addr) = other.custom_server_addr.clone() {
+ self.custom_server_addr = Some(custom_server_addr);
}
- if let Some(custom_resolved_address) = other.custom_resolved_address {
- self.custom_resolved_address = Some(custom_resolved_address);
+ if let Some(custom_socket_addr) = other.custom_socket_addr {
+ self.custom_socket_addr = Some(custom_socket_addr);
}
}
@@ -347,15 +337,26 @@ impl JoinOpts {
/// Set the custom address that this bot will send in the handshake packet.
#[must_use]
- pub fn custom_address(mut self, custom_address: ServerAddress) -> Self {
- self.custom_address = Some(custom_address);
+ pub fn custom_server_addr(mut self, server_addr: ServerAddr) -> Self {
+ self.custom_server_addr = Some(server_addr);
self
}
/// Set the custom resolved address that this bot will use to connect to the
/// server.
#[must_use]
- pub fn custom_resolved_address(mut self, custom_resolved_address: SocketAddr) -> Self {
- self.custom_resolved_address = Some(custom_resolved_address);
+ pub fn custom_socket_addr(mut self, socket_addr: SocketAddr) -> Self {
+ self.custom_socket_addr = Some(socket_addr);
self
}
+
+ #[doc(hidden)]
+ #[deprecated = "renamed to `custom_server_addr`."]
+ pub fn custom_address(self, server_addr: ServerAddr) -> Self {
+ self.custom_server_addr(server_addr)
+ }
+ #[doc(hidden)]
+ #[deprecated = "renamed to `custom_socket_addr`."]
+ pub fn custom_resolved_address(self, socket_addr: SocketAddr) -> Self {
+ self.custom_socket_addr(socket_addr)
+ }
}
diff --git a/azalea/src/prelude.rs b/azalea/src/prelude.rs
index 969b1e86..335244cc 100644
--- a/azalea/src/prelude.rs
+++ b/azalea/src/prelude.rs
@@ -3,6 +3,7 @@
pub use azalea_client::{Account, Client, Event};
pub use azalea_core::tick::GameTick;
+pub use bevy_app::AppExit;
// this is necessary to make the macros that reference bevy_ecs work
pub use crate::ecs as bevy_ecs;
diff --git a/azalea/src/swarm/mod.rs b/azalea/src/swarm/mod.rs
index feb3ff38..24e9c1b2 100644
--- a/azalea/src/swarm/mod.rs
+++ b/azalea/src/swarm/mod.rs
@@ -10,7 +10,6 @@ use std::{
collections::{HashMap, hash_map},
future::Future,
mem,
- net::SocketAddr,
sync::{
Arc,
atomic::{self, AtomicBool},
@@ -19,14 +18,14 @@ use std::{
};
use azalea_client::{
- Account, Client, DefaultPlugins, Event, JoinError, StartClientOpts,
+ Account, Client, DefaultPlugins, Event, StartClientOpts,
auto_reconnect::{AutoReconnectDelay, DEFAULT_RECONNECT_DELAY},
chat::ChatPacket,
join::ConnectOpts,
start_ecs_runner,
};
use azalea_entity::LocalEntity;
-use azalea_protocol::{ServerAddress, resolve};
+use azalea_protocol::address::{ResolvableAddr, ResolvedAddr};
use azalea_world::InstanceContainer;
use bevy_app::{App, AppExit, PluginGroup, PluginGroupBuilder, Plugins, SubApp};
use bevy_ecs::prelude::*;
@@ -35,7 +34,7 @@ use parking_lot::{Mutex, RwLock};
use tokio::{sync::mpsc, task};
use tracing::{debug, error, warn};
-use crate::{BoxHandleFn, DefaultBotPlugins, HandleFn, JoinOpts, NoState, StartError};
+use crate::{BoxHandleFn, DefaultBotPlugins, HandleFn, JoinOpts, NoState};
/// A swarm is a way to conveniently control many bots at once, while also
/// being able to control bots at an individual level when desired.
@@ -51,8 +50,7 @@ pub struct Swarm {
pub ecs_lock: Arc<Mutex<World>>,
// the address is public and mutable so plugins can change it
- pub resolved_address: Arc<RwLock<SocketAddr>>,
- pub address: Arc<RwLock<ServerAddress>>,
+ pub address: Arc<RwLock<ResolvedAddr>>,
pub instance_container: Arc<RwLock<InstanceContainer>>,
@@ -405,28 +403,31 @@ where
/// Build this `SwarmBuilder` into an actual [`Swarm`] and join the given
/// server.
///
- /// The `address` argument can be a `&str`, [`ServerAddress`], or anything
- /// that implements `TryInto<ServerAddress>`.
+ /// The `address` argument can be a `&str`, [`ServerAddr`],
+ /// [`ResolvedAddr`], or anything else that implements [`ResolvableAddr`].
///
- /// [`ServerAddress`]: azalea_protocol::ServerAddress
- pub async fn start(self, address: impl TryInto<ServerAddress>) -> Result<AppExit, StartError> {
- // convert the TryInto<ServerAddress> into a ServerAddress
- let address: ServerAddress = match address.try_into() {
- Ok(address) => address,
- Err(_) => return Err(StartError::InvalidAddress),
- };
+ /// [`ServerAddr`]: azalea_protocol::address::ServerAddr
+ pub async fn start(self, address: impl ResolvableAddr) -> AppExit {
+ self.start_with_opts(address, JoinOpts::default()).await
+ }
- self.start_with_default_opts(address, JoinOpts::default())
- .await
+ #[doc(hidden)]
+ #[deprecated = "renamed to `start_with_opts`."]
+ pub async fn start_with_default_opts(
+ self,
+ address: impl ResolvableAddr,
+ default_join_opts: JoinOpts,
+ ) -> AppExit {
+ self.start_with_opts(address, default_join_opts).await
}
/// Do the same as [`Self::start`], but allow passing in default join
/// options for the bots.
- pub async fn start_with_default_opts(
+ pub async fn start_with_opts(
mut self,
- address: impl TryInto<ServerAddress>,
- default_join_opts: JoinOpts,
- ) -> Result<AppExit, StartError> {
+ address: impl ResolvableAddr,
+ join_opts: JoinOpts,
+ ) -> AppExit {
assert_eq!(
self.accounts.len(),
self.states.len(),
@@ -435,17 +436,32 @@ where
debug!("Starting Azalea {}", env!("CARGO_PKG_VERSION"));
- // convert the TryInto<ServerAddress> into a ServerAddress
- let address = match address.try_into() {
- Ok(address) => address,
- Err(_) => return Err(StartError::InvalidAddress),
- };
+ let address = if let Some(socket_addr) = join_opts.custom_socket_addr.clone() {
+ let server_addr = if let Some(server_addr) = join_opts
+ .custom_server_addr
+ .clone()
+ .or_else(|| address.clone().server_addr().ok())
+ {
+ server_addr
+ } else {
+ error!(
+ "Failed to parse address: {address:?}. If this was expected, consider passing in a `ServerAddr` instead."
+ );
+ return AppExit::error();
+ };
- let address: ServerAddress = default_join_opts.custom_address.clone().unwrap_or(address);
- let resolved_address = if let Some(a) = default_join_opts.custom_resolved_address {
- a
+ ResolvedAddr {
+ server: server_addr,
+ socket: socket_addr,
+ }
} else {
- resolve::resolve_address(&address).await?
+ let Ok(addr) = address.clone().resolve().await else {
+ error!(
+ "Failed to resolve address: {address:?}. If this was expected, consider resolving the address earlier with `ResolvableAddr::resolve`."
+ );
+ return AppExit::error();
+ };
+ addr
};
let instance_container = Arc::new(RwLock::new(InstanceContainer::default()));
@@ -460,14 +476,13 @@ where
let local_set = task::LocalSet::new();
- let appexit = local_set.run_until(async move {
+ let app_exit = local_set.run_until(async move {
// start_ecs_runner must be run inside of the LocalSet
let (ecs_lock, start_running_systems, appexit_rx) = start_ecs_runner(&mut self.app);
let swarm = Swarm {
ecs_lock: ecs_lock.clone(),
- resolved_address: Arc::new(RwLock::new(resolved_address)),
address: Arc::new(RwLock::new(address)),
instance_container,
@@ -507,7 +522,7 @@ where
if let Some(join_delay) = join_delay {
// if there's a join delay, then join one by one
for ((account, bot_join_opts), state) in accounts.iter().zip(states) {
- let mut join_opts = default_join_opts.clone();
+ let mut join_opts = join_opts.clone();
join_opts.update(bot_join_opts);
let _ = swarm_clone.add_with_opts(account, state, &join_opts).await;
tokio::time::sleep(join_delay).await;
@@ -517,7 +532,7 @@ where
let swarm_borrow = &swarm_clone;
join_all(accounts.iter().zip(states).map(
|((account, bot_join_opts), state)| async {
- let mut join_opts = default_join_opts.clone();
+ let mut join_opts = join_opts.clone();
join_opts.update(bot_join_opts);
let _ = swarm_borrow
.clone()
@@ -603,17 +618,17 @@ where
}
});
- let appexit = appexit_rx
+ let app_exit = appexit_rx
.await
.expect("appexit_tx shouldn't be dropped by the ECS runner before sending");
swarm_handler_task.abort();
client_handler_task.abort();
- appexit
+ app_exit
}).await;
- Ok(appexit)
+ app_exit
}
}
@@ -667,7 +682,7 @@ pub type BoxSwarmHandleFn<SS, R> =
/// struct SwarmState {}
///
/// #[tokio::main]
-/// async fn main() {
+/// async fn main() -> AppExit {
/// let mut accounts = Vec::new();
/// let mut states = Vec::new();
///
@@ -683,7 +698,6 @@ pub type BoxSwarmHandleFn<SS, R> =
/// .join_delay(Duration::from_millis(1000))
/// .start("localhost")
/// .await
-/// .unwrap();
/// }
///
/// async fn handle(bot: Client, event: Event, _state: State) -> anyhow::Result<()> {
@@ -714,11 +728,7 @@ impl Swarm {
/// # Errors
///
/// Returns an error if the server's address could not be resolved.
- pub async fn add<S: Component + Clone>(
- &self,
- account: &Account,
- state: S,
- ) -> Result<Client, JoinError> {
+ pub async fn add<S: Component + Clone>(&self, account: &Account, state: S) -> Client {
self.add_with_opts(account, state, &JoinOpts::default())
.await
}
@@ -735,19 +745,19 @@ impl Swarm {
account: &Account,
state: S,
join_opts: &JoinOpts,
- ) -> Result<Client, JoinError> {
+ ) -> Client {
debug!(
"add_with_opts called for account {} with opts {join_opts:?}",
account.username
);
- let address = join_opts
- .custom_address
- .clone()
- .unwrap_or_else(|| self.address.read().clone());
- let resolved_address = join_opts
- .custom_resolved_address
- .unwrap_or_else(|| *self.resolved_address.read());
+ let mut address = self.address.read().clone();
+ if let Some(custom_server_addr) = join_opts.custom_server_addr.clone() {
+ address.server = custom_server_addr;
+ }
+ if let Some(custom_socket_addr) = join_opts.custom_socket_addr.clone() {
+ address.socket = custom_socket_addr;
+ }
let server_proxy = join_opts.server_proxy.clone();
let sessionserver_proxy = join_opts.sessionserver_proxy.clone();
@@ -758,7 +768,6 @@ impl Swarm {
account: account.clone(),
connect_opts: ConnectOpts {
address,
- resolved_address,
server_proxy,
sessionserver_proxy,
},
@@ -780,7 +789,7 @@ impl Swarm {
rx, swarm_tx, bots_tx, cloned_bot, join_opts,
));
- Ok(client)
+ client
}
/// Copy the events from a client's receiver into bots_tx, until the bot is