aboutsummaryrefslogtreecommitdiff
path: root/azalea-client/src/client.rs
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2022-11-27 16:25:07 -0600
committerGitHub <noreply@github.com>2022-11-27 16:25:07 -0600
commit631ed63dbdc7167df4de02a55b5c2ef1cea909e9 (patch)
tree104e567c332f2aeb30ea6acefef8c73f9b2f158b /azalea-client/src/client.rs
parent962b9fcaae917c7e5bef718469fba31f6ff7c3cb (diff)
downloadazalea-drasl-631ed63dbdc7167df4de02a55b5c2ef1cea909e9.tar.xz
Swarm (#36)
* make azalea-pathfinder dir * start writing d* lite impl * more work on d* lite * work more on implementing d* lite * full d* lite impl * updated edges * add next() function * add NoPathError * why does dstar lite not work * fix d* lite implementation * make the test actually check the coords * replace while loop with if statement * fix clippy complaints * make W only have to be PartialOrd * fix PartialOrd issues * implement mtd* lite * add a test to mtd* lite * remove normal d* lite * make heuristic only take in one arg * add `success` function * Update README.md * evil black magic to make .entity not need dimension * start adding moves * slightly improve the vec3/position situation new macro that implements all the useful functions * moves stuff * make it compile * update deps in az-pathfinder * make it compile again * more pathfinding stuff * add Bot::look_at * replace EntityMut and EntityRef with just Entity * block pos pathfinding stuff * rename movedirection to walkdirection * execute path every tick * advance path * change az-pf version * make azalea_client keep plugin state * fix Plugins::get * why does it think there is air * start debugging incorrect air * update some From methods to use rem_euclid * start adding swarm * fix deadlock i still don't understand why it was happening but the solution was to keep the Client::player lock for shorter so it didn't overlap with the Client::dimension lock * make lookat actually work probably * fix going too fast * Update main.rs * make a thing immutable * direction_looking_at * fix rotations * import swarm in an example * fix stuff from merge * remove azalea_pathfinder import * delete azalea-pathfinder crate already in azalea::pathfinder module * swarms * start working on shared dimensions * Shared worlds work * start adding Swarm::add_account * add_account works * change "client" to "bot" in some places * Fix issues from merge * Update world.rs * add SwarmEvent::Disconnect(Account) * almost add SwarmEvent::Chat and new plugin system it panics rn * make plugins have to provide the State associated type * improve comments * make fn build slightly cleaner * fix SwarmEvent::Chat * change a println in bot/main.rs * Client::shutdown -> disconnect * polish fix clippy warnings + improve some docs a bit * fix shared worlds* *there's a bug that entities and bots will have their positions exaggerated because the relative movement packet is applied for every entity once per bot * i am being trolled by rust for some reason some stuff is really slow for literally no reason and it makes no sense i am going insane * make world an RwLock again * remove debug messages * fix skipping event ticks unfortunately now sending events is `.send().await?` instead of just `.send()` * fix deadlock + warnings * turns out my floor_mod impl was wrong and i32::rem_euclid has the correct behavior LOL * still errors with lots of bots * make swarm iter & fix new chunks not loading * improve docs * start fixing tests * fix all the tests except the examples i don't know how to exclude them from the tests * improve docs some more
Diffstat (limited to 'azalea-client/src/client.rs')
-rw-r--r--azalea-client/src/client.rs500
1 files changed, 305 insertions, 195 deletions
diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs
index 675f8bec..ce4ca4cf 100644
--- a/azalea-client/src/client.rs
+++ b/azalea-client/src/client.rs
@@ -1,5 +1,5 @@
pub use crate::chat::ChatPacket;
-use crate::{movement::WalkDirection, plugins::Plugins, Account, PlayerInfo};
+use crate::{movement::WalkDirection, plugins::PluginStates, Account, PlayerInfo};
use azalea_auth::game_profile::GameProfile;
use azalea_chat::Component;
use azalea_core::{ChunkPos, GameType, ResourceLocation, Vec3};
@@ -15,7 +15,10 @@ use azalea_protocol::{
serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket,
ClientboundGamePacket, ServerboundGamePacket,
},
- handshake::client_intention_packet::ClientIntentionPacket,
+ handshake::{
+ client_intention_packet::ClientIntentionPacket, ClientboundHandshakePacket,
+ ServerboundHandshakePacket,
+ },
login::{
serverbound_custom_query_packet::ServerboundCustomQueryPacket,
serverbound_hello_packet::ServerboundHelloPacket,
@@ -29,9 +32,9 @@ use azalea_protocol::{
};
use azalea_world::{
entity::{metadata, Entity, EntityData, EntityMetadata},
- World,
+ WeakWorld, WeakWorldContainer, World,
};
-use log::{debug, error, info, warn};
+use log::{debug, error, info, trace, warn};
use parking_lot::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::{
collections::HashMap,
@@ -41,7 +44,7 @@ use std::{
};
use thiserror::Error;
use tokio::{
- sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
+ sync::mpsc::{self, Receiver, Sender},
task::JoinHandle,
time::{self},
};
@@ -57,7 +60,7 @@ pub enum Event {
/// it's actually spawned. This can be useful for setting the client
/// information with `Client::set_client_information`, so the packet
/// doesn't have to be sent twice.
- Initialize,
+ Init,
Login,
Chat(ChatPacket),
/// Happens 20 times per second, but only when the world is loaded.
@@ -102,14 +105,20 @@ pub struct Client {
pub read_conn: Arc<tokio::sync::Mutex<ReadConnection<ClientboundGamePacket>>>,
pub write_conn: Arc<tokio::sync::Mutex<WriteConnection<ServerboundGamePacket>>>,
pub entity_id: Arc<RwLock<u32>>,
+ /// The world that this client has access to. This supports shared worlds.
pub world: Arc<RwLock<World>>,
+ /// A container of world names to worlds. If we're not using a shared world
+ /// (i.e. not a swarm), then this will only contain data about the world
+ /// we're currently in.
+ world_container: Arc<RwLock<WeakWorldContainer>>,
+ pub world_name: Arc<RwLock<Option<ResourceLocation>>>,
pub physics_state: Arc<Mutex<PhysicsState>>,
pub client_information: Arc<RwLock<ClientInformation>>,
pub dead: Arc<Mutex<bool>>,
/// Plugins are a way for other crates to add custom functionality to the
/// client and keep state. If you're not making a plugin and you're using
/// the `azalea` crate. you can ignore this field.
- pub plugins: Arc<Plugins>,
+ pub plugins: Arc<PluginStates>,
/// A map of player uuids to their information in the tab list
pub players: Arc<RwLock<HashMap<Uuid, PlayerInfo>>>,
tasks: Arc<Mutex<Vec<JoinHandle<()>>>>,
@@ -152,13 +161,50 @@ pub enum JoinError {
pub enum HandleError {
#[error("{0}")]
Poison(String),
- #[error("{0}")]
+ #[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Other(#[from] anyhow::Error),
+ #[error("{0}")]
+ Send(#[from] mpsc::error::SendError<Event>),
}
impl Client {
+ /// Create a new client from the given GameProfile, Connection, and World.
+ /// You should only use this if you want to change these fields from the
+ /// defaults, otherwise use [`Client::join`].
+ pub fn new(
+ profile: GameProfile,
+ conn: Connection<ClientboundGamePacket, ServerboundGamePacket>,
+ world_container: Option<Arc<RwLock<WeakWorldContainer>>>,
+ ) -> 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 {
+ profile,
+ read_conn,
+ write_conn,
+ // default our id to 0, it'll be set later
+ entity_id: Arc::new(RwLock::new(0)),
+ world: Arc::new(RwLock::new(World::default())),
+ world_container: world_container
+ .unwrap_or_else(|| Arc::new(RwLock::new(WeakWorldContainer::new()))),
+ world_name: Arc::new(RwLock::new(None)),
+ physics_state: Arc::new(Mutex::new(PhysicsState::default())),
+ client_information: Arc::new(RwLock::new(ClientInformation::default())),
+ dead: Arc::new(Mutex::new(false)),
+ // 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(PluginStates::default()),
+ players: Arc::new(RwLock::new(HashMap::new())),
+ tasks: Arc::new(Mutex::new(Vec::new())),
+ }
+ }
+
/// Connect to a Minecraft server.
///
/// To change the render distance and other settings, use
@@ -168,26 +214,56 @@ impl Client {
/// # Examples
///
/// ```rust,no_run
- /// use azalea_client::Client;
+ /// use azalea_client::{Client, Account};
///
/// #[tokio::main]
- /// async fn main() -> Box<dyn std::error::Error> {
+ /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let account = Account::offline("bot");
/// let (client, rx) = Client::join(&account, "localhost").await?;
/// client.chat("Hello, world!").await?;
- /// client.shutdown().await?;
+ /// client.disconnect().await?;
+ /// Ok(())
/// }
/// ```
pub async fn join(
account: &Account,
address: impl TryInto<ServerAddress>,
- ) -> Result<(Self, UnboundedReceiver<Event>), JoinError> {
+ ) -> Result<(Self, Receiver<Event>), JoinError> {
let address: ServerAddress = address.try_into().map_err(|_| JoinError::InvalidAddress)?;
-
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?;
+
+ // The buffer has to be 1 to avoid a bug where if it lags events are
+ // received a bit later instead of the instant they were fired.
+ // That bug especially causes issues with the pathfinder.
+ let (tx, rx) = mpsc::channel(1);
+
+ // we got the GameConnection, so the server is now connected :)
+ let client = Client::new(game_profile, conn, None);
+
+ tx.send(Event::Init).await.expect("Failed to send event");
+
+ // 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
conn.write(
ClientIntentionPacket {
@@ -267,48 +343,7 @@ impl Client {
}
};
- let (read_conn, write_conn) = conn.into_split();
-
- 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 {
- profile,
- read_conn,
- write_conn,
- // default our id to 0, it'll be set later
- entity_id: Arc::new(RwLock::new(0)),
- world: Arc::new(RwLock::new(World::default())),
- physics_state: Arc::new(Mutex::new(PhysicsState::default())),
- client_information: Arc::new(RwLock::new(ClientInformation::default())),
- dead: Arc::new(Mutex::new(false)),
- // 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()),
- players: Arc::new(RwLock::new(HashMap::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))
+ Ok((conn, profile))
}
/// Write a packet directly to the server.
@@ -317,8 +352,8 @@ impl Client {
Ok(())
}
- /// Disconnect from the server, ending all tasks.
- pub async fn shutdown(&self) -> Result<(), std::io::Error> {
+ /// Disconnect this client from the server, ending all tasks.
+ pub async fn disconnect(&self) -> Result<(), std::io::Error> {
if let Err(e) = self.write_conn.lock().await.shutdown().await {
warn!(
"Error shutting down connection, but it might be fine: {}",
@@ -332,7 +367,22 @@ impl Client {
Ok(())
}
- async fn protocol_loop(client: Client, tx: UnboundedSender<Event>) {
+ /// Start the protocol and game tick loop.
+ #[doc(hidden)]
+ pub fn start_tasks(&self, tx: Sender<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: Sender<Event>) {
loop {
let r = client.read_conn.lock().await.read().await;
match r {
@@ -340,9 +390,7 @@ impl Client {
Ok(_) => {}
Err(e) => {
error!("Error handling packet: {}", e);
- if IGNORE_ERRORS {
- continue;
- } else {
+ if !IGNORE_ERRORS {
panic!("Error handling packet: {e}");
}
}
@@ -350,16 +398,15 @@ impl Client {
Err(e) => {
if let ReadPacketError::ConnectionClosed = e {
info!("Connection closed");
- if let Err(e) = client.shutdown().await {
+ if let Err(e) = client.disconnect().await {
error!("Error shutting down connection: {:?}", e);
}
- return;
+ break;
}
if IGNORE_ERRORS {
warn!("{}", e);
- match e {
- ReadPacketError::FrameSplitter { .. } => panic!("Error: {e:?}"),
- _ => continue,
+ if let ReadPacketError::FrameSplitter { .. } = e {
+ panic!("Error: {e:?}");
}
} else {
panic!("{}", e);
@@ -372,12 +419,12 @@ impl Client {
async fn handle(
packet: &ClientboundGamePacket,
client: &Client,
- tx: &UnboundedSender<Event>,
+ tx: &Sender<Event>,
) -> Result<(), HandleError> {
- tx.send(Event::Packet(Box::new(packet.clone()))).unwrap();
+ tx.send(Event::Packet(Box::new(packet.clone()))).await?;
match packet {
ClientboundGamePacket::Login(p) => {
- debug!("Got login packet {:?}", p);
+ debug!("Got login packet");
{
// // write p into login.txt
@@ -440,16 +487,27 @@ impl Client {
.as_int()
.expect("min_y tag is not an int");
+ // add this world to the world_container (or don't if it's already there)
+ let weak_world =
+ client
+ .world_container
+ .write()
+ .insert(p.dimension.clone(), height, min_y);
+ // set the loaded_world to an empty world
+ // (when we add chunks or entities those will be in the world_container)
let mut world_lock = client.world.write();
- // the 16 here is our render distance
- // i'll make this an actual setting later
- *world_lock = World::new(16, height, min_y);
+ *world_lock = World::new(
+ client.client_information.read().view_distance.into(),
+ weak_world,
+ p.player_id,
+ );
let entity = EntityData::new(
client.profile.uuid,
Vec3::default(),
EntityMetadata::Player(metadata::Player::default()),
);
+ // make it so other entities don't update this entity in a shared world
world_lock.add_entity(p.player_id, entity);
*client.entity_id.write() = p.player_id;
@@ -476,7 +534,7 @@ impl Client {
)
.await?;
- tx.send(Event::Login).unwrap();
+ tx.send(Event::Login).await?;
}
ClientboundGamePacket::SetChunkCacheRadius(p) => {
debug!("Got set chunk cache radius packet {:?}", p);
@@ -501,7 +559,7 @@ impl Client {
}
ClientboundGamePacket::Disconnect(p) => {
debug!("Got disconnect packet {:?}", p);
- client.shutdown().await?;
+ client.disconnect().await?;
}
ClientboundGamePacket::UpdateRecipes(_p) => {
debug!("Got update recipes packet");
@@ -521,9 +579,7 @@ impl Client {
let mut world_lock = client.world.write();
- let mut player_entity = world_lock
- .entity_mut(player_entity_id)
- .expect("Player entity doesn't exist");
+ let mut player_entity = world_lock.entity_mut(player_entity_id).unwrap();
let delta_movement = player_entity.delta;
@@ -604,94 +660,102 @@ impl Client {
use azalea_protocol::packets::game::clientbound_player_info_packet::Action;
debug!("Got player info packet {:?}", p);
- let mut players_lock = client.players.write();
- match &p.action {
- Action::AddPlayer(players) => {
- for player in players {
- let player_info = PlayerInfo {
- profile: GameProfile {
+ let mut events = Vec::new();
+ {
+ let mut players_lock = client.players.write();
+ match &p.action {
+ Action::AddPlayer(players) => {
+ for player in players {
+ let player_info = PlayerInfo {
+ profile: GameProfile {
+ uuid: player.uuid,
+ name: player.name.clone(),
+ properties: player.properties.clone(),
+ },
uuid: player.uuid,
- name: player.name.clone(),
- properties: player.properties.clone(),
- },
- uuid: player.uuid,
- gamemode: player.gamemode,
- latency: player.latency,
- display_name: player.display_name.clone(),
- };
- players_lock.insert(player.uuid, player_info.clone());
- tx.send(Event::UpdatePlayers(UpdatePlayersEvent::Add(player_info)))
- .unwrap();
+ gamemode: player.gamemode,
+ latency: player.latency,
+ display_name: player.display_name.clone(),
+ };
+ players_lock.insert(player.uuid, player_info.clone());
+ events.push(Event::UpdatePlayers(UpdatePlayersEvent::Add(
+ player_info,
+ )));
+ }
}
- }
- Action::UpdateGameMode(players) => {
- for player in players {
- if let Some(p) = players_lock.get_mut(&player.uuid) {
- p.gamemode = player.gamemode;
- tx.send(Event::UpdatePlayers(UpdatePlayersEvent::GameMode {
- uuid: player.uuid,
- game_mode: player.gamemode,
- }))
- .unwrap();
- } else {
- warn!(
+ Action::UpdateGameMode(players) => {
+ for player in players {
+ if let Some(p) = players_lock.get_mut(&player.uuid) {
+ p.gamemode = player.gamemode;
+ events.push(Event::UpdatePlayers(
+ UpdatePlayersEvent::GameMode {
+ uuid: player.uuid,
+ game_mode: player.gamemode,
+ },
+ ));
+ } else {
+ warn!(
"Ignoring PlayerInfo (UpdateGameMode) for unknown player {}",
player.uuid
);
+ }
}
}
- }
- Action::UpdateLatency(players) => {
- for player in players {
- if let Some(p) = players_lock.get_mut(&player.uuid) {
- p.latency = player.latency;
- tx.send(Event::UpdatePlayers(UpdatePlayersEvent::Latency {
- uuid: player.uuid,
- latency: player.latency,
- }))
- .unwrap();
- } else {
- warn!(
- "Ignoring PlayerInfo (UpdateLatency) for unknown player {}",
- player.uuid
- );
+ Action::UpdateLatency(players) => {
+ for player in players {
+ if let Some(p) = players_lock.get_mut(&player.uuid) {
+ p.latency = player.latency;
+ events.push(Event::UpdatePlayers(
+ UpdatePlayersEvent::Latency {
+ uuid: player.uuid,
+ latency: player.latency,
+ },
+ ));
+ } else {
+ warn!(
+ "Ignoring PlayerInfo (UpdateLatency) for unknown player {}",
+ player.uuid
+ );
+ }
}
}
- }
- Action::UpdateDisplayName(players) => {
- for player in players {
- if let Some(p) = players_lock.get_mut(&player.uuid) {
- p.display_name = player.display_name.clone();
- tx.send(Event::UpdatePlayers(UpdatePlayersEvent::DisplayName {
- uuid: player.uuid,
- display_name: player.display_name.clone(),
- }))
- .unwrap();
- } else {
- warn!(
+ Action::UpdateDisplayName(players) => {
+ for player in players {
+ if let Some(p) = players_lock.get_mut(&player.uuid) {
+ p.display_name = player.display_name.clone();
+ events.push(Event::UpdatePlayers(
+ UpdatePlayersEvent::DisplayName {
+ uuid: player.uuid,
+ display_name: player.display_name.clone(),
+ },
+ ));
+ } else {
+ warn!(
"Ignoring PlayerInfo (UpdateDisplayName) for unknown player {}",
player.uuid
);
+ }
}
}
- }
- Action::RemovePlayer(players) => {
- for player in players {
- if players_lock.remove(&player.uuid).is_some() {
- tx.send(Event::UpdatePlayers(UpdatePlayersEvent::Remove {
- uuid: player.uuid,
- }))
- .unwrap();
- } else {
- warn!(
- "Ignoring PlayerInfo (RemovePlayer) for unknown player {}",
- player.uuid
- );
+ Action::RemovePlayer(players) => {
+ for player in players {
+ if players_lock.remove(&player.uuid).is_some() {
+ events.push(Event::UpdatePlayers(UpdatePlayersEvent::Remove {
+ uuid: player.uuid,
+ }));
+ } else {
+ warn!(
+ "Ignoring PlayerInfo (RemovePlayer) for unknown player {}",
+ player.uuid
+ );
+ }
}
}
}
}
- // TODO
+ for event in events {
+ tx.send(event).await?;
+ }
}
ClientboundGamePacket::SetChunkCacheCenter(p) => {
debug!("Got chunk cache center packet {:?}", p);
@@ -701,8 +765,29 @@ impl Client {
.update_view_center(&ChunkPos::new(p.x, p.z));
}
ClientboundGamePacket::LevelChunkWithLight(p) => {
- debug!("Got chunk with light packet {} {}", p.x, p.z);
+ // debug!("Got chunk with light packet {} {}", p.x, p.z);
let pos = ChunkPos::new(p.x, p.z);
+
+ // OPTIMIZATION: if we already know about the chunk from the
+ // shared world (and not ourselves), then we don't need to
+ // parse it again. This is only used when we have a shared
+ // world, since we check that the chunk isn't currently owned
+ // by this client.
+ let shared_has_chunk = client.world.read().get_chunk(&pos).is_some();
+ let this_client_has_chunk = client
+ .world
+ .read()
+ .chunk_storage
+ .limited_get(&pos)
+ .is_some();
+ if shared_has_chunk && !this_client_has_chunk {
+ trace!(
+ "Skipping parsing chunk {:?} because we already know about it",
+ pos
+ );
+ return Ok(());
+ }
+
// let chunk = Chunk::read_with_world_height(&mut p.chunk_data);
// debug("chunk {:?}")
if let Err(e) = client
@@ -727,7 +812,7 @@ impl Client {
if let Some(mut entity) = world.entity_mut(p.id) {
entity.apply_metadata(&p.packed_items.0);
} else {
- warn!("Server sent an entity data packet for an entity id ({}) that we don't know about", p.id);
+ // warn!("Server sent an entity data packet for an entity id ({}) that we don't know about", p.id);
}
}
ClientboundGamePacket::UpdateAttributes(_p) => {
@@ -759,10 +844,11 @@ impl Client {
ClientboundGamePacket::SetHealth(p) => {
debug!("Got set health packet {:?}", p);
if p.health == 0.0 {
- let mut dead_lock = client.dead.lock();
- if !*dead_lock {
- *dead_lock = true;
- tx.send(Event::Death(None)).unwrap();
+ // we can't define a variable here with client.dead.lock()
+ // because of https://github.com/rust-lang/rust/issues/57478
+ if !*client.dead.lock() {
+ *client.dead.lock() = true;
+ tx.send(Event::Death(None)).await?;
}
}
}
@@ -771,17 +857,14 @@ impl Client {
}
ClientboundGamePacket::TeleportEntity(p) => {
let mut world_lock = client.world.write();
-
- world_lock
- .set_entity_pos(
- p.id,
- Vec3 {
- x: p.x,
- y: p.y,
- z: p.z,
- },
- )
- .map_err(|e| HandleError::Other(e.into()))?;
+ let _ = world_lock.set_entity_pos(
+ p.id,
+ Vec3 {
+ x: p.x,
+ y: p.y,
+ z: p.z,
+ },
+ );
}
ClientboundGamePacket::UpdateAdvancements(p) => {
debug!("Got update advancements packet {:?}", p);
@@ -792,16 +875,12 @@ impl Client {
ClientboundGamePacket::MoveEntityPos(p) => {
let mut world_lock = client.world.write();
- world_lock
- .move_entity_with_delta(p.entity_id, &p.delta)
- .map_err(|e| HandleError::Other(e.into()))?;
+ let _ = world_lock.move_entity_with_delta(p.entity_id, &p.delta);
}
ClientboundGamePacket::MoveEntityPosRot(p) => {
let mut world_lock = client.world.write();
- world_lock
- .move_entity_with_delta(p.entity_id, &p.delta)
- .map_err(|e| HandleError::Other(e.into()))?;
+ let _ = world_lock.move_entity_with_delta(p.entity_id, &p.delta);
}
ClientboundGamePacket::MoveEntityRot(_p) => {
// debug!("Got move entity rot packet {:?}", p);
@@ -816,16 +895,16 @@ impl Client {
debug!("Got remove entities packet {:?}", p);
}
ClientboundGamePacket::PlayerChat(p) => {
- // debug!("Got player chat packet {:?}", p);
+ debug!("Got player chat packet {:?}", p);
tx.send(Event::Chat(ChatPacket::Player(Box::new(p.clone()))))
- .unwrap();
+ .await?;
}
ClientboundGamePacket::SystemChat(p) => {
debug!("Got system chat packet {:?}", p);
- tx.send(Event::Chat(ChatPacket::System(p.clone()))).unwrap();
+ tx.send(Event::Chat(ChatPacket::System(p.clone()))).await?;
}
- ClientboundGamePacket::Sound(p) => {
- debug!("Got sound packet {:?}", p);
+ ClientboundGamePacket::Sound(_p) => {
+ // debug!("Got sound packet {:?}", p);
}
ClientboundGamePacket::LevelEvent(p) => {
debug!("Got level event packet {:?}", p);
@@ -892,10 +971,11 @@ impl Client {
ClientboundGamePacket::PlayerCombatKill(p) => {
debug!("Got player kill packet {:?}", p);
if *client.entity_id.read() == p.player_id {
- let mut dead_lock = client.dead.lock();
- if !*dead_lock {
- *dead_lock = true;
- tx.send(Event::Death(Some(Box::new(p.clone())))).unwrap();
+ // we can't define a variable here with client.dead.lock()
+ // because of https://github.com/rust-lang/rust/issues/57478
+ if !*client.dead.lock() {
+ *client.dead.lock() = true;
+ tx.send(Event::Death(Some(Box::new(p.clone())))).await?;
}
}
}
@@ -938,7 +1018,7 @@ impl Client {
}
/// Runs game_tick every 50 milliseconds.
- async fn game_tick_loop(mut client: Client, tx: UnboundedSender<Event>) {
+ async fn game_tick_loop(mut client: Client, tx: Sender<Event>) {
let mut game_tick_interval = time::interval(time::Duration::from_millis(50));
// TODO: Minecraft bursts up to 10 ticks and then skips, we should too
game_tick_interval.set_missed_tick_behavior(time::MissedTickBehavior::Burst);
@@ -949,24 +1029,25 @@ impl Client {
}
/// Runs every 50 milliseconds.
- async fn game_tick(client: &mut Client, tx: &UnboundedSender<Event>) {
+ async fn game_tick(client: &mut Client, tx: &Sender<Event>) {
// return if there's no chunk at the player's position
+
{
- let world_lock = client.world.write();
+ let world_lock = client.world.read();
let player_entity_id = *client.entity_id.read();
let player_entity = world_lock.entity(player_entity_id);
- let player_entity = if let Some(player_entity) = player_entity {
- player_entity
- } else {
+ let Some(player_entity) = player_entity else {
return;
};
let player_chunk_pos: ChunkPos = player_entity.pos().into();
- if world_lock[&player_chunk_pos].is_none() {
+ if world_lock.get_chunk(&player_chunk_pos).is_none() {
return;
}
}
- tx.send(Event::Tick).unwrap();
+ tx.send(Event::Tick)
+ .await
+ .expect("Sending tick event should never fail");
// TODO: if we're a passenger, send the required packets
@@ -978,15 +1059,34 @@ impl Client {
// TODO: minecraft does ambient sounds here
}
+ /// Get a [`WeakWorld`] from our world container. If it's a normal client,
+ /// then it'll be the same as the world the client has loaded. If the
+ /// client using a shared world, then the shared world will be a superset
+ /// of the client's world.
+ ///
+ /// # Panics
+ /// Panics if the client has not received the login packet yet. You can check this with [`Client::logged_in`].
+ pub fn world(&self) -> Arc<WeakWorld> {
+ let world_name = self.world_name.read();
+ let world_name = world_name
+ .as_ref()
+ .expect("Client has not received login packet yet");
+ if let Some(world) = self.world_container.read().get(world_name) {
+ world
+ } else {
+ unreachable!("The world name must be in the world container");
+ }
+ }
+
/// Returns the entity associated to the player.
pub fn entity_mut(&self) -> Entity<RwLockWriteGuard<World>> {
let entity_id = *self.entity_id.read();
- let mut world = self.world.write();
+ let world = self.world.write();
let entity_data = world
.entity_storage
- .get_mut_by_id(entity_id)
+ .get_by_id(entity_id)
.expect("Player entity should exist");
let entity_ptr = unsafe { entity_data.as_ptr() };
Entity::new(world, entity_id, entity_ptr)
@@ -994,26 +1094,36 @@ impl Client {
/// Returns the entity associated to the player.
pub fn entity(&self) -> Entity<RwLockReadGuard<World>> {
let entity_id = *self.entity_id.read();
-
let world = self.world.read();
let entity_data = world
.entity_storage
.get_by_id(entity_id)
.expect("Player entity should be in the given world");
- let entity_ptr = unsafe { entity_data.as_const_ptr() };
+ let entity_ptr = unsafe { entity_data.as_ptr() };
Entity::new(world, entity_id, entity_ptr)
}
/// Returns whether we have a received the login packet yet.
pub fn logged_in(&self) -> bool {
- let world = self.world.read();
- let entity_id = *self.entity_id.read();
- world.entity(entity_id).is_some()
+ // the login packet tells us the world name
+ self.world_name.read().is_some()
}
/// Tell the server we changed our game options (i.e. render distance, main hand).
/// If this is not set before the login packet, the default will be sent.
+ ///
+ /// ```rust,no_run
+ /// # use azalea_client::{Client, ClientInformation};
+ /// # async fn example(bot: Client) -> Result<(), Box<dyn std::error::Error>> {
+ /// bot.set_client_information(ClientInformation {
+ /// view_distance: 2,
+ /// ..Default::default()
+ /// })
+ /// .await?;
+ /// # Ok(())
+ /// # }
+ /// ```
pub async fn set_client_information(
&self,
client_information: ServerboundClientInformationPacket,