1
2
Fork 0
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:
mat 2022-11-13 17:03:53 -06:00
parent 9b07178a06
commit a16ecae9c2
4 changed files with 138 additions and 59 deletions

View file

@ -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,

View file

@ -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};

View file

@ -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;

View file

@ -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