From efd874957331d8bff1cefa0f43d81685690391bf Mon Sep 17 00:00:00 2001 From: mat Date: Mon, 20 Jun 2022 01:19:59 -0500 Subject: [PATCH] add gametick event and find_one_entity --- Cargo.lock | 1 + azalea-client/src/client.rs | 46 ++++++++++++++++++++++++++++++++----- azalea-world/src/entity.rs | 34 +++++++++++++++++++++++++++ azalea-world/src/lib.rs | 13 +++++++++++ bot/Cargo.toml | 1 + bot/src/main.rs | 14 +++++++---- 6 files changed, 98 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 59edd75e..c3e5a0ce 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -235,6 +235,7 @@ dependencies = [ "azalea-core", "azalea-protocol", "tokio", + "uuid", ] [[package]] diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 3dd206b5..828578de 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -26,7 +26,10 @@ use std::{ fmt::Debug, sync::{Arc, Mutex}, }; -use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; +use tokio::{ + sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, + time::{self, MissedTickBehavior}, +}; #[derive(Default)] pub struct ClientState { @@ -38,6 +41,8 @@ pub struct ClientState { pub enum Event { Login, Chat(ChatPacket), + /// A game tick, happens 20 times per second. + GameTick, } #[derive(Debug, Clone)] @@ -153,20 +158,22 @@ impl Client { conn: conn.clone(), state: Arc::new(Mutex::new(ClientState::default())), }; - // let client = Arc::new(Mutex::new(client)); - // let weak_client = Arc::<_>::downgrade(&client); // just start up the game loop and we're ready! - // tokio::spawn(Self::game_loop(conn, tx, handler, state)) let game_loop_state = client.state.clone(); - tokio::spawn(Self::game_loop(conn, tx, game_loop_state)); + tokio::spawn(Self::protocol_loop( + conn.clone(), + tx.clone(), + game_loop_state.clone(), + )); + tokio::spawn(Self::game_tick_loop(conn, tx, game_loop_state)); Ok(client) } - async fn game_loop( + async fn protocol_loop( conn: Arc>, tx: UnboundedSender, state: Arc>, @@ -487,6 +494,33 @@ impl Client { self.event_receiver.recv().await } + /// Runs game_tick every 50 milliseconds. + async fn game_tick_loop( + conn: Arc>, + tx: UnboundedSender, + state: Arc>, + ) { + 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); + loop { + game_tick_interval.tick().await; + Self::game_tick(&conn, &tx, &state).await; + } + } + + /// Runs every 50 milliseconds. + async fn game_tick( + conn: &Arc>, + tx: &UnboundedSender, + state: &Arc>, + ) { + if state.lock().unwrap().world.is_none() { + return; + } + tx.send(Event::GameTick).unwrap(); + } + /// Gets the `World` the client is in. /// /// This is basically a shortcut for `let world = client.state.lock().unwrap().world.as_ref().unwrap()`. diff --git a/azalea-world/src/entity.rs b/azalea-world/src/entity.rs index 2409995c..e4e9864f 100644 --- a/azalea-world/src/entity.rs +++ b/azalea-world/src/entity.rs @@ -79,6 +79,40 @@ impl EntityStorage { .or_default() .insert(entity_id); } + + /// Get an iterator over all entities. + #[inline] + pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, Entity> { + self.by_id.values() + } + + pub fn find_one_entity(&self, mut f: F) -> Option<&Entity> + where + F: FnMut(&Entity) -> bool, + { + for entity in self.entities() { + if f(entity) { + return Some(entity); + } + } + None + } + + pub fn find_one_entity_in_chunk(&self, chunk: &ChunkPos, mut f: F) -> Option<&Entity> + where + F: FnMut(&Entity) -> bool, + { + if let Some(entities) = self.by_chunk.get(chunk) { + for entity_id in entities { + if let Some(entity) = self.by_id.get(entity_id) { + if f(entity) { + return Some(entity); + } + } + } + } + None + } } impl Default for EntityStorage { diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs index dc538618..10beb309 100644 --- a/azalea-world/src/lib.rs +++ b/azalea-world/src/lib.rs @@ -87,6 +87,19 @@ impl World { pub fn entity_by_id(&self, id: u32) -> Option<&Entity> { self.entity_storage.get_by_id(id) } + + /// Get an iterator over all entities. + #[inline] + pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, Entity> { + self.entity_storage.entities() + } + + pub fn find_one_entity(&self, mut f: F) -> Option<&Entity> + where + F: FnMut(&Entity) -> bool, + { + self.entity_storage.find_one_entity(|entity| f(entity)) + } } impl Index<&ChunkPos> for World { diff --git a/bot/Cargo.toml b/bot/Cargo.toml index b66e3b75..f61bf2fa 100755 --- a/bot/Cargo.toml +++ b/bot/Cargo.toml @@ -10,3 +10,4 @@ azalea-client = {path = "../azalea-client"} azalea-core = {path = "../azalea-core"} azalea-protocol = {path = "../azalea-protocol"} tokio = "^1.19.2" +uuid = "^1.1.2" diff --git a/bot/src/main.rs b/bot/src/main.rs index 2c2bee32..6ff4cc0b 100644 --- a/bot/src/main.rs +++ b/bot/src/main.rs @@ -20,12 +20,15 @@ async fn main() -> Result<(), Box> { match e { // TODO: have a "loaded" or "ready" event that fires when all chunks are loaded Event::Login => {} - Event::Chat(_p) => { + Event::GameTick => { let world = client.world(); - let b = world.get_block_state(&BlockPos::new(0, 0, 0)).unwrap(); - // let world = state.world.as_ref().unwrap(); - // world. - println!("{:?}", b); + if let Some(b) = world.find_one_entity(|e| { + e.uuid == uuid::uuid!("6536bfed-8695-48fd-83a1-ecd24cf2a0fd") + }) { + // let world = state.world.as_ref().unwrap(); + // world. + println!("{:?}", b); + } // world.get_block_state(state.player.entity.pos); // println!("{}", p.message.to_ansi(None)); // if p.message.to_ansi(None) == " ok" { @@ -35,6 +38,7 @@ async fn main() -> Result<(), Box> { // println!("block state: {:?}", c); // } } + _ => {} } }