aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xCargo.lock1
-rw-r--r--azalea-client/src/client.rs46
-rw-r--r--azalea-world/src/entity.rs34
-rw-r--r--azalea-world/src/lib.rs13
-rwxr-xr-xbot/Cargo.toml1
-rw-r--r--bot/src/main.rs14
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<tokio::sync::Mutex<GameConnection>>,
tx: UnboundedSender<Event>,
state: Arc<Mutex<ClientState>>,
@@ -487,6 +494,33 @@ impl Client {
self.event_receiver.recv().await
}
+ /// Runs game_tick every 50 milliseconds.
+ async fn game_tick_loop(
+ 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));
+ // 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<tokio::sync::Mutex<GameConnection>>,
+ tx: &UnboundedSender<Event>,
+ state: &Arc<Mutex<ClientState>>,
+ ) {
+ 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<F>(&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<F>(&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<F>(&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<dyn std::error::Error>> {
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) == "<py5> ok" {
@@ -35,6 +38,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// println!("block state: {:?}", c);
// }
}
+ _ => {}
}
}