mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
start working on shared dimensions
This commit is contained in:
parent
9b07178a06
commit
a16ecae9c2
4 changed files with 138 additions and 59 deletions
|
@ -15,7 +15,10 @@ use azalea_protocol::{
|
||||||
serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket,
|
serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket,
|
||||||
ClientboundGamePacket, ServerboundGamePacket,
|
ClientboundGamePacket, ServerboundGamePacket,
|
||||||
},
|
},
|
||||||
handshake::client_intention_packet::ClientIntentionPacket,
|
handshake::{
|
||||||
|
client_intention_packet::ClientIntentionPacket, ClientboundHandshakePacket,
|
||||||
|
ServerboundHandshakePacket,
|
||||||
|
},
|
||||||
login::{
|
login::{
|
||||||
serverbound_custom_query_packet::ServerboundCustomQueryPacket,
|
serverbound_custom_query_packet::ServerboundCustomQueryPacket,
|
||||||
serverbound_hello_packet::ServerboundHelloPacket,
|
serverbound_hello_packet::ServerboundHelloPacket,
|
||||||
|
@ -88,6 +91,8 @@ pub struct Client {
|
||||||
pub write_conn: Arc<tokio::sync::Mutex<WriteConnection<ServerboundGamePacket>>>,
|
pub write_conn: Arc<tokio::sync::Mutex<WriteConnection<ServerboundGamePacket>>>,
|
||||||
pub player: Arc<RwLock<Player>>,
|
pub player: Arc<RwLock<Player>>,
|
||||||
pub dimension: Arc<RwLock<Dimension>>,
|
pub dimension: Arc<RwLock<Dimension>>,
|
||||||
|
/// Whether there's multiple clients sharing the same dimension.
|
||||||
|
pub dimension_shared: Arc<RwLock<bool>>,
|
||||||
pub physics_state: Arc<Mutex<PhysicsState>>,
|
pub physics_state: Arc<Mutex<PhysicsState>>,
|
||||||
pub client_information: Arc<RwLock<ClientInformation>>,
|
pub client_information: Arc<RwLock<ClientInformation>>,
|
||||||
/// Plugins are a way for other crates to add custom functionality to the
|
/// Plugins are a way for other crates to add custom functionality to the
|
||||||
|
@ -141,6 +146,36 @@ pub enum HandleError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
|
/// Create a new client from the given game profile, conn in the game
|
||||||
|
/// state, and dimension. You should only use this if you want to change
|
||||||
|
/// these fields from the defaults, otherwise use `Client::join`.
|
||||||
|
pub fn new(
|
||||||
|
game_profile: GameProfile,
|
||||||
|
conn: Connection<ClientboundGamePacket, ServerboundGamePacket>,
|
||||||
|
dimension: Option<Arc<RwLock<Dimension>>>,
|
||||||
|
) -> Self {
|
||||||
|
let (read_conn, write_conn) = conn.into_split();
|
||||||
|
let (read_conn, write_conn) = (
|
||||||
|
Arc::new(tokio::sync::Mutex::new(read_conn)),
|
||||||
|
Arc::new(tokio::sync::Mutex::new(write_conn)),
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
game_profile,
|
||||||
|
read_conn,
|
||||||
|
write_conn,
|
||||||
|
player: Arc::new(RwLock::new(Player::default())),
|
||||||
|
dimension_shared: Arc::new(RwLock::new(dimension.is_some())),
|
||||||
|
dimension: dimension.unwrap_or_else(|| Arc::new(RwLock::new(Dimension::default()))),
|
||||||
|
physics_state: Arc::new(Mutex::new(PhysicsState::default())),
|
||||||
|
client_information: Arc::new(
|
||||||
|
RwLock::new(ServerboundClientInformationPacket::default()),
|
||||||
|
),
|
||||||
|
plugins: Arc::new(Plugins::new()),
|
||||||
|
tasks: Arc::new(Mutex::new(Vec::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Connect to a Minecraft server.
|
/// Connect to a Minecraft server.
|
||||||
///
|
///
|
||||||
/// To change the render distance and other settings, use
|
/// To change the render distance and other settings, use
|
||||||
|
@ -167,8 +202,35 @@ impl Client {
|
||||||
let address: ServerAddress = address.try_into().map_err(|_| JoinError::InvalidAddress)?;
|
let address: ServerAddress = address.try_into().map_err(|_| JoinError::InvalidAddress)?;
|
||||||
let resolved_address = resolver::resolve_address(&address).await?;
|
let resolved_address = resolver::resolve_address(&address).await?;
|
||||||
|
|
||||||
let mut conn = Connection::new(&resolved_address).await?;
|
let conn = Connection::new(&resolved_address).await?;
|
||||||
|
let (conn, game_profile) = Self::handshake(conn, account, address).await?;
|
||||||
|
|
||||||
|
let (tx, rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
// we got the GameConnection, so the server is now connected :)
|
||||||
|
let client = Client::new(game_profile, conn, None);
|
||||||
|
|
||||||
|
tx.send(Event::Initialize).unwrap();
|
||||||
|
|
||||||
|
// just start up the game loop and we're ready!
|
||||||
|
|
||||||
|
client.start_tasks(tx);
|
||||||
|
|
||||||
|
Ok((client, rx))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do a handshake with the server and get to the game state from the initial handshake state.
|
||||||
|
pub async fn handshake(
|
||||||
|
mut conn: Connection<ClientboundHandshakePacket, ServerboundHandshakePacket>,
|
||||||
|
account: &Account,
|
||||||
|
address: ServerAddress,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
Connection<ClientboundGamePacket, ServerboundGamePacket>,
|
||||||
|
GameProfile,
|
||||||
|
),
|
||||||
|
JoinError,
|
||||||
|
> {
|
||||||
// handshake
|
// handshake
|
||||||
conn.write(
|
conn.write(
|
||||||
ClientIntentionPacket {
|
ClientIntentionPacket {
|
||||||
|
@ -248,45 +310,7 @@ impl Client {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (read_conn, write_conn) = conn.into_split();
|
Ok((conn, game_profile))
|
||||||
|
|
||||||
let read_conn = Arc::new(tokio::sync::Mutex::new(read_conn));
|
|
||||||
let write_conn = Arc::new(tokio::sync::Mutex::new(write_conn));
|
|
||||||
|
|
||||||
let (tx, rx) = mpsc::unbounded_channel();
|
|
||||||
|
|
||||||
// we got the GameConnection, so the server is now connected :)
|
|
||||||
let client = Client {
|
|
||||||
game_profile,
|
|
||||||
read_conn,
|
|
||||||
write_conn,
|
|
||||||
player: Arc::new(RwLock::new(Player::default())),
|
|
||||||
dimension: Arc::new(RwLock::new(Dimension::default())),
|
|
||||||
physics_state: Arc::new(Mutex::new(PhysicsState::default())),
|
|
||||||
client_information: Arc::new(RwLock::new(ClientInformation::default())),
|
|
||||||
// The plugins can be modified by the user by replacing the plugins
|
|
||||||
// field right after this. No Mutex so the user doesn't need to .lock().
|
|
||||||
plugins: Arc::new(Plugins::new()),
|
|
||||||
tasks: Arc::new(Mutex::new(Vec::new())),
|
|
||||||
};
|
|
||||||
|
|
||||||
tx.send(Event::Initialize).unwrap();
|
|
||||||
|
|
||||||
// just start up the game loop and we're ready!
|
|
||||||
|
|
||||||
// if you get an error right here that means you're doing something with locks wrong
|
|
||||||
// read the error to see where the issue is
|
|
||||||
// you might be able to just drop the lock or put it in its own scope to fix
|
|
||||||
{
|
|
||||||
let mut tasks = client.tasks.lock();
|
|
||||||
tasks.push(tokio::spawn(Self::protocol_loop(
|
|
||||||
client.clone(),
|
|
||||||
tx.clone(),
|
|
||||||
)));
|
|
||||||
tasks.push(tokio::spawn(Self::game_tick_loop(client.clone(), tx)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((client, rx))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a packet directly to the server.
|
/// Write a packet directly to the server.
|
||||||
|
@ -305,6 +329,21 @@ impl Client {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Start the protocol and game tick loop.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn start_tasks(&self, tx: UnboundedSender<Event>) {
|
||||||
|
// if you get an error right here that means you're doing something with locks wrong
|
||||||
|
// read the error to see where the issue is
|
||||||
|
// you might be able to just drop the lock or put it in its own scope to fix
|
||||||
|
|
||||||
|
let mut tasks = self.tasks.lock();
|
||||||
|
tasks.push(tokio::spawn(Client::protocol_loop(
|
||||||
|
self.clone(),
|
||||||
|
tx.clone(),
|
||||||
|
)));
|
||||||
|
tasks.push(tokio::spawn(Client::game_tick_loop(self.clone(), tx)));
|
||||||
|
}
|
||||||
|
|
||||||
async fn protocol_loop(client: Client, tx: UnboundedSender<Event>) {
|
async fn protocol_loop(client: Client, tx: UnboundedSender<Event>) {
|
||||||
loop {
|
loop {
|
||||||
let r = client.read_conn.lock().await.read().await;
|
let r = client.read_conn.lock().await.read().await;
|
||||||
|
@ -313,9 +352,7 @@ impl Client {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error handling packet: {}", e);
|
error!("Error handling packet: {}", e);
|
||||||
if IGNORE_ERRORS {
|
if !IGNORE_ERRORS {
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
panic!("Error handling packet: {e}");
|
panic!("Error handling packet: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -332,7 +369,7 @@ impl Client {
|
||||||
warn!("{}", e);
|
warn!("{}", e);
|
||||||
match e {
|
match e {
|
||||||
ReadPacketError::FrameSplitter { .. } => panic!("Error: {e:?}"),
|
ReadPacketError::FrameSplitter { .. } => panic!("Error: {e:?}"),
|
||||||
_ => continue,
|
_ => {}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("{}", e);
|
panic!("{}", e);
|
||||||
|
@ -350,7 +387,7 @@ impl Client {
|
||||||
tx.send(Event::Packet(Box::new(packet.clone()))).unwrap();
|
tx.send(Event::Packet(Box::new(packet.clone()))).unwrap();
|
||||||
match packet {
|
match packet {
|
||||||
ClientboundGamePacket::Login(p) => {
|
ClientboundGamePacket::Login(p) => {
|
||||||
debug!("Got login packet {:?}", p);
|
debug!("Got login packet");
|
||||||
|
|
||||||
{
|
{
|
||||||
// // write p into login.txt
|
// // write p into login.txt
|
||||||
|
@ -414,9 +451,31 @@ impl Client {
|
||||||
.expect("min_y tag is not an int");
|
.expect("min_y tag is not an int");
|
||||||
|
|
||||||
let mut dimension_lock = client.dimension.write();
|
let mut dimension_lock = client.dimension.write();
|
||||||
// the 16 here is our render distance
|
|
||||||
// i'll make this an actual setting later
|
if *client.dimension_shared.read() {
|
||||||
*dimension_lock = Dimension::new(16, height, min_y);
|
// we can't clear the dimension if it's shared, so just
|
||||||
|
// make sure the height and stuff is correct
|
||||||
|
if dimension_lock.height() != height {
|
||||||
|
error!(
|
||||||
|
"Shared dimension height mismatch: {} != {}",
|
||||||
|
dimension_lock.height(),
|
||||||
|
height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if dimension_lock.min_y() != min_y {
|
||||||
|
error!(
|
||||||
|
"Shared dimension min_y mismatch: {} != {}",
|
||||||
|
dimension_lock.min_y(),
|
||||||
|
min_y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*dimension_lock = Dimension::new(
|
||||||
|
client.client_information.read().view_distance.into(),
|
||||||
|
height,
|
||||||
|
min_y,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let entity = EntityData::new(
|
let entity = EntityData::new(
|
||||||
client.game_profile.uuid,
|
client.game_profile.uuid,
|
||||||
|
|
|
@ -18,7 +18,7 @@ mod player;
|
||||||
mod plugins;
|
mod plugins;
|
||||||
|
|
||||||
pub use account::Account;
|
pub use account::Account;
|
||||||
pub use client::{ChatPacket, Client, ClientInformation, Event, JoinError};
|
pub use client::{ChatPacket, Client, ClientInformation, Event, JoinError, PhysicsState};
|
||||||
pub use movement::{SprintDirection, WalkDirection};
|
pub use movement::{SprintDirection, WalkDirection};
|
||||||
pub use player::Player;
|
pub use player::Player;
|
||||||
pub use plugins::{Plugin, Plugins};
|
pub use plugins::{Plugin, Plugins};
|
||||||
|
|
|
@ -76,6 +76,7 @@
|
||||||
//! [`azalea_client`]: https://crates.io/crates/azalea-client
|
//! [`azalea_client`]: https://crates.io/crates/azalea-client
|
||||||
|
|
||||||
#![feature(trait_upcasting)]
|
#![feature(trait_upcasting)]
|
||||||
|
#![feature(async_closure)]
|
||||||
#![allow(incomplete_features)]
|
#![allow(incomplete_features)]
|
||||||
|
|
||||||
mod bot;
|
mod bot;
|
||||||
|
|
|
@ -3,19 +3,23 @@ mod plugins;
|
||||||
pub use self::plugins::*;
|
pub use self::plugins::*;
|
||||||
use crate::{bot, HandleFn};
|
use crate::{bot, HandleFn};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use azalea_client::{Account, Client, Event, JoinError, Plugin, Plugins};
|
use azalea_client::{
|
||||||
|
Account, Client, ClientInformation, Event, JoinError, PhysicsState, Player, Plugin, Plugins,
|
||||||
|
};
|
||||||
use azalea_protocol::{
|
use azalea_protocol::{
|
||||||
|
connect::Connection,
|
||||||
resolver::{self, ResolverError},
|
resolver::{self, ResolverError},
|
||||||
ServerAddress,
|
ServerAddress,
|
||||||
};
|
};
|
||||||
|
use azalea_world::Dimension;
|
||||||
use futures::{
|
use futures::{
|
||||||
future::{select_all, try_join_all},
|
future::{select_all, try_join_all},
|
||||||
FutureExt,
|
FutureExt,
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::{Mutex, RwLock};
|
||||||
use std::{any::Any, future::Future, sync::Arc};
|
use std::{any::Any, future::Future, sync::Arc};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::sync::mpsc::UnboundedReceiver;
|
use tokio::sync::mpsc::{self, UnboundedReceiver};
|
||||||
|
|
||||||
/// A helper macro that generates a [`Plugins`] struct from a list of objects
|
/// A helper macro that generates a [`Plugins`] struct from a list of objects
|
||||||
/// that implement [`Plugin`].
|
/// that implement [`Plugin`].
|
||||||
|
@ -115,13 +119,28 @@ pub async fn start_swarm<
|
||||||
// resolve the address
|
// resolve the address
|
||||||
let address: ServerAddress = address.try_into().map_err(|_| JoinError::InvalidAddress)?;
|
let address: ServerAddress = address.try_into().map_err(|_| JoinError::InvalidAddress)?;
|
||||||
let resolved_address = resolver::resolve_address(&address).await?;
|
let resolved_address = resolver::resolve_address(&address).await?;
|
||||||
|
let address_borrow = &address;
|
||||||
|
|
||||||
let bots = try_join_all(
|
let shared_dimension = Arc::new(RwLock::new(Dimension::default()));
|
||||||
options
|
let shared_dimension_borrow = &shared_dimension;
|
||||||
.accounts
|
|
||||||
.iter()
|
let bots: Vec<(Client, UnboundedReceiver<Event>)> = try_join_all(options.accounts.iter().map(
|
||||||
.map(|account| Client::join(&account, resolved_address)),
|
async move |account| -> Result<(Client, UnboundedReceiver<Event>), JoinError> {
|
||||||
)
|
let conn = Connection::new(&resolved_address).await?;
|
||||||
|
let (conn, game_profile) =
|
||||||
|
Client::handshake(conn, account, address_borrow.clone()).await?;
|
||||||
|
|
||||||
|
let (tx, rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
let client = Client::new(game_profile, conn, Some(shared_dimension_borrow.clone()));
|
||||||
|
|
||||||
|
tx.send(Event::Initialize).unwrap();
|
||||||
|
|
||||||
|
client.start_tasks(tx);
|
||||||
|
|
||||||
|
Ok((client, rx))
|
||||||
|
},
|
||||||
|
))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// extract it into two different vecs
|
// extract it into two different vecs
|
||||||
|
|
Loading…
Add table
Reference in a new issue