mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
Improvements to azalea-world for entities
This commit is contained in:
parent
fc3151f89d
commit
bb6b116cb8
10 changed files with 263 additions and 138 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -198,6 +198,7 @@ dependencies = [
|
||||||
"azalea-entity",
|
"azalea-entity",
|
||||||
"azalea-nbt",
|
"azalea-nbt",
|
||||||
"azalea-protocol",
|
"azalea-protocol",
|
||||||
|
"log",
|
||||||
"nohash-hasher",
|
"nohash-hasher",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,11 @@ use azalea_protocol::{
|
||||||
resolver, ServerAddress,
|
resolver, ServerAddress,
|
||||||
};
|
};
|
||||||
use azalea_world::{ChunkStorage, EntityStorage, World};
|
use azalea_world::{ChunkStorage, EntityStorage, World};
|
||||||
use std::{fmt::Debug, sync::Arc};
|
use std::{
|
||||||
|
fmt::Debug,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
|
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
|
||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ClientState {
|
pub struct ClientState {
|
||||||
|
@ -55,7 +57,7 @@ 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.
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
event_receiver: UnboundedReceiver<Event>,
|
event_receiver: UnboundedReceiver<Event>,
|
||||||
pub conn: Arc<Mutex<GameConnection>>,
|
pub conn: Arc<tokio::sync::Mutex<GameConnection>>,
|
||||||
pub state: Arc<Mutex<ClientState>>,
|
pub state: Arc<Mutex<ClientState>>,
|
||||||
// game_loop
|
// game_loop
|
||||||
}
|
}
|
||||||
|
@ -63,6 +65,8 @@ pub struct Client {
|
||||||
/// Whether we should ignore errors when decoding packets.
|
/// Whether we should ignore errors when decoding packets.
|
||||||
const IGNORE_ERRORS: bool = !cfg!(debug_assertions);
|
const IGNORE_ERRORS: bool = !cfg!(debug_assertions);
|
||||||
|
|
||||||
|
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, String> {
|
||||||
|
@ -137,7 +141,7 @@ impl Client {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let conn = Arc::new(Mutex::new(conn));
|
let conn = Arc::new(tokio::sync::Mutex::new(conn));
|
||||||
|
|
||||||
let (tx, rx) = mpsc::unbounded_channel();
|
let (tx, rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
@ -161,14 +165,16 @@ impl Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn game_loop(
|
async fn game_loop(
|
||||||
conn: Arc<Mutex<GameConnection>>,
|
conn: Arc<tokio::sync::Mutex<GameConnection>>,
|
||||||
tx: UnboundedSender<Event>,
|
tx: UnboundedSender<Event>,
|
||||||
state: Arc<Mutex<ClientState>>,
|
state: Arc<Mutex<ClientState>>,
|
||||||
) {
|
) {
|
||||||
loop {
|
loop {
|
||||||
let r = conn.lock().await.read().await;
|
let r = conn.lock().await.read().await;
|
||||||
match r {
|
match r {
|
||||||
Ok(packet) => Self::handle(&packet, &tx, &state, &conn).await,
|
Ok(packet) => {
|
||||||
|
Self::handle(&packet, &tx, &state, &conn).await;
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if IGNORE_ERRORS {
|
if IGNORE_ERRORS {
|
||||||
println!("Error: {:?}", e);
|
println!("Error: {:?}", e);
|
||||||
|
@ -187,82 +193,79 @@ impl Client {
|
||||||
packet: &GamePacket,
|
packet: &GamePacket,
|
||||||
tx: &UnboundedSender<Event>,
|
tx: &UnboundedSender<Event>,
|
||||||
state: &Arc<Mutex<ClientState>>,
|
state: &Arc<Mutex<ClientState>>,
|
||||||
conn: &Arc<Mutex<GameConnection>>,
|
conn: &Arc<tokio::sync::Mutex<GameConnection>>,
|
||||||
) {
|
) -> 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 = state.lock().await;
|
{
|
||||||
|
let mut state = 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(),
|
||||||
// format!("{:#?}", p).as_bytes(),
|
// format!("{:#?}", p).as_bytes(),
|
||||||
// )
|
// )
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
state.player.entity.id = p.player_id;
|
state.player.entity.id = p.player_id;
|
||||||
|
|
||||||
// TODO: have registry_holder be a struct because this sucks rn
|
// TODO: have registry_holder be a struct because this sucks rn
|
||||||
// best way would be to add serde support to azalea-nbt
|
// best way would be to add serde support to azalea-nbt
|
||||||
|
|
||||||
let registry_holder = p
|
let registry_holder = p
|
||||||
.registry_holder
|
.registry_holder
|
||||||
.as_compound()
|
.as_compound()
|
||||||
.expect("Registry holder is not a compound")
|
.expect("Registry holder is not a compound")
|
||||||
.get("")
|
.get("")
|
||||||
.expect("No \"\" tag")
|
.expect("No \"\" tag")
|
||||||
.as_compound()
|
.as_compound()
|
||||||
.expect("\"\" tag is not a compound");
|
.expect("\"\" tag is not a compound");
|
||||||
let dimension_types = registry_holder
|
let dimension_types = registry_holder
|
||||||
.get("minecraft:dimension_type")
|
.get("minecraft:dimension_type")
|
||||||
.expect("No dimension_type tag")
|
.expect("No dimension_type tag")
|
||||||
.as_compound()
|
.as_compound()
|
||||||
.expect("dimension_type is not a compound")
|
.expect("dimension_type is not a compound")
|
||||||
.get("value")
|
.get("value")
|
||||||
.expect("No dimension_type value")
|
.expect("No dimension_type value")
|
||||||
.as_list()
|
.as_list()
|
||||||
.expect("dimension_type value is not a list");
|
.expect("dimension_type value is not a list");
|
||||||
let dimension_type = dimension_types
|
let dimension_type = dimension_types
|
||||||
.iter()
|
.iter()
|
||||||
.find(|t| {
|
.find(|t| {
|
||||||
t.as_compound()
|
t.as_compound()
|
||||||
.expect("dimension_type value is not a compound")
|
.expect("dimension_type value is not a compound")
|
||||||
.get("name")
|
.get("name")
|
||||||
.expect("No name tag")
|
.expect("No name tag")
|
||||||
.as_string()
|
.as_string()
|
||||||
.expect("name is not a string")
|
.expect("name is not a string")
|
||||||
== p.dimension_type.to_string()
|
== p.dimension_type.to_string()
|
||||||
})
|
})
|
||||||
.expect(&format!("No dimension_type with name {}", p.dimension_type))
|
.expect(&format!("No dimension_type with name {}", p.dimension_type))
|
||||||
.as_compound()
|
.as_compound()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get("element")
|
.get("element")
|
||||||
.expect("No element tag")
|
.expect("No element tag")
|
||||||
.as_compound()
|
.as_compound()
|
||||||
.expect("element is not a compound");
|
.expect("element is not a compound");
|
||||||
let height = (*dimension_type
|
let height = (*dimension_type
|
||||||
.get("height")
|
.get("height")
|
||||||
.expect("No height tag")
|
.expect("No height tag")
|
||||||
.as_int()
|
.as_int()
|
||||||
.expect("height tag is not an int"))
|
.expect("height tag is not an int"))
|
||||||
.try_into()
|
.try_into()
|
||||||
.expect("height is not a u32");
|
.expect("height is not a u32");
|
||||||
let min_y = (*dimension_type
|
let min_y = (*dimension_type
|
||||||
.get("min_y")
|
.get("min_y")
|
||||||
.expect("No min_y tag")
|
.expect("No min_y tag")
|
||||||
.as_int()
|
.as_int()
|
||||||
.expect("min_y tag is not an int"))
|
.expect("min_y tag is not an int"))
|
||||||
.try_into()
|
.try_into()
|
||||||
.expect("min_y is not an i32");
|
.expect("min_y is not an i32");
|
||||||
|
|
||||||
state.world = Some(World {
|
state.world = Some(World::new(16, height, min_y));
|
||||||
height,
|
}
|
||||||
min_y,
|
|
||||||
storage: ChunkStorage::new(16),
|
|
||||||
entities: EntityStorage::new(),
|
|
||||||
});
|
|
||||||
|
|
||||||
conn.lock()
|
conn.lock()
|
||||||
.await
|
.await
|
||||||
|
@ -321,8 +324,7 @@ impl Client {
|
||||||
GamePacket::ClientboundSetChunkCacheCenterPacket(p) => {
|
GamePacket::ClientboundSetChunkCacheCenterPacket(p) => {
|
||||||
println!("Got chunk cache center packet {:?}", p);
|
println!("Got chunk cache center packet {:?}", p);
|
||||||
state
|
state
|
||||||
.lock()
|
.lock()?
|
||||||
.await
|
|
||||||
.world
|
.world
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -334,8 +336,7 @@ impl Client {
|
||||||
// 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
|
state
|
||||||
.lock()
|
.lock()?
|
||||||
.await
|
|
||||||
.world
|
.world
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.expect("World doesn't exist! We should've gotten a login packet by now.")
|
.expect("World doesn't exist! We should've gotten a login packet by now.")
|
||||||
|
@ -349,13 +350,11 @@ impl Client {
|
||||||
println!("Got add entity packet {:?}", p);
|
println!("Got add entity packet {:?}", p);
|
||||||
let entity = Entity::from(p);
|
let entity = Entity::from(p);
|
||||||
state
|
state
|
||||||
.lock()
|
.lock()?
|
||||||
.await
|
|
||||||
.world
|
.world
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.expect("World doesn't exist! We should've gotten a login packet by now.")
|
.expect("World doesn't exist! We should've gotten a login packet by now.")
|
||||||
.entities
|
.add_entity(entity);
|
||||||
.insert(entity);
|
|
||||||
}
|
}
|
||||||
GamePacket::ClientboundSetEntityDataPacket(p) => {
|
GamePacket::ClientboundSetEntityDataPacket(p) => {
|
||||||
// println!("Got set entity data packet {:?}", p);
|
// println!("Got set entity data packet {:?}", p);
|
||||||
|
@ -392,6 +391,18 @@ impl Client {
|
||||||
}
|
}
|
||||||
GamePacket::ClientboundTeleportEntityPacket(p) => {
|
GamePacket::ClientboundTeleportEntityPacket(p) => {
|
||||||
// println!("Got teleport entity packet {:?}", p);
|
// println!("Got teleport entity packet {:?}", p);
|
||||||
|
let state_lock = state.lock()?;
|
||||||
|
|
||||||
|
// let entity = state_lock
|
||||||
|
// .world
|
||||||
|
// .unwrap()
|
||||||
|
// .entity_by_id(p.id)
|
||||||
|
// .ok_or("Teleporting entity that doesn't exist.".to_string())?;
|
||||||
|
// state_lock
|
||||||
|
// .world
|
||||||
|
// .as_mut()
|
||||||
|
// .expect("World doesn't exist! We should've gotten a login packet by now.")
|
||||||
|
// .move_entity(&mut entity, new_pos)
|
||||||
}
|
}
|
||||||
GamePacket::ClientboundUpdateAdvancementsPacket(p) => {
|
GamePacket::ClientboundUpdateAdvancementsPacket(p) => {
|
||||||
println!("Got update advancements packet {:?}", p);
|
println!("Got update advancements packet {:?}", p);
|
||||||
|
@ -457,9 +468,23 @@ impl Client {
|
||||||
}
|
}
|
||||||
_ => panic!("Unexpected packet {:?}", packet),
|
_ => panic!("Unexpected packet {:?}", packet),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn next(&mut self) -> Option<Event> {
|
pub async fn next(&mut self) -> Option<Event> {
|
||||||
self.event_receiver.recv().await
|
self.event_receiver.recv().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> From<std::sync::PoisonError<T>> for HandleError {
|
||||||
|
fn from(e: std::sync::PoisonError<T>) -> Self {
|
||||||
|
HandleError(e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for HandleError {
|
||||||
|
fn from(e: String) -> Self {
|
||||||
|
HandleError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ impl Rem<i32> for BlockPos {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
|
||||||
pub struct ChunkPos {
|
pub struct ChunkPos {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
pub z: i32,
|
pub z: i32,
|
||||||
|
@ -164,6 +164,12 @@ impl From<&EntityPos> for BlockPos {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&EntityPos> for ChunkPos {
|
||||||
|
fn from(pos: &EntityPos) -> Self {
|
||||||
|
ChunkPos::from(&BlockPos::from(pos))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -181,4 +187,15 @@ mod tests {
|
||||||
let chunk_block_pos = ChunkBlockPos::from(&block_pos);
|
let chunk_block_pos = ChunkBlockPos::from(&block_pos);
|
||||||
assert_eq!(chunk_block_pos, ChunkBlockPos::new(5, 78, 14));
|
assert_eq!(chunk_block_pos, ChunkBlockPos::new(5, 78, 14));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_entity_pos_to_chunk_pos() {
|
||||||
|
let entity_pos = EntityPos {
|
||||||
|
x: 33.5,
|
||||||
|
y: 77.0,
|
||||||
|
z: -19.6,
|
||||||
|
};
|
||||||
|
let chunk_pos = ChunkPos::from(&entity_pos);
|
||||||
|
assert_eq!(chunk_pos, ChunkPos::new(2, -2));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use azalea_core::EntityPos;
|
use azalea_core::{ChunkPos, EntityPos};
|
||||||
#[cfg(feature = "protocol")]
|
#[cfg(feature = "protocol")]
|
||||||
use azalea_protocol::packets::game::clientbound_add_entity_packet::ClientboundAddEntityPacket;
|
use azalea_protocol::packets::game::clientbound_add_entity_packet::ClientboundAddEntityPacket;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -16,19 +16,16 @@ impl Entity {
|
||||||
&self.pos
|
&self.pos
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_pos(&mut self, pos: EntityPos) {
|
/// Sets the position of the entity. This doesn't update the cache in
|
||||||
// TODO: check if it moved to another chunk
|
/// azalea-world, and should only be used within azalea-world!
|
||||||
self.pos = pos;
|
pub fn unsafe_move(&mut self, new_pos: EntityPos) {
|
||||||
|
self.pos = new_pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "protocol")]
|
#[cfg(feature = "protocol")]
|
||||||
impl From<&azalea_protocol::packets::game::clientbound_add_entity_packet::ClientboundAddEntityPacket>
|
impl From<&ClientboundAddEntityPacket> for Entity {
|
||||||
for Entity
|
fn from(p: &ClientboundAddEntityPacket) -> Self {
|
||||||
{
|
|
||||||
fn from(
|
|
||||||
p: &azalea_protocol::packets::game::clientbound_add_entity_packet::ClientboundAddEntityPacket,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
id: p.id,
|
id: p.id,
|
||||||
uuid: p.uuid,
|
uuid: p.uuid,
|
||||||
|
|
|
@ -11,6 +11,7 @@ azalea-core = {path = "../azalea-core"}
|
||||||
azalea-entity = {path = "../azalea-entity"}
|
azalea-entity = {path = "../azalea-entity"}
|
||||||
azalea-nbt = {path = "../azalea-nbt"}
|
azalea-nbt = {path = "../azalea-nbt"}
|
||||||
azalea-protocol = {path = "../azalea-protocol"}
|
azalea-protocol = {path = "../azalea-protocol"}
|
||||||
|
log = "0.4.17"
|
||||||
nohash-hasher = "0.2.0"
|
nohash-hasher = "0.2.0"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::bit_storage::BitStorage;
|
|
||||||
use crate::palette::PalettedContainer;
|
use crate::palette::PalettedContainer;
|
||||||
use crate::palette::PalettedContainerType;
|
use crate::palette::PalettedContainerType;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
@ -13,10 +12,13 @@ use std::{
|
||||||
|
|
||||||
const SECTION_HEIGHT: u32 = 16;
|
const SECTION_HEIGHT: u32 = 16;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct ChunkStorage {
|
pub struct ChunkStorage {
|
||||||
pub view_center: ChunkPos,
|
pub view_center: ChunkPos,
|
||||||
chunk_radius: u32,
|
chunk_radius: u32,
|
||||||
view_range: u32,
|
view_range: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub min_y: i32,
|
||||||
// chunks is a list of size chunk_radius * chunk_radius
|
// chunks is a list of size chunk_radius * chunk_radius
|
||||||
chunks: Vec<Option<Arc<Mutex<Chunk>>>>,
|
chunks: Vec<Option<Arc<Mutex<Chunk>>>>,
|
||||||
}
|
}
|
||||||
|
@ -32,12 +34,14 @@ fn floor_mod(x: i32, y: u32) -> u32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChunkStorage {
|
impl ChunkStorage {
|
||||||
pub fn new(chunk_radius: u32) -> Self {
|
pub fn new(chunk_radius: u32, height: u32, min_y: i32) -> Self {
|
||||||
let view_range = chunk_radius * 2 + 1;
|
let view_range = chunk_radius * 2 + 1;
|
||||||
ChunkStorage {
|
ChunkStorage {
|
||||||
view_center: ChunkPos::new(0, 0),
|
view_center: ChunkPos::new(0, 0),
|
||||||
chunk_radius,
|
chunk_radius,
|
||||||
view_range,
|
view_range,
|
||||||
|
height,
|
||||||
|
min_y,
|
||||||
chunks: vec![None; (view_range * view_range) as usize],
|
chunks: vec![None; (view_range * view_range) as usize],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,6 +65,29 @@ impl ChunkStorage {
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn replace_with_packet_data(
|
||||||
|
&mut self,
|
||||||
|
pos: &ChunkPos,
|
||||||
|
data: &mut impl Read,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
if !self.in_range(pos) {
|
||||||
|
println!(
|
||||||
|
"Ignoring chunk since it's not in the view range: {}, {}",
|
||||||
|
pos.x, pos.z
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let chunk = Arc::new(Mutex::new(Chunk::read_with_world_height(
|
||||||
|
data,
|
||||||
|
self.height,
|
||||||
|
)?));
|
||||||
|
println!("Loaded chunk {:?}", pos);
|
||||||
|
self[pos] = Some(chunk);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Index<&ChunkPos> for ChunkStorage {
|
impl Index<&ChunkPos> for ChunkStorage {
|
||||||
|
@ -84,7 +111,7 @@ pub struct Chunk {
|
||||||
|
|
||||||
impl Chunk {
|
impl Chunk {
|
||||||
pub fn read_with_world(buf: &mut impl Read, data: &World) -> Result<Self, String> {
|
pub fn read_with_world(buf: &mut impl Read, data: &World) -> Result<Self, String> {
|
||||||
Self::read_with_world_height(buf, data.height)
|
Self::read_with_world_height(buf, data.height())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_with_world_height(buf: &mut impl Read, world_height: u32) -> Result<Self, String> {
|
pub fn read_with_world_height(buf: &mut impl Read, world_height: u32) -> Result<Self, String> {
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use azalea_core::ChunkPos;
|
use azalea_core::ChunkPos;
|
||||||
use azalea_entity::Entity;
|
use azalea_entity::Entity;
|
||||||
use nohash_hasher::IntMap;
|
use log::warn;
|
||||||
|
use nohash_hasher::{IntMap, IntSet};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EntityStorage {
|
pub struct EntityStorage {
|
||||||
by_id: IntMap<u32, Entity>,
|
by_id: IntMap<u32, Entity>,
|
||||||
// TODO: this doesn't work yet (should be updated in the set_pos method in azalea-entity)
|
// TODO: this doesn't work yet (should be updated in the set_pos method in azalea-entity)
|
||||||
by_chunk: HashMap<ChunkPos, u32>,
|
by_chunk: HashMap<ChunkPos, IntSet<u32>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EntityStorage {
|
impl EntityStorage {
|
||||||
|
@ -22,13 +22,24 @@ impl EntityStorage {
|
||||||
/// Add an entity to the storage.
|
/// Add an entity to the storage.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn insert(&mut self, entity: Entity) {
|
pub fn insert(&mut self, entity: Entity) {
|
||||||
|
self.by_chunk
|
||||||
|
.entry(ChunkPos::from(entity.pos()))
|
||||||
|
.or_default()
|
||||||
|
.insert(entity.id);
|
||||||
self.by_id.insert(entity.id, entity);
|
self.by_id.insert(entity.id, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove an entity from the storage by its id.
|
/// Remove an entity from the storage by its id.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn remove_by_id(&mut self, id: u32) {
|
pub fn remove_by_id(&mut self, id: u32) {
|
||||||
self.by_id.remove(&id);
|
if let Some(entity) = self.by_id.remove(&id) {
|
||||||
|
let entity_chunk = ChunkPos::from(entity.pos());
|
||||||
|
if let None = self.by_chunk.remove(&entity_chunk) {
|
||||||
|
warn!("Tried to remove entity with id {id} from chunk {entity_chunk:?} but it was not found.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Tried to remove entity with id {id} but it was not found.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to an entity by its id.
|
/// Get a reference to an entity by its id.
|
||||||
|
@ -42,4 +53,30 @@ impl EntityStorage {
|
||||||
pub fn get_mut_by_id(&mut self, id: u32) -> Option<&mut Entity> {
|
pub fn get_mut_by_id(&mut self, id: u32) -> Option<&mut Entity> {
|
||||||
self.by_id.get_mut(&id)
|
self.by_id.get_mut(&id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clear all entities in a chunk.
|
||||||
|
pub fn clear_chunk(&mut self, chunk: &ChunkPos) {
|
||||||
|
if let Some(entities) = self.by_chunk.remove(chunk) {
|
||||||
|
for entity_id in entities {
|
||||||
|
self.by_id.remove(&entity_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates an entity from its old chunk.
|
||||||
|
#[inline]
|
||||||
|
pub fn update_entity_chunk(
|
||||||
|
&mut self,
|
||||||
|
entity_id: u32,
|
||||||
|
old_chunk: &ChunkPos,
|
||||||
|
new_chunk: &ChunkPos,
|
||||||
|
) {
|
||||||
|
if let Some(entities) = self.by_chunk.get_mut(old_chunk) {
|
||||||
|
entities.remove(&entity_id);
|
||||||
|
}
|
||||||
|
self.by_chunk
|
||||||
|
.entry(*new_chunk)
|
||||||
|
.or_default()
|
||||||
|
.insert(entity_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,8 @@ mod entity;
|
||||||
mod palette;
|
mod palette;
|
||||||
|
|
||||||
use azalea_block::BlockState;
|
use azalea_block::BlockState;
|
||||||
use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos};
|
use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos, EntityPos};
|
||||||
|
use azalea_entity::Entity;
|
||||||
use azalea_protocol::mc_buf::{McBufReadable, McBufWritable};
|
use azalea_protocol::mc_buf::{McBufReadable, McBufWritable};
|
||||||
pub use bit_storage::BitStorage;
|
pub use bit_storage::BitStorage;
|
||||||
pub use chunk::{Chunk, ChunkStorage};
|
pub use chunk::{Chunk, ChunkStorage};
|
||||||
|
@ -26,61 +27,78 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct World {
|
pub struct World {
|
||||||
pub storage: ChunkStorage,
|
chunk_storage: ChunkStorage,
|
||||||
pub entities: EntityStorage,
|
entity_storage: EntityStorage,
|
||||||
pub height: u32,
|
|
||||||
pub min_y: i32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl World {
|
impl World {
|
||||||
|
pub fn new(chunk_radius: u32, height: u32, min_y: i32) -> Self {
|
||||||
|
World {
|
||||||
|
chunk_storage: ChunkStorage::new(chunk_radius, height, min_y),
|
||||||
|
entity_storage: EntityStorage::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn replace_with_packet_data(
|
pub fn replace_with_packet_data(
|
||||||
&mut self,
|
&mut self,
|
||||||
pos: &ChunkPos,
|
pos: &ChunkPos,
|
||||||
data: &mut impl Read,
|
data: &mut impl Read,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if !self.storage.in_range(pos) {
|
self.chunk_storage.replace_with_packet_data(pos, data)
|
||||||
println!(
|
|
||||||
"Ignoring chunk since it's not in the view range: {}, {}",
|
|
||||||
pos.x, pos.z
|
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
// let existing_chunk = &self.storage[pos];
|
|
||||||
|
|
||||||
let chunk = Arc::new(Mutex::new(Chunk::read_with_world(data, self)?));
|
|
||||||
println!("Loaded chunk {:?}", pos);
|
|
||||||
self.storage[pos] = Some(chunk);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_view_center(&mut self, pos: &ChunkPos) {
|
pub fn update_view_center(&mut self, pos: &ChunkPos) {
|
||||||
self.storage.view_center = *pos;
|
self.chunk_storage.view_center = *pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> {
|
pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> {
|
||||||
self.storage.get_block_state(pos, self.min_y)
|
self.chunk_storage.get_block_state(pos, self.min_y())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_entity(&mut self, entity_id: u32, new_pos: EntityPos) -> Result<(), String> {
|
||||||
|
let entity = self
|
||||||
|
.entity_storage
|
||||||
|
.get_mut_by_id(entity_id)
|
||||||
|
.ok_or("Moving entity that doesn't exist".to_string())?;
|
||||||
|
let old_chunk = ChunkPos::from(entity.pos());
|
||||||
|
let new_chunk = ChunkPos::from(&new_pos);
|
||||||
|
// this is fine because we update the chunk below
|
||||||
|
entity.unsafe_move(new_pos);
|
||||||
|
if old_chunk != new_chunk {
|
||||||
|
self.entity_storage
|
||||||
|
.update_entity_chunk(entity_id, &old_chunk, &new_chunk);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_entity(&mut self, entity: Entity) {
|
||||||
|
self.entity_storage.insert(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(&self) -> u32 {
|
||||||
|
self.chunk_storage.height
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn min_y(&self) -> i32 {
|
||||||
|
self.chunk_storage.min_y
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn entity_by_id(&self, id: u32) -> Option<&Entity> {
|
||||||
|
self.entity_storage.get_by_id(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Index<&ChunkPos> for World {
|
impl Index<&ChunkPos> for World {
|
||||||
type Output = Option<Arc<Mutex<Chunk>>>;
|
type Output = Option<Arc<Mutex<Chunk>>>;
|
||||||
|
|
||||||
fn index(&self, pos: &ChunkPos) -> &Self::Output {
|
fn index(&self, pos: &ChunkPos) -> &Self::Output {
|
||||||
&self.storage[pos]
|
&self.chunk_storage[pos]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl IndexMut<&ChunkPos> for World {
|
impl IndexMut<&ChunkPos> for World {
|
||||||
fn index_mut<'a>(&'a mut self, pos: &ChunkPos) -> &'a mut Self::Output {
|
fn index_mut<'a>(&'a mut self, pos: &ChunkPos) -> &'a mut Self::Output {
|
||||||
&mut self.storage[pos]
|
&mut self.chunk_storage[pos]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// impl Index<&BlockPos> for World {
|
|
||||||
// type Output = Option<Arc<Mutex<Chunk>>>;
|
|
||||||
|
|
||||||
// fn index(&self, pos: &BlockPos) -> &Self::Output {
|
|
||||||
// let chunk = &self[ChunkPos::from(pos)];
|
|
||||||
// // chunk.
|
|
||||||
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use azalea_client::{Account, Event};
|
use azalea_client::{Account, Event};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
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";
|
||||||
|
@ -15,18 +15,18 @@ async fn main() {
|
||||||
let mut client = account.join(&address.try_into().unwrap()).await.unwrap();
|
let mut client = account.join(&address.try_into().unwrap()).await.unwrap();
|
||||||
println!("connected");
|
println!("connected");
|
||||||
|
|
||||||
while let Some(e) = client.next().await {
|
while let Some(e) = &client.next().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 => {}
|
||||||
Event::Chat(_p) => {
|
Event::Chat(_p) => {
|
||||||
let state = client.state.lock().await;
|
let state = &client.state.lock()?;
|
||||||
let world = state.world.as_ref().unwrap();
|
let world = state.world.as_ref().unwrap();
|
||||||
println!("{:?}", world.entities);
|
println!("{:?}", world);
|
||||||
// world.get_block_state(state.player.entity.pos);
|
// world.get_block_state(state.player.entity.pos);
|
||||||
// println!("{}", p.message.to_ansi(None));
|
// println!("{}", p.message.to_ansi(None));
|
||||||
// if p.message.to_ansi(None) == "<py5> ok" {
|
// if p.message.to_ansi(None) == "<py5> ok" {
|
||||||
// let state = client.state.lock().await;
|
// let state = client.state.lock();
|
||||||
// let world = state.world.as_ref().unwrap();
|
// let world = state.world.as_ref().unwrap();
|
||||||
// let c = world.get_block_state(&BlockPos::new(5, 78, -2)).unwrap();
|
// let c = world.get_block_state(&BlockPos::new(5, 78, -2)).unwrap();
|
||||||
// println!("block state: {:?}", c);
|
// println!("block state: {:?}", c);
|
||||||
|
@ -36,4 +36,6 @@ async fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("done");
|
println!("done");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ async fn main() {
|
||||||
if bot.entity.can_reach(target.bounding_box) {
|
if bot.entity.can_reach(target.bounding_box) {
|
||||||
bot.swing();
|
bot.swing();
|
||||||
}
|
}
|
||||||
if !h.using_held_item() && bot.state.lock().await.hunger <= 17 {
|
if !h.using_held_item() && bot.state.lock().hunger <= 17 {
|
||||||
bot.hold(azalea::ItemGroup::Food);
|
bot.hold(azalea::ItemGroup::Food);
|
||||||
tokio::task::spawn(bot.use_held_item());
|
tokio::task::spawn(bot.use_held_item());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue