1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00

merge ClientState and Client

This commit is contained in:
mat 2022-06-25 16:23:40 -05:00
parent 77980f0018
commit 460bdfb8bb
7 changed files with 99 additions and 147 deletions

16
Cargo.lock generated
View file

@ -120,7 +120,6 @@ dependencies = [
"azalea-entity", "azalea-entity",
"azalea-protocol", "azalea-protocol",
"azalea-world", "azalea-world",
"owning_ref",
"tokio", "tokio",
"uuid", "uuid",
] ]
@ -920,15 +919,6 @@ version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "owning_ref"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce"
dependencies = [
"stable_deref_trait",
]
[[package]] [[package]]
name = "packet-macros" name = "packet-macros"
version = "0.1.0" version = "0.1.0"
@ -1277,12 +1267,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.96" version = "1.0.96"

View file

@ -12,6 +12,5 @@ azalea-crypto = {path = "../azalea-crypto"}
azalea-entity = {path = "../azalea-entity"} azalea-entity = {path = "../azalea-entity"}
azalea-protocol = {path = "../azalea-protocol"} azalea-protocol = {path = "../azalea-protocol"}
azalea-world = {path = "../azalea-world"} azalea-world = {path = "../azalea-world"}
owning_ref = "0.4.1"
tokio = {version = "1.19.2", features = ["sync"]} tokio = {version = "1.19.2", features = ["sync"]}
uuid = "1.1.2" uuid = "1.1.2"

View file

@ -1,7 +1,8 @@
//! Connect to Minecraft servers. //! Connect to Minecraft servers.
use crate::Client; use crate::{Client, Event};
use azalea_protocol::ServerAddress; use azalea_protocol::ServerAddress;
use tokio::sync::mpsc::UnboundedReceiver;
/// Something that can join Minecraft servers. /// Something that can join Minecraft servers.
pub struct Account { pub struct Account {
@ -14,7 +15,11 @@ impl Account {
} }
} }
pub async fn join(&self, address: &ServerAddress) -> Result<Client, String> { /// Joins the Minecraft server on the given address using this account.
pub async fn join(
&self,
address: &ServerAddress,
) -> Result<(Client, UnboundedReceiver<Event>), String> {
Client::join(self, address).await Client::join(self, address).await
} }
} }

View file

@ -24,22 +24,15 @@ use azalea_protocol::{
resolver, ServerAddress, resolver, ServerAddress,
}; };
use azalea_world::Dimension; use azalea_world::Dimension;
use owning_ref::OwningRef;
use std::{ use std::{
fmt::Debug, fmt::Debug,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use tokio::{ use tokio::{
sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
time::{self, MissedTickBehavior}, time::{self},
}; };
#[derive(Default)]
pub struct ClientState {
pub player: Player,
pub world: Option<Dimension>,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Event { pub enum Event {
Login, Login,
@ -64,11 +57,12 @@ pub enum ChatPacket {
// } // }
/// A player that you can control that is currently in a Minecraft server. /// A player that you can control that is currently in a Minecraft server.
#[derive(Clone)]
pub struct Client { pub struct Client {
event_receiver: UnboundedReceiver<Event>,
game_profile: GameProfile, game_profile: GameProfile,
pub conn: Arc<tokio::sync::Mutex<GameConnection>>, pub conn: Arc<tokio::sync::Mutex<GameConnection>>,
pub state: Arc<Mutex<ClientState>>, pub player: Arc<Mutex<Player>>,
pub dimension: Arc<Mutex<Option<Dimension>>>,
// game_loop // game_loop
} }
@ -80,7 +74,10 @@ struct HandleError(String);
impl Client { impl Client {
/// Connect to a Minecraft server with an account. /// Connect to a Minecraft server with an account.
pub async fn join(account: &Account, address: &ServerAddress) -> Result<Self, String> { pub async fn join(
account: &Account,
address: &ServerAddress,
) -> Result<(Self, UnboundedReceiver<Event>), String> {
let resolved_address = resolver::resolve_address(address).await?; let resolved_address = resolver::resolve_address(address).await?;
let mut conn = HandshakeConnection::new(&resolved_address).await?; let mut conn = HandshakeConnection::new(&resolved_address).await?;
@ -159,51 +156,39 @@ impl Client {
// we got the GameConnection, so the server is now connected :) // we got the GameConnection, so the server is now connected :)
let client = Client { let client = Client {
game_profile: game_profile.clone(), game_profile: game_profile.clone(),
event_receiver: rx,
conn: conn.clone(), conn: conn.clone(),
state: Arc::new(Mutex::new(ClientState::default())), player: Arc::new(Mutex::new(Player::default())),
dimension: Arc::new(Mutex::new(None)),
}; };
// just start up the game loop and we're ready! // just start up the game loop and we're ready!
let game_loop_state = client.state.clone(); let game_loop_state = client.clone();
// if you get an error right here that means you're doing something with locks wrong // 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 // 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 // you might be able to just drop the lock or put it in its own scope to fix
tokio::spawn(Self::protocol_loop( tokio::spawn(Self::protocol_loop(client.clone(), tx.clone()));
conn.clone(), tokio::spawn(Self::game_tick_loop(client.clone(), tx.clone()));
tx.clone(),
game_loop_state.clone(),
game_profile.clone(),
));
tokio::spawn(Self::game_tick_loop(conn, tx, game_loop_state));
Ok(client) Ok((client, rx))
} }
async fn protocol_loop( async fn protocol_loop(client: Client, tx: UnboundedSender<Event>) {
conn: Arc<tokio::sync::Mutex<GameConnection>>,
tx: UnboundedSender<Event>,
state: Arc<Mutex<ClientState>>,
game_profile: GameProfile,
) {
loop { loop {
let r = conn.lock().await.read().await; let r = client.conn.lock().await.read().await;
match r { match r {
Ok(packet) => { Ok(packet) => match Self::handle(&packet, &client, &tx).await {
match Self::handle(&packet, &tx, &state, &conn, &game_profile).await { Ok(_) => {}
Ok(_) => {} Err(e) => {
Err(e) => { println!("Error handling packet: {:?}", e);
println!("Error handling packet: {:?}", e); if IGNORE_ERRORS {
if IGNORE_ERRORS { continue;
continue; } else {
} else { panic!("Error handling packet: {:?}", e);
panic!("Error handling packet: {:?}", e);
}
} }
} }
} },
Err(e) => { Err(e) => {
if IGNORE_ERRORS { if IGNORE_ERRORS {
println!("Error: {:?}", e); println!("Error: {:?}", e);
@ -220,18 +205,14 @@ impl Client {
async fn handle( async fn handle(
packet: &GamePacket, packet: &GamePacket,
client: &Client,
tx: &UnboundedSender<Event>, tx: &UnboundedSender<Event>,
state: &Arc<Mutex<ClientState>>,
conn: &Arc<tokio::sync::Mutex<GameConnection>>,
game_profile: &GameProfile,
) -> Result<(), HandleError> { ) -> Result<(), HandleError> {
match packet { match packet {
GamePacket::ClientboundLoginPacket(p) => { GamePacket::ClientboundLoginPacket(p) => {
println!("Got login packet {:?}", p); println!("Got login packet {:?}", p);
{ {
let mut state_lock = state.lock()?;
// // write p into login.txt // // write p into login.txt
// std::io::Write::write_all( // std::io::Write::write_all(
// &mut std::fs::File::create("login.txt").unwrap(), // &mut std::fs::File::create("login.txt").unwrap(),
@ -292,23 +273,28 @@ impl Client {
.as_int() .as_int()
.expect("min_y tag is not an int"); .expect("min_y tag is not an int");
let mut dimension_lock = client.dimension.lock().unwrap();
// the 16 here is our render distance // the 16 here is our render distance
// i'll make this an actual setting later // i'll make this an actual setting later
state_lock.world = Some(Dimension::new(16, height, min_y)); *dimension_lock = Some(Dimension::new(16, height, min_y));
let entity = Entity::new(p.player_id, game_profile.uuid, EntityPos::default()); let entity =
state_lock Entity::new(p.player_id, client.game_profile.uuid, EntityPos::default());
.world dimension_lock
.as_mut() .as_mut()
.expect( .expect(
"Dimension doesn't exist! We should've gotten a login packet by now.", "Dimension doesn't exist! We should've gotten a login packet by now.",
) )
.add_entity(entity); .add_entity(entity);
state_lock.player.set_entity_id(p.player_id); let mut player_lock = client.player.lock().unwrap();
player_lock.set_entity_id(p.player_id);
} }
conn.lock() client
.conn
.lock()
.await .await
.write( .write(
ServerboundCustomPayloadPacket { ServerboundCustomPayloadPacket {
@ -360,12 +346,17 @@ impl Client {
println!("Got player position packet {:?}", p); println!("Got player position packet {:?}", p);
let (new_pos, y_rot, x_rot) = { let (new_pos, y_rot, x_rot) = {
let mut state_lock = state.lock()?; let player_lock = client.player.lock().unwrap();
let player_entity_id = state_lock.player.entity_id; let player_entity_id = player_lock.entity_id;
let world = state_lock.world.as_mut().unwrap(); drop(player_lock);
let player_entity = world
let mut dimension_lock = client.dimension.lock().unwrap();
let dimension = dimension_lock.as_mut().unwrap();
let player_entity = dimension
.mut_entity_by_id(player_entity_id) .mut_entity_by_id(player_entity_id)
.expect("Player entity doesn't exist"); .expect("Player entity doesn't exist");
let delta_movement = &player_entity.delta; let delta_movement = &player_entity.delta;
let is_x_relative = p.relative_arguments.x; let is_x_relative = p.relative_arguments.x;
@ -416,14 +407,14 @@ impl Client {
y: new_pos_y, y: new_pos_y,
z: new_pos_z, z: new_pos_z,
}; };
world dimension
.move_entity(player_entity_id, new_pos) .move_entity(player_entity_id, new_pos)
.expect("The player entity should always exist"); .expect("The player entity should always exist");
(new_pos, y_rot, x_rot) (new_pos, y_rot, x_rot)
}; };
let mut conn_lock = conn.lock().await; let mut conn_lock = client.conn.lock().await;
conn_lock conn_lock
.write(ServerboundAcceptTeleportationPacket { id: p.id }.get()) .write(ServerboundAcceptTeleportationPacket { id: p.id }.get())
.await; .await;
@ -447,9 +438,9 @@ impl Client {
} }
GamePacket::ClientboundSetChunkCacheCenterPacket(p) => { GamePacket::ClientboundSetChunkCacheCenterPacket(p) => {
println!("Got chunk cache center packet {:?}", p); println!("Got chunk cache center packet {:?}", p);
state client
.dimension
.lock()? .lock()?
.world
.as_mut() .as_mut()
.unwrap() .unwrap()
.update_view_center(&ChunkPos::new(p.x, p.z)); .update_view_center(&ChunkPos::new(p.x, p.z));
@ -459,9 +450,9 @@ impl Client {
let pos = ChunkPos::new(p.x, p.z); let pos = ChunkPos::new(p.x, p.z);
// let chunk = Chunk::read_with_world_height(&mut p.chunk_data); // let chunk = Chunk::read_with_world_height(&mut p.chunk_data);
// println("chunk {:?}") // println("chunk {:?}")
state client
.dimension
.lock()? .lock()?
.world
.as_mut() .as_mut()
.expect("Dimension doesn't exist! We should've gotten a login packet by now.") .expect("Dimension doesn't exist! We should've gotten a login packet by now.")
.replace_with_packet_data(&pos, &mut p.chunk_data.data.as_slice()) .replace_with_packet_data(&pos, &mut p.chunk_data.data.as_slice())
@ -473,9 +464,9 @@ impl Client {
GamePacket::ClientboundAddEntityPacket(p) => { GamePacket::ClientboundAddEntityPacket(p) => {
println!("Got add entity packet {:?}", p); println!("Got add entity packet {:?}", p);
let entity = Entity::from(p); let entity = Entity::from(p);
state client
.dimension
.lock()? .lock()?
.world
.as_mut() .as_mut()
.expect("Dimension doesn't exist! We should've gotten a login packet by now.") .expect("Dimension doesn't exist! We should've gotten a login packet by now.")
.add_entity(entity); .add_entity(entity);
@ -495,9 +486,9 @@ impl Client {
GamePacket::ClientboundAddPlayerPacket(p) => { GamePacket::ClientboundAddPlayerPacket(p) => {
println!("Got add player packet {:?}", p); println!("Got add player packet {:?}", p);
let entity = Entity::from(p); let entity = Entity::from(p);
state client
.dimension
.lock()? .lock()?
.world
.as_mut() .as_mut()
.expect("Dimension doesn't exist! We should've gotten a login packet by now.") .expect("Dimension doesn't exist! We should've gotten a login packet by now.")
.add_entity(entity); .add_entity(entity);
@ -521,10 +512,10 @@ impl Client {
println!("Got set experience packet {:?}", p); println!("Got set experience packet {:?}", p);
} }
GamePacket::ClientboundTeleportEntityPacket(p) => { GamePacket::ClientboundTeleportEntityPacket(p) => {
let mut state_lock = state.lock()?; let mut dimension_lock = client.dimension.lock()?;
let world = state_lock.world.as_mut().unwrap(); let dimension = dimension_lock.as_mut().unwrap();
world.move_entity( dimension.move_entity(
p.id, p.id,
EntityPos { EntityPos {
x: p.x, x: p.x,
@ -540,23 +531,25 @@ impl Client {
// println!("Got rotate head packet {:?}", p); // println!("Got rotate head packet {:?}", p);
} }
GamePacket::ClientboundMoveEntityPosPacket(p) => { GamePacket::ClientboundMoveEntityPosPacket(p) => {
let mut state_lock = state.lock()?; let mut dimension_lock = client.dimension.lock()?;
let world = state_lock.world.as_mut().unwrap(); let dimension = dimension_lock.as_mut().unwrap();
world.move_entity_with_delta(p.entity_id, &p.delta)?; dimension.move_entity_with_delta(p.entity_id, &p.delta)?;
} }
GamePacket::ClientboundMoveEntityPosRotPacket(p) => { GamePacket::ClientboundMoveEntityPosRotPacket(p) => {
let mut state_lock = state.lock()?; let mut dimension_lock = client.dimension.lock()?;
let world = state_lock.world.as_mut().unwrap(); let dimension = dimension_lock.as_mut().unwrap();
world.move_entity_with_delta(p.entity_id, &p.delta)?; dimension.move_entity_with_delta(p.entity_id, &p.delta)?;
} }
GamePacket::ClientboundMoveEntityRotPacket(p) => { GamePacket::ClientboundMoveEntityRotPacket(p) => {
println!("Got move entity rot packet {:?}", p); println!("Got move entity rot packet {:?}", p);
} }
GamePacket::ClientboundKeepAlivePacket(p) => { GamePacket::ClientboundKeepAlivePacket(p) => {
println!("Got keep alive packet {:?}", p); println!("Got keep alive packet {:?}", p);
conn.lock() client
.conn
.lock()
.await .await
.write(ServerboundKeepAlivePacket { id: p.id }.get()) .write(ServerboundKeepAlivePacket { id: p.id }.get())
.await; .await;
@ -611,55 +604,24 @@ impl Client {
Ok(()) Ok(())
} }
pub async fn next(&mut self) -> Option<Event> {
self.event_receiver.recv().await
}
/// Runs game_tick every 50 milliseconds. /// Runs game_tick every 50 milliseconds.
async fn game_tick_loop( async fn game_tick_loop(client: Client, tx: UnboundedSender<Event>) {
conn: Arc<tokio::sync::Mutex<GameConnection>>,
tx: UnboundedSender<Event>,
state: Arc<Mutex<ClientState>>,
) {
let mut game_tick_interval = time::interval(time::Duration::from_millis(50)); 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 // TODO: Minecraft bursts up to 10 ticks and then skips, we should too
game_tick_interval.set_missed_tick_behavior(time::MissedTickBehavior::Burst); game_tick_interval.set_missed_tick_behavior(time::MissedTickBehavior::Burst);
loop { loop {
game_tick_interval.tick().await; game_tick_interval.tick().await;
Self::game_tick(&conn, &tx, &state).await; Self::game_tick(&client, &tx).await;
} }
} }
/// Runs every 50 milliseconds. /// Runs every 50 milliseconds.
async fn game_tick( async fn game_tick(client: &Client, tx: &UnboundedSender<Event>) {
conn: &Arc<tokio::sync::Mutex<GameConnection>>, if client.dimension.lock().unwrap().is_none() {
tx: &UnboundedSender<Event>,
state: &Arc<Mutex<ClientState>>,
) {
if state.lock().unwrap().world.is_none() {
return; return;
} }
tx.send(Event::GameTick).unwrap(); tx.send(Event::GameTick).unwrap();
} }
/// Gets the `Dimension` the client is in.
///
/// This is basically a shortcut for `client.state.lock().unwrap().world.as_ref().unwrap()`.
/// If the client hasn't received a login packet yet, this will panic.
pub fn world(&self) -> OwningRef<std::sync::MutexGuard<ClientState>, Dimension> {
let state_lock: std::sync::MutexGuard<ClientState> = self.state.lock().unwrap();
let state_lock_ref = OwningRef::new(state_lock);
state_lock_ref.map(|state| state.world.as_ref().expect("Dimension doesn't exist!"))
}
/// Gets the `Player` struct for our player.
///
/// This is basically a shortcut for `client.state.lock().unwrap().player`.
pub fn player(&self) -> OwningRef<std::sync::MutexGuard<ClientState>, Player> {
let state_lock: std::sync::MutexGuard<ClientState> = self.state.lock().unwrap();
let state_lock_ref = OwningRef::new(state_lock);
state_lock_ref.map(|state| &state.player)
}
} }
impl<T> From<std::sync::PoisonError<T>> for HandleError { impl<T> From<std::sync::PoisonError<T>> for HandleError {

View file

@ -5,24 +5,21 @@ use azalea_protocol::packets::game::serverbound_move_player_packet_pos_rot::Serv
impl Client { impl Client {
/// Set the client's position to the given coordinates. /// Set the client's position to the given coordinates.
pub async fn move_to(&mut self, new_pos: EntityPos) -> Result<(), String> { pub async fn move_to(&mut self, new_pos: EntityPos) -> Result<(), String> {
println!("obtaining lock on state"); let mut dimension_lock = self.dimension.lock().unwrap();
let mut state_lock = self.state.lock().unwrap(); let dimension = dimension_lock.as_mut().unwrap();
println!("obtained lock on state");
let world = state_lock.world.as_ref().unwrap(); let mut player_lock = self.player.lock().unwrap();
let player = &state_lock.player; let player_id = if let Some(player_lock) = player_lock.entity(dimension) {
let player_id = if let Some(player) = player.entity(world) { player_lock.id
player.id
} else { } else {
return Err("Player entity not found".to_string()); return Err("Player entity not found".to_string());
}; };
let world = state_lock.world.as_mut().unwrap(); dimension.move_entity(player_id, new_pos)?;
world.move_entity(player_id, new_pos)?; drop(dimension_lock);
drop(state_lock); drop(player_lock);
println!("obtaining lock on conn");
self.conn self.conn
.lock() .lock()
.await .await

View file

@ -2,6 +2,11 @@ use azalea_entity::Entity;
use azalea_world::Dimension; use azalea_world::Dimension;
use uuid::Uuid; use uuid::Uuid;
/// Something that has a dimension associated to it. Usually, this is a `Client`.
pub trait DimensionHaver {
fn dimension(&self) -> &Dimension;
}
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Player { pub struct Player {
/// The player's uuid. /// The player's uuid.

View file

@ -6,17 +6,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Hello, world!"); println!("Hello, world!");
// let address = "95.111.249.143:10000"; // let address = "95.111.249.143:10000";
let address = "localhost:65399"; let address = "localhost:56150";
// let response = azalea_client::ping::ping_server(&address.try_into().unwrap()) // let response = azalea_client::ping::ping_server(&address.try_into().unwrap())
// .await // .await
// .unwrap(); // .unwrap();
// println!("{}", response.description.to_ansi(None)); // println!("{}", response.description.to_ansi(None));
let account = Account::offline("bot"); let account = Account::offline("bot");
let mut client = account.join(&address.try_into().unwrap()).await.unwrap(); let (mut client, mut rx) = account.join(&address.try_into().unwrap()).await.unwrap();
println!("connected"); println!("connected");
while let Some(e) = &client.next().await { while let Some(e) = &rx.recv().await {
match e { match e {
// TODO: have a "loaded" or "ready" event that fires when all chunks are loaded // TODO: have a "loaded" or "ready" event that fires when all chunks are loaded
Event::Login => {} Event::Login => {}
@ -40,11 +40,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// } // }
Event::Chat(msg) => { Event::Chat(msg) => {
let new_pos = { let new_pos = {
let state_lock = client.state.lock().unwrap(); let dimension_lock = client.dimension.lock().unwrap();
let world = state_lock.world.as_ref().unwrap(); let dimension = dimension_lock.as_ref().unwrap();
let player = &state_lock.player; let player = client.player.lock().unwrap();
let entity = player let entity = player
.entity(&world) .entity(&dimension)
.expect("Player entity is not in world"); .expect("Player entity is not in world");
entity.pos().add_y(0.5) entity.pos().add_y(0.5)
}; };