mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
packet handling
now it runs the schedule only when we get a tick or packet 😄
also i systemified some more functions and did other random fixes so az-world and az-physics compile
making azalea-client use the ecs is almost done! all the hard parts are done now i hope, i just have to finish writing all the code so it actually works
This commit is contained in:
parent
9193c1b408
commit
50f6578794
20 changed files with 1300 additions and 1234 deletions
117
Cargo.lock
generated
117
Cargo.lock
generated
|
@ -278,7 +278,10 @@ dependencies = [
|
|||
"azalea-protocol",
|
||||
"azalea-registry",
|
||||
"azalea-world",
|
||||
"bevy_app",
|
||||
"bevy_ecs",
|
||||
"futures",
|
||||
"iyes_loopless",
|
||||
"log",
|
||||
"nohash-hasher",
|
||||
"once_cell",
|
||||
|
@ -463,6 +466,7 @@ checksum = "536e4d0018347478545ed8b6cb6e57b9279ee984868e81b7c0e78e0fb3222e42"
|
|||
dependencies = [
|
||||
"bevy_derive",
|
||||
"bevy_ecs",
|
||||
"bevy_reflect",
|
||||
"bevy_utils",
|
||||
"downcast-rs",
|
||||
"wasm-bindgen",
|
||||
|
@ -489,6 +493,7 @@ dependencies = [
|
|||
"async-channel",
|
||||
"bevy_ecs_macros",
|
||||
"bevy_ptr",
|
||||
"bevy_reflect",
|
||||
"bevy_tasks",
|
||||
"bevy_utils",
|
||||
"downcast-rs",
|
||||
|
@ -522,12 +527,56 @@ dependencies = [
|
|||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_math"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d434c77ab766c806ed9062ef8a7285b3b02b47df51f188d4496199c3ac062eaf"
|
||||
dependencies = [
|
||||
"glam",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_ptr"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ec44f7655039546bc5d34d98de877083473f3e9b2b81d560c528d6d74d3eff4"
|
||||
|
||||
[[package]]
|
||||
name = "bevy_reflect"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6deae303a7f69dc243b2fa35b5e193cc920229f448942080c8eb2dbd9de6d37a"
|
||||
dependencies = [
|
||||
"bevy_math",
|
||||
"bevy_ptr",
|
||||
"bevy_reflect_derive",
|
||||
"bevy_utils",
|
||||
"downcast-rs",
|
||||
"erased-serde",
|
||||
"glam",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_reflect_derive"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2bf4cb9cd5acb4193f890f36cb63679f1502e2de025e66a63b194b8b133d018"
|
||||
dependencies = [
|
||||
"bevy_macro_utils",
|
||||
"bit-set",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_tasks"
|
||||
version = "0.9.1"
|
||||
|
@ -543,6 +592,19 @@ dependencies = [
|
|||
"wasm-bindgen-futures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_time"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a5c38a6d3ea929c7f81e6adf5a6c62cf7e8c40f5106c2174d6057e9d8ea624d"
|
||||
dependencies = [
|
||||
"bevy_app",
|
||||
"bevy_ecs",
|
||||
"bevy_reflect",
|
||||
"bevy_utils",
|
||||
"crossbeam-channel",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_utils"
|
||||
version = "0.9.1"
|
||||
|
@ -557,6 +619,21 @@ dependencies = [
|
|||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
|
@ -604,6 +681,12 @@ version = "3.11.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
|
@ -924,6 +1007,15 @@ dependencies = [
|
|||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4ca605381c017ec7a5fef5e548f1cfaa419ed0f6df6367339300db74c92aa7d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.3"
|
||||
|
@ -1127,6 +1219,16 @@ version = "0.26.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
|
||||
|
||||
[[package]]
|
||||
name = "glam"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.15"
|
||||
|
@ -1333,6 +1435,18 @@ version = "1.0.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
|
||||
|
||||
[[package]]
|
||||
name = "iyes_loopless"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c47fd2cbdb1d7f295c25e6bfccfd78a84b6eef3055bc9f01b34ae861721b01ee"
|
||||
dependencies = [
|
||||
"bevy_app",
|
||||
"bevy_ecs",
|
||||
"bevy_time",
|
||||
"bevy_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.60"
|
||||
|
@ -2076,6 +2190,9 @@ name = "smallvec"
|
|||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -24,19 +24,9 @@ debug = true
|
|||
# decoding packets takes forever if we don't do this
|
||||
[profile.dev.package.azalea-crypto]
|
||||
opt-level = 3
|
||||
[profile.dev.package.cipher]
|
||||
opt-level = 3
|
||||
[profile.dev.package.cfb8]
|
||||
opt-level = 3
|
||||
[profile.dev.package.aes]
|
||||
opt-level = 3
|
||||
[profile.dev.package.crypto-common]
|
||||
opt-level = 3
|
||||
[profile.dev.package.generic-array]
|
||||
opt-level = 3
|
||||
[profile.dev.package.typenum]
|
||||
opt-level = 3
|
||||
[profile.dev.package.inout]
|
||||
opt-level = 3
|
||||
[profile.dev.package.flate2]
|
||||
opt-level = 3
|
||||
|
|
|
@ -20,7 +20,10 @@ azalea-physics = {path = "../azalea-physics", version = "0.5.0"}
|
|||
azalea-protocol = {path = "../azalea-protocol", version = "0.5.0"}
|
||||
azalea-registry = {path = "../azalea-registry", version = "0.5.0"}
|
||||
azalea-world = {path = "../azalea-world", version = "0.5.0"}
|
||||
bevy_app = {version = "0.9.1", default-features = false}
|
||||
bevy_ecs = {version = "0.9.1", default-features = false}
|
||||
futures = "0.3.25"
|
||||
iyes_loopless = "0.9.1"
|
||||
log = "0.4.17"
|
||||
nohash-hasher = "0.2.0"
|
||||
once_cell = "1.16.0"
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
pub use crate::chat::ChatPacket;
|
||||
use crate::{
|
||||
local_player::LocalPlayer, movement::WalkDirection, plugins::PluginStates, Account, PlayerInfo,
|
||||
local_player::LocalPlayer,
|
||||
movement::{send_position, WalkDirection},
|
||||
packet_handling,
|
||||
plugins::PluginStates,
|
||||
Account, PlayerInfo,
|
||||
};
|
||||
|
||||
use azalea_auth::{game_profile::GameProfile, sessionserver::SessionServerError};
|
||||
use azalea_core::{ChunkPos, ResourceLocation, Vec3};
|
||||
use azalea_protocol::{
|
||||
|
@ -34,16 +39,19 @@ use azalea_world::{
|
|||
entity::{
|
||||
self,
|
||||
metadata::{self, PlayerMetadataBundle},
|
||||
EntityId,
|
||||
Entity,
|
||||
},
|
||||
PartialChunkStorage, PartialWorld, WeakWorldContainer, World,
|
||||
EntityInfos, PartialChunkStorage, PartialWorld, World, WorldContainer,
|
||||
};
|
||||
use bevy_app::App;
|
||||
use bevy_ecs::{
|
||||
prelude::Component,
|
||||
query::{QueryState, WorldQuery},
|
||||
schedule::{Schedule, Stage, StageLabel, SystemStage},
|
||||
schedule::{IntoSystemDescriptor, Schedule, Stage, StageLabel, SystemStage},
|
||||
system::{Query, SystemState},
|
||||
};
|
||||
use futures::{stream::FuturesUnordered, StreamExt};
|
||||
use iyes_loopless::prelude::*;
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use std::{
|
||||
|
@ -52,7 +60,9 @@ use std::{
|
|||
collections::HashMap,
|
||||
fmt::Debug,
|
||||
io::{self, Cursor},
|
||||
ops::DerefMut,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use thiserror::Error;
|
||||
use tokio::{
|
||||
|
@ -100,11 +110,12 @@ pub enum Event {
|
|||
/// Things that a player in the world will want to know are in [`LocalPlayer`].
|
||||
pub struct Client {
|
||||
pub profile: GameProfile,
|
||||
pub entity_id: Arc<RwLock<EntityId>>,
|
||||
/// The entity for this client in the ECS.
|
||||
pub entity: Entity,
|
||||
/// 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>>,
|
||||
world_container: Arc<RwLock<WorldContainer>>,
|
||||
/// The world that this client is in.
|
||||
pub world: Arc<RwLock<PartialWorld>>,
|
||||
|
||||
|
@ -113,6 +124,11 @@ pub struct Client {
|
|||
/// the `azalea` crate. you can ignore this field.
|
||||
pub plugins: Arc<PluginStates>,
|
||||
tasks: Arc<Mutex<Vec<JoinHandle<()>>>>,
|
||||
|
||||
/// The entity component system. You probably don't need to access this
|
||||
/// directly. Note that if you're using a shared world (i.e. a swarm), this
|
||||
/// will contain all entities in all worlds.
|
||||
pub ecs: Arc<Mutex<bevy_ecs::world::World>>,
|
||||
}
|
||||
|
||||
/// Whether we should ignore errors when decoding packets.
|
||||
|
@ -143,19 +159,23 @@ impl Client {
|
|||
/// defaults, otherwise use [`Client::join`].
|
||||
pub fn new(
|
||||
profile: GameProfile,
|
||||
world_container: Option<Arc<RwLock<WeakWorldContainer>>>,
|
||||
world_container: Option<Arc<RwLock<WorldContainer>>>,
|
||||
entity: Entity,
|
||||
ecs: Arc<Mutex<bevy_ecs::world::World>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
profile,
|
||||
// default our id to 0, it'll be set later
|
||||
entity_id: Arc::new(RwLock::new(EntityId(0))),
|
||||
entity,
|
||||
world: Arc::new(RwLock::new(PartialWorld::default())),
|
||||
world_container: world_container
|
||||
.unwrap_or_else(|| Arc::new(RwLock::new(WeakWorldContainer::new()))),
|
||||
.unwrap_or_else(|| Arc::new(RwLock::new(WorldContainer::new()))),
|
||||
// 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()),
|
||||
tasks: Arc::new(Mutex::new(Vec::new())),
|
||||
|
||||
ecs,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,6 +208,7 @@ impl Client {
|
|||
|
||||
let conn = Connection::new(&resolved_address).await?;
|
||||
let (conn, game_profile) = Self::handshake(conn, account, &address).await?;
|
||||
let (read_conn, write_conn) = conn.into_split();
|
||||
|
||||
// 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.
|
||||
|
@ -195,21 +216,43 @@ impl Client {
|
|||
let (tx, rx) = mpsc::channel(1);
|
||||
tx.send(Event::Init).await.expect("Failed to send event");
|
||||
|
||||
// we got the GameConnection, so the server is now connected :)
|
||||
let client = Client::new(game_profile.clone(), None);
|
||||
// An event that causes the schedule to run. This is only used internally.
|
||||
let (run_schedule_sender, run_schedule_receiver) = mpsc::unbounded_channel();
|
||||
|
||||
let (read_conn, write_conn) = conn.into_split();
|
||||
let ecs_lock = start_ecs(run_schedule_receiver).await;
|
||||
|
||||
let mut ecs = ecs_lock.lock();
|
||||
ecs.init_resource::<EntityInfos>();
|
||||
|
||||
let entity_mut = ecs.spawn_empty();
|
||||
let entity = entity_mut.id();
|
||||
|
||||
// we got the GameConnection, so the server is now connected :)
|
||||
let client = Client::new(game_profile.clone(), None, entity, ecs_lock);
|
||||
|
||||
let world = client.world();
|
||||
|
||||
let local_player = crate::local_player::LocalPlayer::new(
|
||||
entity,
|
||||
game_profile,
|
||||
write_conn,
|
||||
client.world.clone(),
|
||||
world.clone(),
|
||||
ecs.resource_mut::<EntityInfos>().deref_mut(),
|
||||
tx,
|
||||
);
|
||||
|
||||
// just start up the game loop and we're ready!
|
||||
// start receiving packets
|
||||
let packet_receiver = packet_handling::PacketReceiver {
|
||||
packets: Arc::new(Mutex::new(Vec::new())),
|
||||
run_schedule_sender,
|
||||
};
|
||||
|
||||
client.start_tasks(read_conn);
|
||||
tokio::spawn(packet_receiver.read_task(read_conn));
|
||||
|
||||
ecs.entity_mut(entity)
|
||||
.insert((local_player, packet_receiver));
|
||||
|
||||
// just start up the game loop and we're ready!
|
||||
|
||||
Ok((client, rx))
|
||||
}
|
||||
|
@ -359,688 +402,33 @@ impl Client {
|
|||
self.query::<&mut LocalPlayer>().into_inner()
|
||||
}
|
||||
|
||||
/// Start the protocol and game tick loop.
|
||||
#[doc(hidden)]
|
||||
pub fn start_tasks(&self, read_conn: ReadConnection<ClientboundGamePacket>) {
|
||||
// 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(self.protocol_loop(tx.clone(), read_conn)));
|
||||
let ecs = self.world_container.clone().read().ecs;
|
||||
tasks.push(tokio::spawn(Client::game_tick_loop(ecs.clone())));
|
||||
}
|
||||
|
||||
async fn protocol_loop(
|
||||
&self,
|
||||
tx: Sender<Event>,
|
||||
read_conn: ReadConnection<ClientboundGamePacket>,
|
||||
) {
|
||||
loop {
|
||||
let r = read_conn.lock().await.read().await;
|
||||
match r {
|
||||
Ok(packet) => LocalPlayer::send_event(Event::Packet(packet.clone()), &tx),
|
||||
Err(e) => {
|
||||
let e = *e;
|
||||
if let ReadPacketError::ConnectionClosed = e {
|
||||
info!("Connection closed");
|
||||
if let Err(e) = client.disconnect().await {
|
||||
error!("Error shutting down connection: {:?}", e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
let default_backtrace = Backtrace::capture();
|
||||
if IGNORE_ERRORS {
|
||||
let backtrace =
|
||||
any::request_ref::<Backtrace>(&e).unwrap_or(&default_backtrace);
|
||||
warn!("{e}\n{backtrace}");
|
||||
match e {
|
||||
ReadPacketError::FrameSplitter { .. } => panic!("Error: {e:?}"),
|
||||
_ => continue,
|
||||
}
|
||||
} else {
|
||||
let backtrace =
|
||||
any::request_ref::<Backtrace>(&e).unwrap_or(&default_backtrace);
|
||||
panic!("{e}\n{backtrace}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handling packets is outside of the ecs schedule since it's not bound to
|
||||
// ticks.
|
||||
pub fn handle_packet(
|
||||
client: &Client,
|
||||
packet: ClientboundGamePacket,
|
||||
ecs: &mut bevy_ecs::world::World,
|
||||
tx: &mpsc::Sender<Event>,
|
||||
) -> Result<(), HandlePacketError> {
|
||||
let (mut local_player,) = query.get_mut((*player_entity_id).into()).unwrap();
|
||||
|
||||
match &packet {
|
||||
ClientboundGamePacket::Login(p) => {
|
||||
debug!("Got login packet");
|
||||
|
||||
{
|
||||
// // write p into login.txt
|
||||
// std::io::Write::write_all(
|
||||
// &mut std::fs::File::create("login.txt").unwrap(),
|
||||
// format!("{:#?}", p).as_bytes(),
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// TODO: have registry_holder be a struct because this sucks rn
|
||||
// best way would be to add serde support to azalea-nbt
|
||||
|
||||
let registry_holder = p
|
||||
.registry_holder
|
||||
.as_compound()
|
||||
.expect("Registry holder is not a compound")
|
||||
.get("")
|
||||
.expect("No \"\" tag")
|
||||
.as_compound()
|
||||
.expect("\"\" tag is not a compound");
|
||||
let dimension_types = registry_holder
|
||||
.get("minecraft:dimension_type")
|
||||
.expect("No dimension_type tag")
|
||||
.as_compound()
|
||||
.expect("dimension_type is not a compound")
|
||||
.get("value")
|
||||
.expect("No dimension_type value")
|
||||
.as_list()
|
||||
.expect("dimension_type value is not a list");
|
||||
let dimension_type = dimension_types
|
||||
.iter()
|
||||
.find(|t| {
|
||||
t.as_compound()
|
||||
.expect("dimension_type value is not a compound")
|
||||
.get("name")
|
||||
.expect("No name tag")
|
||||
.as_string()
|
||||
.expect("name is not a string")
|
||||
== p.dimension_type.to_string()
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
panic!("No dimension_type with name {}", p.dimension_type)
|
||||
})
|
||||
.as_compound()
|
||||
.unwrap()
|
||||
.get("element")
|
||||
.expect("No element tag")
|
||||
.as_compound()
|
||||
.expect("element is not a compound");
|
||||
let height = (*dimension_type
|
||||
.get("height")
|
||||
.expect("No height tag")
|
||||
.as_int()
|
||||
.expect("height tag is not an int"))
|
||||
.try_into()
|
||||
.expect("height is not a u32");
|
||||
let min_y = *dimension_type
|
||||
.get("min_y")
|
||||
.expect("No min_y tag")
|
||||
.as_int()
|
||||
.expect("min_y tag is not an int");
|
||||
|
||||
let world_name = p.dimension.clone();
|
||||
|
||||
local_player.world_name = Some(world_name.clone());
|
||||
// add this world to the world_container (or don't if it's already there)
|
||||
let weak_world = world_container.insert(world_name, 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 = local_player.world.write();
|
||||
*world_lock = PartialWorld::new(
|
||||
local_player.client_information.view_distance.into(),
|
||||
weak_world,
|
||||
Some(EntityId(p.player_id)),
|
||||
);
|
||||
|
||||
let player_bundle = entity::PlayerBundle {
|
||||
entity: entity::EntityBundle::new(
|
||||
local_player.profile.uuid,
|
||||
Vec3::default(),
|
||||
azalea_registry::EntityKind::Player,
|
||||
),
|
||||
metadata: PlayerMetadataBundle::default(),
|
||||
};
|
||||
// let entity = EntityData::new(
|
||||
// client.profile.uuid,
|
||||
// Vec3::default(),
|
||||
// EntityMetadata::Player(metadata::Player::default()),
|
||||
// );
|
||||
// the first argument makes it so other entities don't update this entity in
|
||||
// a shared world
|
||||
world_lock.add_entity(EntityId(p.player_id), player_bundle);
|
||||
|
||||
*client.entity_id.write() = EntityId(p.player_id);
|
||||
}
|
||||
|
||||
// send the client information that we have set
|
||||
let client_information_packet: ClientInformation =
|
||||
client.local_player().client_information.clone();
|
||||
log::debug!(
|
||||
"Sending client information because login: {:?}",
|
||||
client_information_packet
|
||||
);
|
||||
client.write_packet(client_information_packet.get()).await?;
|
||||
|
||||
// brand
|
||||
client
|
||||
.write_packet(
|
||||
ServerboundCustomPayloadPacket {
|
||||
identifier: ResourceLocation::new("brand").unwrap(),
|
||||
// they don't have to know :)
|
||||
data: "vanilla".into(),
|
||||
}
|
||||
.get(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
tx.send(Event::Login).await?;
|
||||
}
|
||||
ClientboundGamePacket::SetChunkCacheRadius(p) => {
|
||||
debug!("Got set chunk cache radius packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::CustomPayload(p) => {
|
||||
debug!("Got custom payload packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::ChangeDifficulty(p) => {
|
||||
debug!("Got difficulty packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::Commands(_p) => {
|
||||
debug!("Got declare commands packet");
|
||||
}
|
||||
ClientboundGamePacket::PlayerAbilities(p) => {
|
||||
debug!("Got player abilities packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::SetCarriedItem(p) => {
|
||||
debug!("Got set carried item packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::UpdateTags(_p) => {
|
||||
debug!("Got update tags packet");
|
||||
}
|
||||
ClientboundGamePacket::Disconnect(p) => {
|
||||
debug!("Got disconnect packet {:?}", p);
|
||||
client.disconnect().await?;
|
||||
}
|
||||
ClientboundGamePacket::UpdateRecipes(_p) => {
|
||||
debug!("Got update recipes packet");
|
||||
}
|
||||
ClientboundGamePacket::EntityEvent(_p) => {
|
||||
// debug!("Got entity event packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::Recipe(_p) => {
|
||||
debug!("Got recipe packet");
|
||||
}
|
||||
ClientboundGamePacket::PlayerPosition(p) => {
|
||||
// TODO: reply with teleport confirm
|
||||
debug!("Got player position packet {:?}", p);
|
||||
|
||||
let (new_pos, y_rot, x_rot) = {
|
||||
let player_entity_id = *client.entity();
|
||||
let world = client.world();
|
||||
// let mut player_entity = world.entity_mut(player_entity_id).unwrap();
|
||||
let (mut physics, position) =
|
||||
client.query::<(&mut entity::Physics, &mut entity::Position)>();
|
||||
|
||||
let delta_movement = physics.delta;
|
||||
|
||||
let is_x_relative = p.relative_arguments.x;
|
||||
let is_y_relative = p.relative_arguments.y;
|
||||
let is_z_relative = p.relative_arguments.z;
|
||||
|
||||
let (delta_x, new_pos_x) = if is_x_relative {
|
||||
physics.last_pos.x += p.x;
|
||||
(delta_movement.x, position.x + p.x)
|
||||
} else {
|
||||
physics.last_pos.x = p.x;
|
||||
(0.0, p.x)
|
||||
};
|
||||
let (delta_y, new_pos_y) = if is_y_relative {
|
||||
physics.last_pos.y += p.y;
|
||||
(delta_movement.y, position.y + p.y)
|
||||
} else {
|
||||
physics.last_pos.y = p.y;
|
||||
(0.0, p.y)
|
||||
};
|
||||
let (delta_z, new_pos_z) = if is_z_relative {
|
||||
physics.last_pos.z += p.z;
|
||||
(delta_movement.z, position.z + p.z)
|
||||
} else {
|
||||
physics.last_pos.z = p.z;
|
||||
(0.0, p.z)
|
||||
};
|
||||
|
||||
let mut y_rot = p.y_rot;
|
||||
let mut x_rot = p.x_rot;
|
||||
if p.relative_arguments.x_rot {
|
||||
x_rot += physics.x_rot;
|
||||
}
|
||||
if p.relative_arguments.y_rot {
|
||||
y_rot += physics.y_rot;
|
||||
}
|
||||
|
||||
physics.delta = Vec3 {
|
||||
x: delta_x,
|
||||
y: delta_y,
|
||||
z: delta_z,
|
||||
};
|
||||
entity::set_rotation(physics.into_inner(), y_rot, x_rot);
|
||||
// TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means
|
||||
// so investigate that ig
|
||||
let new_pos = Vec3 {
|
||||
x: new_pos_x,
|
||||
y: new_pos_y,
|
||||
z: new_pos_z,
|
||||
};
|
||||
world
|
||||
.set_entity_pos(
|
||||
player_entity_id,
|
||||
new_pos,
|
||||
position.into_inner(),
|
||||
physics.into_inner(),
|
||||
)
|
||||
.expect("The player entity should always exist");
|
||||
|
||||
(new_pos, y_rot, x_rot)
|
||||
};
|
||||
|
||||
client
|
||||
.write_packet(ServerboundAcceptTeleportationPacket { id: p.id }.get())
|
||||
.await?;
|
||||
client
|
||||
.write_packet(
|
||||
ServerboundMovePlayerPosRotPacket {
|
||||
x: new_pos.x,
|
||||
y: new_pos.y,
|
||||
z: new_pos.z,
|
||||
y_rot,
|
||||
x_rot,
|
||||
// this is always false
|
||||
on_ground: false,
|
||||
}
|
||||
.get(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ClientboundGamePacket::PlayerInfoUpdate(p) => {
|
||||
debug!("Got player info packet {:?}", p);
|
||||
let mut events = Vec::new();
|
||||
{
|
||||
let mut players_lock = client.players.write();
|
||||
for updated_info in &p.entries {
|
||||
// add the new player maybe
|
||||
if p.actions.add_player {
|
||||
let player_info = PlayerInfo {
|
||||
profile: updated_info.profile.clone(),
|
||||
uuid: updated_info.profile.uuid,
|
||||
gamemode: updated_info.game_mode,
|
||||
latency: updated_info.latency,
|
||||
display_name: updated_info.display_name.clone(),
|
||||
};
|
||||
players_lock.insert(updated_info.profile.uuid, player_info.clone());
|
||||
events.push(Event::AddPlayer(player_info));
|
||||
} else if let Some(info) = players_lock.get_mut(&updated_info.profile.uuid)
|
||||
{
|
||||
// `else if` because the block for add_player above
|
||||
// already sets all the fields
|
||||
if p.actions.update_game_mode {
|
||||
info.gamemode = updated_info.game_mode;
|
||||
}
|
||||
if p.actions.update_latency {
|
||||
info.latency = updated_info.latency;
|
||||
}
|
||||
if p.actions.update_display_name {
|
||||
info.display_name = updated_info.display_name.clone();
|
||||
}
|
||||
events.push(Event::UpdatePlayer(info.clone()));
|
||||
} else {
|
||||
warn!(
|
||||
"Ignoring PlayerInfoUpdate for unknown player {}",
|
||||
updated_info.profile.uuid
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
for event in events {
|
||||
tx.send(event).await?;
|
||||
}
|
||||
}
|
||||
ClientboundGamePacket::PlayerInfoRemove(p) => {
|
||||
let mut events = Vec::new();
|
||||
{
|
||||
let mut players_lock = client.players.write();
|
||||
for uuid in &p.profile_ids {
|
||||
if let Some(info) = players_lock.remove(uuid) {
|
||||
events.push(Event::RemovePlayer(info));
|
||||
}
|
||||
}
|
||||
}
|
||||
for event in events {
|
||||
tx.send(event).await?;
|
||||
}
|
||||
}
|
||||
ClientboundGamePacket::SetChunkCacheCenter(p) => {
|
||||
debug!("Got chunk cache center packet {:?}", p);
|
||||
client
|
||||
.world
|
||||
.write()
|
||||
.update_view_center(&ChunkPos::new(p.x, p.z));
|
||||
}
|
||||
ClientboundGamePacket::LevelChunkWithLight(p) => {
|
||||
// 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().chunks.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
|
||||
.world
|
||||
.write()
|
||||
.replace_with_packet_data(&pos, &mut Cursor::new(&p.chunk_data.data))
|
||||
{
|
||||
error!("Couldn't set chunk data: {}", e);
|
||||
}
|
||||
}
|
||||
ClientboundGamePacket::LightUpdate(_p) => {
|
||||
// debug!("Got light update packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::AddEntity(p) => {
|
||||
debug!("Got add entity packet {:?}", p);
|
||||
let bundle = p.as_entity_bundle();
|
||||
let mut world = client.world.write();
|
||||
world.add_entity(EntityId(p.id), bundle);
|
||||
// the bundle doesn't include the default entity metadata so we add that
|
||||
// separately
|
||||
let mut entities = world.entity_infos.shared.write();
|
||||
let mut entity = entities.ecs_entity_mut(EntityId(p.id)).unwrap();
|
||||
p.apply_metadata(&mut entity);
|
||||
}
|
||||
ClientboundGamePacket::SetEntityData(p) => {
|
||||
debug!("Got set entity data packet {:?}", p);
|
||||
let world = client.world.write();
|
||||
let mut entities = world.entity_infos.shared.write();
|
||||
let entity = entities.ecs_entity_mut(EntityId(p.id));
|
||||
if let Some(mut entity) = entity {
|
||||
entity::metadata::apply_metadata(&mut entity, p.packed_items.0.clone());
|
||||
} else {
|
||||
// warn!("Server sent an entity data packet for an
|
||||
// entity id ({}) that we don't
|
||||
// know about", p.id);
|
||||
}
|
||||
}
|
||||
ClientboundGamePacket::UpdateAttributes(_p) => {
|
||||
// debug!("Got update attributes packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::SetEntityMotion(_p) => {
|
||||
// debug!("Got entity velocity packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::SetEntityLink(p) => {
|
||||
debug!("Got set entity link packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::AddPlayer(p) => {
|
||||
debug!("Got add player packet {:?}", p);
|
||||
let bundle = p.as_player_bundle();
|
||||
let mut world = client.world.write();
|
||||
world.add_entity(EntityId(p.id), bundle);
|
||||
// the default metadata was already included in the bundle
|
||||
// for us
|
||||
}
|
||||
ClientboundGamePacket::InitializeBorder(p) => {
|
||||
debug!("Got initialize border packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::SetTime(p) => {
|
||||
debug!("Got set time packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::SetDefaultSpawnPosition(p) => {
|
||||
debug!("Got set default spawn position packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::ContainerSetContent(p) => {
|
||||
debug!("Got container set content packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::SetHealth(p) => {
|
||||
debug!("Got set health packet {:?}", p);
|
||||
if p.health == 0.0 {
|
||||
// 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?;
|
||||
}
|
||||
}
|
||||
}
|
||||
ClientboundGamePacket::SetExperience(p) => {
|
||||
debug!("Got set experience packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::TeleportEntity(p) => {
|
||||
let mut world = client.world.write();
|
||||
let (pos, physics) = self.query::<(&entity::Position, &entity::Physics)>();
|
||||
let _ = world.set_entity_pos(
|
||||
EntityId(p.id),
|
||||
Vec3 {
|
||||
x: p.x,
|
||||
y: p.y,
|
||||
z: p.z,
|
||||
},
|
||||
pos,
|
||||
physics,
|
||||
);
|
||||
}
|
||||
ClientboundGamePacket::UpdateAdvancements(p) => {
|
||||
debug!("Got update advancements packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::RotateHead(_p) => {
|
||||
// debug!("Got rotate head packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::MoveEntityPos(p) => {
|
||||
let mut world = client.world.write();
|
||||
let _ = world.move_entity_with_delta(EntityId(p.entity_id), &p.delta);
|
||||
}
|
||||
ClientboundGamePacket::MoveEntityPosRot(p) => {
|
||||
let mut world = client.world.write();
|
||||
let _ = world.move_entity_with_delta(EntityId(p.entity_id), &p.delta);
|
||||
}
|
||||
ClientboundGamePacket::MoveEntityRot(_p) => {
|
||||
// debug!("Got move entity rot packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::KeepAlive(p) => {
|
||||
debug!("Got keep alive packet {:?}", p);
|
||||
client
|
||||
.write_packet(ServerboundKeepAlivePacket { id: p.id }.get())
|
||||
.await?;
|
||||
}
|
||||
ClientboundGamePacket::RemoveEntities(p) => {
|
||||
debug!("Got remove entities packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::PlayerChat(p) => {
|
||||
debug!("Got player chat packet {:?}", p);
|
||||
tx.send(Event::Chat(ChatPacket::Player(Arc::new(p.clone()))))
|
||||
.await?;
|
||||
}
|
||||
ClientboundGamePacket::SystemChat(p) => {
|
||||
debug!("Got system chat packet {:?}", p);
|
||||
tx.send(Event::Chat(ChatPacket::System(Arc::new(p.clone()))))
|
||||
.await?;
|
||||
}
|
||||
ClientboundGamePacket::Sound(_p) => {
|
||||
// debug!("Got sound packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::LevelEvent(p) => {
|
||||
debug!("Got level event packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::BlockUpdate(p) => {
|
||||
debug!("Got block update packet {:?}", p);
|
||||
let mut world = client.world.write();
|
||||
world.set_block_state(&p.pos, p.block_state);
|
||||
}
|
||||
ClientboundGamePacket::Animate(p) => {
|
||||
debug!("Got animate packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::SectionBlocksUpdate(p) => {
|
||||
debug!("Got section blocks update packet {:?}", p);
|
||||
let mut world = client.world.write();
|
||||
for state in &p.states {
|
||||
world.set_block_state(&(p.section_pos + state.pos.clone()), state.state);
|
||||
}
|
||||
}
|
||||
ClientboundGamePacket::GameEvent(p) => {
|
||||
debug!("Got game event packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::LevelParticles(p) => {
|
||||
debug!("Got level particles packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::ServerData(p) => {
|
||||
debug!("Got server data packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::SetEquipment(p) => {
|
||||
debug!("Got set equipment packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::UpdateMobEffect(p) => {
|
||||
debug!("Got update mob effect packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::AddExperienceOrb(_) => {}
|
||||
ClientboundGamePacket::AwardStats(_) => {}
|
||||
ClientboundGamePacket::BlockChangedAck(_) => {}
|
||||
ClientboundGamePacket::BlockDestruction(_) => {}
|
||||
ClientboundGamePacket::BlockEntityData(_) => {}
|
||||
ClientboundGamePacket::BlockEvent(_) => {}
|
||||
ClientboundGamePacket::BossEvent(_) => {}
|
||||
ClientboundGamePacket::CommandSuggestions(_) => {}
|
||||
ClientboundGamePacket::ContainerSetData(_) => {}
|
||||
ClientboundGamePacket::ContainerSetSlot(_) => {}
|
||||
ClientboundGamePacket::Cooldown(_) => {}
|
||||
ClientboundGamePacket::CustomChatCompletions(_) => {}
|
||||
ClientboundGamePacket::DeleteChat(_) => {}
|
||||
ClientboundGamePacket::Explode(_) => {}
|
||||
ClientboundGamePacket::ForgetLevelChunk(_) => {}
|
||||
ClientboundGamePacket::HorseScreenOpen(_) => {}
|
||||
ClientboundGamePacket::MapItemData(_) => {}
|
||||
ClientboundGamePacket::MerchantOffers(_) => {}
|
||||
ClientboundGamePacket::MoveVehicle(_) => {}
|
||||
ClientboundGamePacket::OpenBook(_) => {}
|
||||
ClientboundGamePacket::OpenScreen(_) => {}
|
||||
ClientboundGamePacket::OpenSignEditor(_) => {}
|
||||
ClientboundGamePacket::Ping(_) => {}
|
||||
ClientboundGamePacket::PlaceGhostRecipe(_) => {}
|
||||
ClientboundGamePacket::PlayerCombatEnd(_) => {}
|
||||
ClientboundGamePacket::PlayerCombatEnter(_) => {}
|
||||
ClientboundGamePacket::PlayerCombatKill(p) => {
|
||||
debug!("Got player kill packet {:?}", p);
|
||||
if client.entity() == EntityId(p.player_id) {
|
||||
// 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(Arc::new(p.clone())))).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
ClientboundGamePacket::PlayerLookAt(_) => {}
|
||||
ClientboundGamePacket::RemoveMobEffect(_) => {}
|
||||
ClientboundGamePacket::ResourcePack(_) => {}
|
||||
ClientboundGamePacket::Respawn(p) => {
|
||||
debug!("Got respawn packet {:?}", p);
|
||||
// Sets clients dead state to false.
|
||||
let mut dead_lock = client.dead.lock();
|
||||
*dead_lock = false;
|
||||
}
|
||||
ClientboundGamePacket::SelectAdvancementsTab(_) => {}
|
||||
ClientboundGamePacket::SetActionBarText(_) => {}
|
||||
ClientboundGamePacket::SetBorderCenter(_) => {}
|
||||
ClientboundGamePacket::SetBorderLerpSize(_) => {}
|
||||
ClientboundGamePacket::SetBorderSize(_) => {}
|
||||
ClientboundGamePacket::SetBorderWarningDelay(_) => {}
|
||||
ClientboundGamePacket::SetBorderWarningDistance(_) => {}
|
||||
ClientboundGamePacket::SetCamera(_) => {}
|
||||
ClientboundGamePacket::SetDisplayObjective(_) => {}
|
||||
ClientboundGamePacket::SetObjective(_) => {}
|
||||
ClientboundGamePacket::SetPassengers(_) => {}
|
||||
ClientboundGamePacket::SetPlayerTeam(_) => {}
|
||||
ClientboundGamePacket::SetScore(_) => {}
|
||||
ClientboundGamePacket::SetSimulationDistance(_) => {}
|
||||
ClientboundGamePacket::SetSubtitleText(_) => {}
|
||||
ClientboundGamePacket::SetTitleText(_) => {}
|
||||
ClientboundGamePacket::SetTitlesAnimation(_) => {}
|
||||
ClientboundGamePacket::SoundEntity(_) => {}
|
||||
ClientboundGamePacket::StopSound(_) => {}
|
||||
ClientboundGamePacket::TabList(_) => {}
|
||||
ClientboundGamePacket::TagQuery(_) => {}
|
||||
ClientboundGamePacket::TakeItemEntity(_) => {}
|
||||
ClientboundGamePacket::DisguisedChat(_) => {}
|
||||
ClientboundGamePacket::UpdateEnabledFeatures(_) => {}
|
||||
ClientboundGamePacket::ContainerClose(_) => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start the game tick loop for every client in the shared world. This
|
||||
/// should only be run once per shared world!
|
||||
async fn game_tick_loop(ecs: Arc<Mutex<bevy_ecs::world::World>>) {
|
||||
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);
|
||||
|
||||
let mut schedule = Schedule::default();
|
||||
#[derive(StageLabel)]
|
||||
pub struct Tick;
|
||||
schedule.add_stage(
|
||||
Tick,
|
||||
SystemStage::single_threaded()
|
||||
.with_system(LocalPlayer::update_in_loaded_chunk)
|
||||
.with_system(LocalPlayer::send_position)
|
||||
.with_system(LocalPlayer::ai_step),
|
||||
);
|
||||
|
||||
loop {
|
||||
game_tick_interval.tick().await;
|
||||
|
||||
schedule.run(&mut ecs.lock());
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to our (potentially shared) world.
|
||||
///
|
||||
/// This gets the [`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.
|
||||
pub fn world(&self) -> Arc<World> {
|
||||
self.world.read().shared.clone()
|
||||
}
|
||||
|
||||
pub fn entity(&self) -> EntityId {
|
||||
*self.entity_id.read()
|
||||
pub fn world(&self) -> Arc<RwLock<World>> {
|
||||
let world_name = self
|
||||
.local_player()
|
||||
.world_name
|
||||
.expect("World name must be known if we're doing Client::world");
|
||||
let world_container = self.world_container.read();
|
||||
world_container.get(&world_name).unwrap()
|
||||
}
|
||||
|
||||
/// Query data of our player's entity.
|
||||
pub fn query<'w, 's, Q: WorldQuery>(&'w self) -> <Q as WorldQuery>::Item<'_> {
|
||||
let mut ecs = &mut self.world_container.write().ecs.lock();
|
||||
let mut ecs = &mut self.ecs.lock();
|
||||
QueryState::<Q>::new(ecs)
|
||||
.get_mut(ecs, self.entity().into())
|
||||
.get_mut(ecs, self.entity)
|
||||
.expect("Player entity should always exist when being queried")
|
||||
}
|
||||
|
||||
/// Returns whether we have a received the login packet yet.
|
||||
pub fn logged_in(&self) -> bool {
|
||||
// the login packet tells us the world name
|
||||
self.world_name.read().is_some()
|
||||
self.local_player().world_name.is_some()
|
||||
}
|
||||
|
||||
/// Tell the server we changed our game options (i.e. render distance, main
|
||||
|
@ -1078,3 +466,55 @@ impl Client {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the protocol and game tick loop.
|
||||
#[doc(hidden)]
|
||||
pub async fn start_ecs(
|
||||
run_schedule_receiver: mpsc::UnboundedReceiver<()>,
|
||||
) -> Arc<Mutex<bevy_ecs::world::World>> {
|
||||
// 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 app = App::new();
|
||||
|
||||
app.add_fixed_timestep(Duration::from_millis(50), "tick");
|
||||
|
||||
app.add_fixed_timestep_system("tick", 0, send_position);
|
||||
// .add_system(LocalPlayer::update_in_loaded_chunk)
|
||||
// .add_system(send_position)
|
||||
// .add_system(LocalPlayer::ai_step);
|
||||
|
||||
app.add_system(packet_handling::handle_packets.label("handle_packets"));
|
||||
|
||||
// should happen last
|
||||
app.add_system(packet_handling::clear_packets.after("handle_packets"));
|
||||
|
||||
// all resources should have been added by now so we can take the ecs from the
|
||||
// app
|
||||
let ecs = Arc::new(Mutex::new(app.world));
|
||||
|
||||
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);
|
||||
|
||||
tokio::spawn(run_schedule_loop(
|
||||
ecs.clone(),
|
||||
app.schedule,
|
||||
run_schedule_receiver,
|
||||
));
|
||||
|
||||
ecs
|
||||
}
|
||||
|
||||
async fn run_schedule_loop(
|
||||
ecs: Arc<Mutex<bevy_ecs::world::World>>,
|
||||
mut schedule: Schedule,
|
||||
mut run_schedule_receiver: mpsc::UnboundedReceiver<()>,
|
||||
) {
|
||||
loop {
|
||||
// whenever we get an event from run_schedule_receiver, run the schedule
|
||||
run_schedule_receiver.recv().await;
|
||||
schedule.run(&mut ecs.lock());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ mod client;
|
|||
mod get_mc_dir;
|
||||
mod local_player;
|
||||
mod movement;
|
||||
pub mod packet_handling;
|
||||
pub mod ping;
|
||||
mod player;
|
||||
mod plugins;
|
||||
|
|
|
@ -10,8 +10,8 @@ use azalea_protocol::{
|
|||
},
|
||||
};
|
||||
use azalea_world::{
|
||||
entity::{self, metadata::PlayerMetadataBundle, EntityId},
|
||||
PartialWorld, WeakWorldContainer,
|
||||
entity::{self, metadata::PlayerMetadataBundle, Entity, EntityId},
|
||||
EntityInfos, PartialWorld, World, WorldContainer,
|
||||
};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
|
@ -32,6 +32,7 @@ pub struct LocalPlayer {
|
|||
pub profile: GameProfile,
|
||||
|
||||
pub write_conn: WriteConnection<ServerboundGamePacket>,
|
||||
|
||||
// pub world: Arc<RwLock<PartialWorld>>,
|
||||
pub physics_state: PhysicsState,
|
||||
pub client_information: ClientInformation,
|
||||
|
@ -39,7 +40,8 @@ pub struct LocalPlayer {
|
|||
/// A map of player uuids to their information in the tab list
|
||||
pub players: HashMap<Uuid, PlayerInfo>,
|
||||
|
||||
pub world: Arc<RwLock<PartialWorld>>,
|
||||
pub partial_world: Arc<RwLock<PartialWorld>>,
|
||||
pub world: Arc<RwLock<World>>,
|
||||
pub world_name: Option<ResourceLocation>,
|
||||
|
||||
pub tx: mpsc::Sender<Event>,
|
||||
|
@ -66,28 +68,39 @@ pub struct PhysicsState {
|
|||
pub struct LocalPlayerInLoadedChunk;
|
||||
|
||||
impl LocalPlayer {
|
||||
/// Create a new client from the given GameProfile, Connection, and World.
|
||||
/// Create a new `LocalPlayer`.
|
||||
///
|
||||
/// You should only use this if you want to change these fields from the
|
||||
/// defaults, otherwise use [`Client::join`].
|
||||
pub fn new(
|
||||
entity: Entity,
|
||||
profile: GameProfile,
|
||||
write_conn: WriteConnection<ServerboundGamePacket>,
|
||||
world: Arc<RwLock<PartialWorld>>,
|
||||
world: Arc<RwLock<World>>,
|
||||
entity_infos: &mut EntityInfos,
|
||||
tx: mpsc::Sender<Event>,
|
||||
) -> Self {
|
||||
let client_information = ClientInformation::default();
|
||||
|
||||
LocalPlayer {
|
||||
profile,
|
||||
|
||||
write_conn,
|
||||
|
||||
physics_state: PhysicsState::default(),
|
||||
client_information: ClientInformation::default(),
|
||||
dead: false,
|
||||
players: HashMap::new(),
|
||||
|
||||
world,
|
||||
world_name: Arc::new(RwLock::new(None)),
|
||||
partial_world: Arc::new(RwLock::new(PartialWorld::new(
|
||||
client_information.view_distance.into(),
|
||||
Some(entity),
|
||||
entity_infos,
|
||||
))),
|
||||
world_name: None,
|
||||
|
||||
tx,
|
||||
world_name: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::client::{Client, LocalPlayerInLoadedChunk};
|
||||
use crate::{LocalPlayer, PhysicsState};
|
||||
use crate::client::Client;
|
||||
use crate::local_player::{LocalPlayer, LocalPlayerInLoadedChunk, PhysicsState};
|
||||
use azalea_core::Vec3;
|
||||
use azalea_protocol::packets::game::serverbound_player_command_packet::ServerboundPlayerCommandPacket;
|
||||
use azalea_protocol::packets::game::{
|
||||
|
@ -8,7 +8,7 @@ use azalea_protocol::packets::game::{
|
|||
serverbound_move_player_rot_packet::ServerboundMovePlayerRotPacket,
|
||||
serverbound_move_player_status_only_packet::ServerboundMovePlayerStatusOnlyPacket,
|
||||
};
|
||||
use azalea_world::entity::EcsEntityId;
|
||||
use azalea_world::entity::{Entity, MinecraftEntityId};
|
||||
use azalea_world::{entity, MoveEntityError};
|
||||
use bevy_ecs::system::Query;
|
||||
use std::backtrace::Backtrace;
|
||||
|
@ -60,117 +60,118 @@ impl Client {
|
|||
}
|
||||
}
|
||||
|
||||
impl LocalPlayer {
|
||||
/// This gets called automatically every tick.
|
||||
pub(crate) fn send_position(
|
||||
mut query: Query<
|
||||
(
|
||||
EcsEntityId,
|
||||
&LocalPlayer,
|
||||
&entity::Position,
|
||||
&mut entity::Physics,
|
||||
&entity::metadata::Sprinting,
|
||||
),
|
||||
&LocalPlayerInLoadedChunk,
|
||||
>,
|
||||
) {
|
||||
for (entity, local_player, position, physics, sprinting) in &query {
|
||||
local_player.send_sprinting_if_needed(
|
||||
entity.into(),
|
||||
sprinting,
|
||||
&mut local_player.physics_state,
|
||||
);
|
||||
pub(crate) fn send_position(
|
||||
mut query: Query<
|
||||
(
|
||||
Entity,
|
||||
&MinecraftEntityId,
|
||||
&LocalPlayer,
|
||||
&entity::Position,
|
||||
&entity::LastSentPosition,
|
||||
&mut entity::Physics,
|
||||
&entity::metadata::Sprinting,
|
||||
),
|
||||
&LocalPlayerInLoadedChunk,
|
||||
>,
|
||||
) {
|
||||
for (entity, id, local_player, position, last_sent_position, physics, sprinting) in &query {
|
||||
local_player.send_sprinting_if_needed(
|
||||
entity.into(),
|
||||
id,
|
||||
sprinting,
|
||||
&mut local_player.physics_state,
|
||||
);
|
||||
|
||||
let packet = {
|
||||
// TODO: the camera being able to be controlled by other entities isn't
|
||||
// implemented yet if !self.is_controlled_camera() { return };
|
||||
let packet = {
|
||||
// TODO: the camera being able to be controlled by other entities isn't
|
||||
// implemented yet if !self.is_controlled_camera() { return };
|
||||
|
||||
let old_position = physics.last_pos;
|
||||
let x_delta = position.x - last_sent_position.x;
|
||||
let y_delta = position.y - last_sent_position.y;
|
||||
let z_delta = position.z - last_sent_position.z;
|
||||
let y_rot_delta = (physics.y_rot - physics.y_rot_last) as f64;
|
||||
let x_rot_delta = (physics.x_rot - physics.x_rot_last) as f64;
|
||||
|
||||
let x_delta = position.x - old_position.x;
|
||||
let y_delta = position.y - old_position.y;
|
||||
let z_delta = position.z - old_position.z;
|
||||
let y_rot_delta = (physics.y_rot - physics.y_rot_last) as f64;
|
||||
let x_rot_delta = (physics.x_rot - physics.x_rot_last) as f64;
|
||||
local_player.physics_state.position_remainder += 1;
|
||||
|
||||
local_player.physics_state.position_remainder += 1;
|
||||
// boolean sendingPosition = Mth.lengthSquared(xDelta, yDelta, zDelta) >
|
||||
// Mth.square(2.0E-4D) || this.positionReminder >= 20;
|
||||
let sending_position = ((x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2))
|
||||
> 2.0e-4f64.powi(2))
|
||||
|| local_player.physics_state.position_remainder >= 20;
|
||||
let sending_rotation = y_rot_delta != 0.0 || x_rot_delta != 0.0;
|
||||
|
||||
// boolean sendingPosition = Mth.lengthSquared(xDelta, yDelta, zDelta) >
|
||||
// Mth.square(2.0E-4D) || this.positionReminder >= 20;
|
||||
let sending_position = ((x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2))
|
||||
> 2.0e-4f64.powi(2))
|
||||
|| local_player.physics_state.position_remainder >= 20;
|
||||
let sending_rotation = y_rot_delta != 0.0 || x_rot_delta != 0.0;
|
||||
|
||||
// if self.is_passenger() {
|
||||
// TODO: posrot packet for being a passenger
|
||||
// }
|
||||
let packet = if sending_position && sending_rotation {
|
||||
Some(
|
||||
ServerboundMovePlayerPosRotPacket {
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
z: position.z,
|
||||
x_rot: physics.x_rot,
|
||||
y_rot: physics.y_rot,
|
||||
on_ground: physics.on_ground,
|
||||
}
|
||||
.get(),
|
||||
)
|
||||
} else if sending_position {
|
||||
Some(
|
||||
ServerboundMovePlayerPosPacket {
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
z: position.z,
|
||||
on_ground: physics.on_ground,
|
||||
}
|
||||
.get(),
|
||||
)
|
||||
} else if sending_rotation {
|
||||
Some(
|
||||
ServerboundMovePlayerRotPacket {
|
||||
x_rot: physics.x_rot,
|
||||
y_rot: physics.y_rot,
|
||||
on_ground: physics.on_ground,
|
||||
}
|
||||
.get(),
|
||||
)
|
||||
} else if physics.last_on_ground != physics.on_ground {
|
||||
Some(
|
||||
ServerboundMovePlayerStatusOnlyPacket {
|
||||
on_ground: physics.on_ground,
|
||||
}
|
||||
.get(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if sending_position {
|
||||
physics.last_pos = **position;
|
||||
local_player.physics_state.position_remainder = 0;
|
||||
}
|
||||
if sending_rotation {
|
||||
physics.y_rot_last = physics.y_rot;
|
||||
physics.x_rot_last = physics.x_rot;
|
||||
}
|
||||
|
||||
physics.last_on_ground = physics.on_ground;
|
||||
// minecraft checks for autojump here, but also autojump is bad so
|
||||
|
||||
packet
|
||||
// if self.is_passenger() {
|
||||
// TODO: posrot packet for being a passenger
|
||||
// }
|
||||
let packet = if sending_position && sending_rotation {
|
||||
Some(
|
||||
ServerboundMovePlayerPosRotPacket {
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
z: position.z,
|
||||
x_rot: physics.x_rot,
|
||||
y_rot: physics.y_rot,
|
||||
on_ground: physics.on_ground,
|
||||
}
|
||||
.get(),
|
||||
)
|
||||
} else if sending_position {
|
||||
Some(
|
||||
ServerboundMovePlayerPosPacket {
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
z: position.z,
|
||||
on_ground: physics.on_ground,
|
||||
}
|
||||
.get(),
|
||||
)
|
||||
} else if sending_rotation {
|
||||
Some(
|
||||
ServerboundMovePlayerRotPacket {
|
||||
x_rot: physics.x_rot,
|
||||
y_rot: physics.y_rot,
|
||||
on_ground: physics.on_ground,
|
||||
}
|
||||
.get(),
|
||||
)
|
||||
} else if physics.last_on_ground != physics.on_ground {
|
||||
Some(
|
||||
ServerboundMovePlayerStatusOnlyPacket {
|
||||
on_ground: physics.on_ground,
|
||||
}
|
||||
.get(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(packet) = packet {
|
||||
tokio::spawn(local_player.write_packet(packet));
|
||||
if sending_position {
|
||||
**last_sent_position = **position;
|
||||
local_player.physics_state.position_remainder = 0;
|
||||
}
|
||||
if sending_rotation {
|
||||
physics.y_rot_last = physics.y_rot;
|
||||
physics.x_rot_last = physics.x_rot;
|
||||
}
|
||||
|
||||
physics.last_on_ground = physics.on_ground;
|
||||
// minecraft checks for autojump here, but also autojump is bad so
|
||||
|
||||
packet
|
||||
};
|
||||
|
||||
if let Some(packet) = packet {
|
||||
local_player.write_packet(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalPlayer {
|
||||
fn send_sprinting_if_needed(
|
||||
&mut self,
|
||||
entity: entity::EntityId,
|
||||
entity: Entity,
|
||||
id: &MinecraftEntityId,
|
||||
sprinting: &entity::metadata::Sprinting,
|
||||
physics_state: &mut PhysicsState,
|
||||
) {
|
||||
|
@ -181,15 +182,13 @@ impl LocalPlayer {
|
|||
} else {
|
||||
azalea_protocol::packets::game::serverbound_player_command_packet::Action::StopSprinting
|
||||
};
|
||||
tokio::spawn(
|
||||
self.write_packet(
|
||||
ServerboundPlayerCommandPacket {
|
||||
id: *entity,
|
||||
action: sprinting_action,
|
||||
data: 0,
|
||||
}
|
||||
.get(),
|
||||
),
|
||||
self.write_packet(
|
||||
ServerboundPlayerCommandPacket {
|
||||
id: **id,
|
||||
action: sprinting_action,
|
||||
data: 0,
|
||||
}
|
||||
.get(),
|
||||
);
|
||||
physics_state.was_sprinting = **sprinting;
|
||||
}
|
||||
|
@ -200,7 +199,7 @@ impl LocalPlayer {
|
|||
pub fn ai_step(
|
||||
query: Query<
|
||||
(
|
||||
EcsEntityId,
|
||||
Entity,
|
||||
&mut LocalPlayer,
|
||||
&mut entity::Physics,
|
||||
&mut entity::Position,
|
||||
|
@ -251,8 +250,7 @@ impl LocalPlayer {
|
|||
}
|
||||
|
||||
azalea_physics::ai_step(
|
||||
ecs_entity_id.into(),
|
||||
&local_player.world.read().shared,
|
||||
&local_player.world.read(),
|
||||
&mut physics,
|
||||
&mut position,
|
||||
&sprinting,
|
||||
|
|
616
azalea-client/src/packet_handling.rs
Normal file
616
azalea-client/src/packet_handling.rs
Normal file
|
@ -0,0 +1,616 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use azalea_protocol::{connect::ReadConnection, packets::game::ClientboundGamePacket};
|
||||
use bevy_ecs::{component::Component, prelude::Entity, query::Changed, system::Query};
|
||||
use parking_lot::Mutex;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
/// Something that receives packets from the server.
|
||||
#[derive(Component, Clone)]
|
||||
pub struct PacketReceiver {
|
||||
pub packets: Arc<Mutex<Vec<ClientboundGamePacket>>>,
|
||||
pub run_schedule_sender: mpsc::UnboundedSender<()>,
|
||||
}
|
||||
|
||||
pub fn handle_packets(
|
||||
query: Query<(Entity, &PacketReceiver), Changed<PacketReceiver>>,
|
||||
// ecs: &mut bevy_ecs::world::World,
|
||||
) {
|
||||
for (entity, packet_events) in &query {
|
||||
for packet in packet_events.packets.lock().iter() {
|
||||
handle_packet(entity, packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_packet(entity: Entity, packet: &ClientboundGamePacket) {
|
||||
// match packet {
|
||||
// ClientboundGamePacket::Login(p) => {
|
||||
// debug!("Got login packet");
|
||||
|
||||
// {
|
||||
// // // write p into login.txt
|
||||
// // std::io::Write::write_all(
|
||||
// // &mut std::fs::File::create("login.txt").unwrap(),
|
||||
// // format!("{:#?}", p).as_bytes(),
|
||||
// // )
|
||||
// // .unwrap();
|
||||
|
||||
// // TODO: have registry_holder be a struct because this sucks
|
||||
// rn // best way would be to add serde support to
|
||||
// azalea-nbt
|
||||
|
||||
// let registry_holder = p
|
||||
// .registry_holder
|
||||
// .as_compound()
|
||||
// .expect("Registry holder is not a compound")
|
||||
// .get("")
|
||||
// .expect("No \"\" tag")
|
||||
// .as_compound()
|
||||
// .expect("\"\" tag is not a compound");
|
||||
// let dimension_types = registry_holder
|
||||
// .get("minecraft:dimension_type")
|
||||
// .expect("No dimension_type tag")
|
||||
// .as_compound()
|
||||
// .expect("dimension_type is not a compound")
|
||||
// .get("value")
|
||||
// .expect("No dimension_type value")
|
||||
// .as_list()
|
||||
// .expect("dimension_type value is not a list");
|
||||
// let dimension_type = dimension_types
|
||||
// .iter()
|
||||
// .find(|t| {
|
||||
// t.as_compound()
|
||||
// .expect("dimension_type value is not a compound")
|
||||
// .get("name")
|
||||
// .expect("No name tag")
|
||||
// .as_string()
|
||||
// .expect("name is not a string")
|
||||
// == p.dimension_type.to_string()
|
||||
// })
|
||||
// .unwrap_or_else(|| panic!("No dimension_type with name
|
||||
// {}", p.dimension_type)) .as_compound()
|
||||
// .unwrap()
|
||||
// .get("element")
|
||||
// .expect("No element tag")
|
||||
// .as_compound()
|
||||
// .expect("element is not a compound");
|
||||
// let height = (*dimension_type
|
||||
// .get("height")
|
||||
// .expect("No height tag")
|
||||
// .as_int()
|
||||
// .expect("height tag is not an int"))
|
||||
// .try_into()
|
||||
// .expect("height is not a u32");
|
||||
// let min_y = *dimension_type
|
||||
// .get("min_y")
|
||||
// .expect("No min_y tag")
|
||||
// .as_int()
|
||||
// .expect("min_y tag is not an int");
|
||||
|
||||
// let world_name = p.dimension.clone();
|
||||
|
||||
// local_player.world_name = Some(world_name.clone());
|
||||
// // add this world to the world_container (or don't if it's
|
||||
// already there) let weak_world =
|
||||
// world_container.insert(world_name, 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 = local_player.world.write(); *world_lock =
|
||||
// PartialWorld::new(
|
||||
// local_player.client_information.view_distance.into(),
|
||||
// weak_world, Some(EntityId(p.player_id)),
|
||||
// );
|
||||
|
||||
// let player_bundle = entity::PlayerBundle {
|
||||
// entity: entity::EntityBundle::new(
|
||||
// local_player.profile.uuid,
|
||||
// Vec3::default(),
|
||||
// azalea_registry::EntityKind::Player,
|
||||
// ),
|
||||
// metadata: PlayerMetadataBundle::default(),
|
||||
// };
|
||||
// // let entity = EntityData::new(
|
||||
// // client.profile.uuid,
|
||||
// // Vec3::default(),
|
||||
// // EntityMetadata::Player(metadata::Player::default()),
|
||||
// // );
|
||||
// // the first argument makes it so other entities don't update
|
||||
// this entity in // a shared world
|
||||
// world_lock.add_entity(EntityId(p.player_id), player_bundle);
|
||||
|
||||
// *client.entity_id.write() = EntityId(p.player_id);
|
||||
// }
|
||||
|
||||
// // send the client information that we have set
|
||||
// let client_information_packet: ClientInformation =
|
||||
// client.local_player().client_information.clone();
|
||||
// log::debug!(
|
||||
// "Sending client information because login: {:?}",
|
||||
// client_information_packet
|
||||
// );
|
||||
// client.write_packet(client_information_packet.get()).await?;
|
||||
|
||||
// // brand
|
||||
// client
|
||||
// .write_packet(
|
||||
// ServerboundCustomPayloadPacket {
|
||||
// identifier: ResourceLocation::new("brand").unwrap(),
|
||||
// // they don't have to know :)
|
||||
// data: "vanilla".into(),
|
||||
// }
|
||||
// .get(),
|
||||
// )
|
||||
// .await?;
|
||||
|
||||
// tx.send(Event::Login).await?;
|
||||
// }
|
||||
// ClientboundGamePacket::SetChunkCacheRadius(p) => {
|
||||
// debug!("Got set chunk cache radius packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::CustomPayload(p) => {
|
||||
// debug!("Got custom payload packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::ChangeDifficulty(p) => {
|
||||
// debug!("Got difficulty packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::Commands(_p) => {
|
||||
// debug!("Got declare commands packet");
|
||||
// }
|
||||
// ClientboundGamePacket::PlayerAbilities(p) => {
|
||||
// debug!("Got player abilities packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::SetCarriedItem(p) => {
|
||||
// debug!("Got set carried item packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::UpdateTags(_p) => {
|
||||
// debug!("Got update tags packet");
|
||||
// }
|
||||
// ClientboundGamePacket::Disconnect(p) => {
|
||||
// debug!("Got disconnect packet {:?}", p);
|
||||
// client.disconnect().await?;
|
||||
// }
|
||||
// ClientboundGamePacket::UpdateRecipes(_p) => {
|
||||
// debug!("Got update recipes packet");
|
||||
// }
|
||||
// ClientboundGamePacket::EntityEvent(_p) => {
|
||||
// // debug!("Got entity event packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::Recipe(_p) => {
|
||||
// debug!("Got recipe packet");
|
||||
// }
|
||||
// ClientboundGamePacket::PlayerPosition(p) => {
|
||||
// // TODO: reply with teleport confirm
|
||||
// debug!("Got player position packet {:?}", p);
|
||||
|
||||
// let (new_pos, y_rot, x_rot) = {
|
||||
// let player_entity_id = *client.entity();
|
||||
// let world = client.world();
|
||||
// // let mut player_entity =
|
||||
// world.entity_mut(player_entity_id).unwrap(); let (mut
|
||||
// physics, position) = client.query::<(&mut
|
||||
// entity::Physics, &mut entity::Position)>();
|
||||
|
||||
// let delta_movement = physics.delta;
|
||||
|
||||
// let is_x_relative = p.relative_arguments.x;
|
||||
// let is_y_relative = p.relative_arguments.y;
|
||||
// let is_z_relative = p.relative_arguments.z;
|
||||
|
||||
// let (delta_x, new_pos_x) = if is_x_relative {
|
||||
// physics.last_pos.x += p.x;
|
||||
// (delta_movement.x, position.x + p.x)
|
||||
// } else {
|
||||
// physics.last_pos.x = p.x;
|
||||
// (0.0, p.x)
|
||||
// };
|
||||
// let (delta_y, new_pos_y) = if is_y_relative {
|
||||
// physics.last_pos.y += p.y;
|
||||
// (delta_movement.y, position.y + p.y)
|
||||
// } else {
|
||||
// physics.last_pos.y = p.y;
|
||||
// (0.0, p.y)
|
||||
// };
|
||||
// let (delta_z, new_pos_z) = if is_z_relative {
|
||||
// physics.last_pos.z += p.z;
|
||||
// (delta_movement.z, position.z + p.z)
|
||||
// } else {
|
||||
// physics.last_pos.z = p.z;
|
||||
// (0.0, p.z)
|
||||
// };
|
||||
|
||||
// let mut y_rot = p.y_rot;
|
||||
// let mut x_rot = p.x_rot;
|
||||
// if p.relative_arguments.x_rot {
|
||||
// x_rot += physics.x_rot;
|
||||
// }
|
||||
// if p.relative_arguments.y_rot {
|
||||
// y_rot += physics.y_rot;
|
||||
// }
|
||||
|
||||
// physics.delta = Vec3 {
|
||||
// x: delta_x,
|
||||
// y: delta_y,
|
||||
// z: delta_z,
|
||||
// };
|
||||
// entity::set_rotation(physics.into_inner(), y_rot, x_rot);
|
||||
// // TODO: minecraft sets "xo", "yo", and "zo" here but idk
|
||||
// what that means // so investigate that ig
|
||||
// let new_pos = Vec3 {
|
||||
// x: new_pos_x,
|
||||
// y: new_pos_y,
|
||||
// z: new_pos_z,
|
||||
// };
|
||||
// world
|
||||
// .set_entity_pos(
|
||||
// player_entity_id,
|
||||
// new_pos,
|
||||
// position.into_inner(),
|
||||
// physics.into_inner(),
|
||||
// )
|
||||
// .expect("The player entity should always exist");
|
||||
|
||||
// (new_pos, y_rot, x_rot)
|
||||
// };
|
||||
|
||||
// client
|
||||
// .write_packet(ServerboundAcceptTeleportationPacket { id: p.id
|
||||
// }.get()) .await?;
|
||||
// client
|
||||
// .write_packet(
|
||||
// ServerboundMovePlayerPosRotPacket {
|
||||
// x: new_pos.x,
|
||||
// y: new_pos.y,
|
||||
// z: new_pos.z,
|
||||
// y_rot,
|
||||
// x_rot,
|
||||
// // this is always false
|
||||
// on_ground: false,
|
||||
// }
|
||||
// .get(),
|
||||
// )
|
||||
// .await?;
|
||||
// }
|
||||
// ClientboundGamePacket::PlayerInfoUpdate(p) => {
|
||||
// debug!("Got player info packet {:?}", p);
|
||||
// let mut events = Vec::new();
|
||||
// {
|
||||
// let mut players_lock = client.players.write();
|
||||
// for updated_info in &p.entries {
|
||||
// // add the new player maybe
|
||||
// if p.actions.add_player {
|
||||
// let player_info = PlayerInfo {
|
||||
// profile: updated_info.profile.clone(),
|
||||
// uuid: updated_info.profile.uuid,
|
||||
// gamemode: updated_info.game_mode,
|
||||
// latency: updated_info.latency,
|
||||
// display_name: updated_info.display_name.clone(),
|
||||
// };
|
||||
// players_lock.insert(updated_info.profile.uuid,
|
||||
// player_info.clone());
|
||||
// events.push(Event::AddPlayer(player_info)); } else if
|
||||
// let Some(info) = players_lock.get_mut(&updated_info.profile.uuid) {
|
||||
// // `else if` because the block for add_player above
|
||||
// // already sets all the fields
|
||||
// if p.actions.update_game_mode {
|
||||
// info.gamemode = updated_info.game_mode;
|
||||
// }
|
||||
// if p.actions.update_latency {
|
||||
// info.latency = updated_info.latency;
|
||||
// }
|
||||
// if p.actions.update_display_name {
|
||||
// info.display_name =
|
||||
// updated_info.display_name.clone(); }
|
||||
// events.push(Event::UpdatePlayer(info.clone()));
|
||||
// } else {
|
||||
// warn!(
|
||||
// "Ignoring PlayerInfoUpdate for unknown player
|
||||
// {}", updated_info.profile.uuid
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// for event in events {
|
||||
// tx.send(event).await?;
|
||||
// }
|
||||
// }
|
||||
// ClientboundGamePacket::PlayerInfoRemove(p) => {
|
||||
// let mut events = Vec::new();
|
||||
// {
|
||||
// let mut players_lock = client.players.write();
|
||||
// for uuid in &p.profile_ids {
|
||||
// if let Some(info) = players_lock.remove(uuid) {
|
||||
// events.push(Event::RemovePlayer(info));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// for event in events {
|
||||
// tx.send(event).await?;
|
||||
// }
|
||||
// }
|
||||
// ClientboundGamePacket::SetChunkCacheCenter(p) => {
|
||||
// debug!("Got chunk cache center packet {:?}", p);
|
||||
// client
|
||||
// .world
|
||||
// .write()
|
||||
// .update_view_center(&ChunkPos::new(p.x, p.z));
|
||||
// }
|
||||
// ClientboundGamePacket::LevelChunkWithLight(p) => {
|
||||
// // 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().chunks.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
|
||||
// .world
|
||||
// .write()
|
||||
// .replace_with_packet_data(&pos, &mut
|
||||
// Cursor::new(&p.chunk_data.data)) {
|
||||
// error!("Couldn't set chunk data: {}", e);
|
||||
// }
|
||||
// }
|
||||
// ClientboundGamePacket::LightUpdate(_p) => {
|
||||
// // debug!("Got light update packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::AddEntity(p) => {
|
||||
// debug!("Got add entity packet {:?}", p);
|
||||
// let bundle = p.as_entity_bundle();
|
||||
// let mut world = client.world.write();
|
||||
// world.add_entity(EntityId(p.id), bundle);
|
||||
// // the bundle doesn't include the default entity metadata so we
|
||||
// add that // separately
|
||||
// let mut entities = world.entity_infos.shared.write();
|
||||
// let mut entity =
|
||||
// entities.ecs_entity_mut(EntityId(p.id)).unwrap();
|
||||
// p.apply_metadata(&mut entity);
|
||||
// }
|
||||
// ClientboundGamePacket::SetEntityData(p) => {
|
||||
// debug!("Got set entity data packet {:?}", p);
|
||||
// let world = client.world.write();
|
||||
// let mut entities = world.entity_infos.shared.write();
|
||||
// let entity = entities.ecs_entity_mut(EntityId(p.id));
|
||||
// if let Some(mut entity) = entity {
|
||||
// entity::metadata::apply_metadata(&mut entity,
|
||||
// p.packed_items.0.clone()); } else {
|
||||
// // warn!("Server sent an entity data packet for an
|
||||
// // entity id ({}) that we don't
|
||||
// // know about", p.id);
|
||||
// }
|
||||
// }
|
||||
// ClientboundGamePacket::UpdateAttributes(_p) => {
|
||||
// // debug!("Got update attributes packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::SetEntityMotion(_p) => {
|
||||
// // debug!("Got entity velocity packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::SetEntityLink(p) => {
|
||||
// debug!("Got set entity link packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::AddPlayer(p) => {
|
||||
// debug!("Got add player packet {:?}", p);
|
||||
// let bundle = p.as_player_bundle();
|
||||
// let mut world = client.world.write();
|
||||
// world.add_entity(EntityId(p.id), bundle);
|
||||
// // the default metadata was already included in the bundle
|
||||
// // for us
|
||||
// }
|
||||
// ClientboundGamePacket::InitializeBorder(p) => {
|
||||
// debug!("Got initialize border packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::SetTime(p) => {
|
||||
// debug!("Got set time packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::SetDefaultSpawnPosition(p) => {
|
||||
// debug!("Got set default spawn position packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::ContainerSetContent(p) => {
|
||||
// debug!("Got container set content packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::SetHealth(p) => {
|
||||
// debug!("Got set health packet {:?}", p);
|
||||
// if p.health == 0.0 {
|
||||
// // 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?;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ClientboundGamePacket::SetExperience(p) => {
|
||||
// debug!("Got set experience packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::TeleportEntity(p) => {
|
||||
// let mut world = client.world.write();
|
||||
// let (pos, physics) = self.query::<(&entity::Position,
|
||||
// &entity::Physics)>(); let _ = world.set_entity_pos(
|
||||
// EntityId(p.id),
|
||||
// Vec3 {
|
||||
// x: p.x,
|
||||
// y: p.y,
|
||||
// z: p.z,
|
||||
// },
|
||||
// pos,
|
||||
// physics,
|
||||
// );
|
||||
// }
|
||||
// ClientboundGamePacket::UpdateAdvancements(p) => {
|
||||
// debug!("Got update advancements packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::RotateHead(_p) => {
|
||||
// // debug!("Got rotate head packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::MoveEntityPos(p) => {
|
||||
// let mut world = client.world.write();
|
||||
// let _ = world.move_entity_with_delta(EntityId(p.entity_id),
|
||||
// &p.delta); }
|
||||
// ClientboundGamePacket::MoveEntityPosRot(p) => {
|
||||
// let mut world = client.world.write();
|
||||
// let _ = world.move_entity_with_delta(EntityId(p.entity_id),
|
||||
// &p.delta); }
|
||||
// ClientboundGamePacket::MoveEntityRot(_p) => {
|
||||
// // debug!("Got move entity rot packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::KeepAlive(p) => {
|
||||
// debug!("Got keep alive packet {:?}", p);
|
||||
// client
|
||||
// .write_packet(ServerboundKeepAlivePacket { id: p.id }.get())
|
||||
// .await?;
|
||||
// }
|
||||
// ClientboundGamePacket::RemoveEntities(p) => {
|
||||
// debug!("Got remove entities packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::PlayerChat(p) => {
|
||||
// debug!("Got player chat packet {:?}", p);
|
||||
// tx.send(Event::Chat(ChatPacket::Player(Arc::new(p.clone()))))
|
||||
// .await?;
|
||||
// }
|
||||
// ClientboundGamePacket::SystemChat(p) => {
|
||||
// debug!("Got system chat packet {:?}", p);
|
||||
// tx.send(Event::Chat(ChatPacket::System(Arc::new(p.clone()))))
|
||||
// .await?;
|
||||
// }
|
||||
// ClientboundGamePacket::Sound(_p) => {
|
||||
// // debug!("Got sound packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::LevelEvent(p) => {
|
||||
// debug!("Got level event packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::BlockUpdate(p) => {
|
||||
// debug!("Got block update packet {:?}", p);
|
||||
// let mut world = client.world.write();
|
||||
// world.set_block_state(&p.pos, p.block_state);
|
||||
// }
|
||||
// ClientboundGamePacket::Animate(p) => {
|
||||
// debug!("Got animate packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::SectionBlocksUpdate(p) => {
|
||||
// debug!("Got section blocks update packet {:?}", p);
|
||||
// let mut world = client.world.write();
|
||||
// for state in &p.states {
|
||||
// world.set_block_state(&(p.section_pos + state.pos.clone()),
|
||||
// state.state); }
|
||||
// }
|
||||
// ClientboundGamePacket::GameEvent(p) => {
|
||||
// debug!("Got game event packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::LevelParticles(p) => {
|
||||
// debug!("Got level particles packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::ServerData(p) => {
|
||||
// debug!("Got server data packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::SetEquipment(p) => {
|
||||
// debug!("Got set equipment packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::UpdateMobEffect(p) => {
|
||||
// debug!("Got update mob effect packet {:?}", p);
|
||||
// }
|
||||
// ClientboundGamePacket::AddExperienceOrb(_) => {}
|
||||
// ClientboundGamePacket::AwardStats(_) => {}
|
||||
// ClientboundGamePacket::BlockChangedAck(_) => {}
|
||||
// ClientboundGamePacket::BlockDestruction(_) => {}
|
||||
// ClientboundGamePacket::BlockEntityData(_) => {}
|
||||
// ClientboundGamePacket::BlockEvent(_) => {}
|
||||
// ClientboundGamePacket::BossEvent(_) => {}
|
||||
// ClientboundGamePacket::CommandSuggestions(_) => {}
|
||||
// ClientboundGamePacket::ContainerSetData(_) => {}
|
||||
// ClientboundGamePacket::ContainerSetSlot(_) => {}
|
||||
// ClientboundGamePacket::Cooldown(_) => {}
|
||||
// ClientboundGamePacket::CustomChatCompletions(_) => {}
|
||||
// ClientboundGamePacket::DeleteChat(_) => {}
|
||||
// ClientboundGamePacket::Explode(_) => {}
|
||||
// ClientboundGamePacket::ForgetLevelChunk(_) => {}
|
||||
// ClientboundGamePacket::HorseScreenOpen(_) => {}
|
||||
// ClientboundGamePacket::MapItemData(_) => {}
|
||||
// ClientboundGamePacket::MerchantOffers(_) => {}
|
||||
// ClientboundGamePacket::MoveVehicle(_) => {}
|
||||
// ClientboundGamePacket::OpenBook(_) => {}
|
||||
// ClientboundGamePacket::OpenScreen(_) => {}
|
||||
// ClientboundGamePacket::OpenSignEditor(_) => {}
|
||||
// ClientboundGamePacket::Ping(_) => {}
|
||||
// ClientboundGamePacket::PlaceGhostRecipe(_) => {}
|
||||
// ClientboundGamePacket::PlayerCombatEnd(_) => {}
|
||||
// ClientboundGamePacket::PlayerCombatEnter(_) => {}
|
||||
// ClientboundGamePacket::PlayerCombatKill(p) => {
|
||||
// debug!("Got player kill packet {:?}", p);
|
||||
// if client.entity() == EntityId(p.player_id) {
|
||||
// // 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(Arc::new(p.clone())))).await?;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ClientboundGamePacket::PlayerLookAt(_) => {}
|
||||
// ClientboundGamePacket::RemoveMobEffect(_) => {}
|
||||
// ClientboundGamePacket::ResourcePack(_) => {}
|
||||
// ClientboundGamePacket::Respawn(p) => {
|
||||
// debug!("Got respawn packet {:?}", p);
|
||||
// // Sets clients dead state to false.
|
||||
// let mut dead_lock = client.dead.lock();
|
||||
// *dead_lock = false;
|
||||
// }
|
||||
// ClientboundGamePacket::SelectAdvancementsTab(_) => {}
|
||||
// ClientboundGamePacket::SetActionBarText(_) => {}
|
||||
// ClientboundGamePacket::SetBorderCenter(_) => {}
|
||||
// ClientboundGamePacket::SetBorderLerpSize(_) => {}
|
||||
// ClientboundGamePacket::SetBorderSize(_) => {}
|
||||
// ClientboundGamePacket::SetBorderWarningDelay(_) => {}
|
||||
// ClientboundGamePacket::SetBorderWarningDistance(_) => {}
|
||||
// ClientboundGamePacket::SetCamera(_) => {}
|
||||
// ClientboundGamePacket::SetDisplayObjective(_) => {}
|
||||
// ClientboundGamePacket::SetObjective(_) => {}
|
||||
// ClientboundGamePacket::SetPassengers(_) => {}
|
||||
// ClientboundGamePacket::SetPlayerTeam(_) => {}
|
||||
// ClientboundGamePacket::SetScore(_) => {}
|
||||
// ClientboundGamePacket::SetSimulationDistance(_) => {}
|
||||
// ClientboundGamePacket::SetSubtitleText(_) => {}
|
||||
// ClientboundGamePacket::SetTitleText(_) => {}
|
||||
// ClientboundGamePacket::SetTitlesAnimation(_) => {}
|
||||
// ClientboundGamePacket::SoundEntity(_) => {}
|
||||
// ClientboundGamePacket::StopSound(_) => {}
|
||||
// ClientboundGamePacket::TabList(_) => {}
|
||||
// ClientboundGamePacket::TagQuery(_) => {}
|
||||
// ClientboundGamePacket::TakeItemEntity(_) => {}
|
||||
// ClientboundGamePacket::DisguisedChat(_) => {}
|
||||
// ClientboundGamePacket::UpdateEnabledFeatures(_) => {}
|
||||
// ClientboundGamePacket::ContainerClose(_) => {}
|
||||
// }
|
||||
}
|
||||
|
||||
/// A system that clears all packets in the clientbound packet events.
|
||||
pub fn clear_packets(mut query: Query<&mut PacketReceiver>) {
|
||||
for mut packets in query.iter_mut() {
|
||||
packets.packets.lock().clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl PacketReceiver {
|
||||
/// Loop that reads from the connection and adds the packets to the queue +
|
||||
/// runs the schedule.
|
||||
pub async fn read_task(&self, mut read_conn: ReadConnection<ClientboundGamePacket>) {
|
||||
while let Ok(packet) = read_conn.read().await {
|
||||
self.packets.lock().push(packet);
|
||||
// tell the client to run all the systems
|
||||
self.run_schedule_sender.send(());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,13 +6,12 @@ mod world_collisions;
|
|||
|
||||
use azalea_core::{Axis, Vec3, AABB, EPSILON};
|
||||
use azalea_world::{
|
||||
entity::{self, EntityId},
|
||||
entity::{self},
|
||||
MoveEntityError, World,
|
||||
};
|
||||
pub use blocks::BlockWithShape;
|
||||
pub use discrete_voxel_shape::*;
|
||||
pub use shape::*;
|
||||
use std::ops::Deref;
|
||||
|
||||
use self::world_collisions::get_block_collisions;
|
||||
|
||||
|
@ -74,7 +73,6 @@ fn collide(movement: &Vec3, world: &World, physics: &entity::Physics) -> Vec3 {
|
|||
pub fn move_colliding(
|
||||
_mover_type: &MoverType,
|
||||
movement: &Vec3,
|
||||
entity_id: EntityId,
|
||||
world: &World,
|
||||
position: &mut entity::Position,
|
||||
physics: &mut entity::Physics,
|
||||
|
@ -100,7 +98,7 @@ pub fn move_colliding(
|
|||
|
||||
// movement = this.maybeBackOffFromEdge(movement, moverType);
|
||||
|
||||
let collide_result = { collide(movement, world, physics) };
|
||||
let collide_result = collide(movement, world, physics);
|
||||
|
||||
let move_distance = collide_result.length_sqr();
|
||||
|
||||
|
@ -115,7 +113,7 @@ pub fn move_colliding(
|
|||
}
|
||||
};
|
||||
|
||||
world.set_entity_pos(entity_id, new_pos, position, physics);
|
||||
**position = new_pos;
|
||||
}
|
||||
|
||||
let x_collision = movement.x != collide_result.x;
|
||||
|
@ -127,7 +125,7 @@ pub fn move_colliding(
|
|||
|
||||
// TODO: minecraft checks for a "minor" horizontal collision here
|
||||
|
||||
let _block_pos_below = entity::on_pos_legacy(world, position, physics);
|
||||
let _block_pos_below = entity::on_pos_legacy(&world.chunks, position, physics);
|
||||
// let _block_state_below = self
|
||||
// .world
|
||||
// .get_block_state(&block_pos_below)
|
||||
|
|
|
@ -2,7 +2,7 @@ use super::Shapes;
|
|||
use crate::collision::{BlockWithShape, VoxelShape, AABB};
|
||||
use azalea_block::BlockState;
|
||||
use azalea_core::{ChunkPos, ChunkSectionPos, Cursor3d, CursorIterationType, EPSILON};
|
||||
use azalea_world::{entity::EntityId, Chunk, World};
|
||||
use azalea_world::{Chunk, World};
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -57,7 +57,7 @@ impl<'a> BlockCollisions<'a> {
|
|||
// return var7;
|
||||
// }
|
||||
|
||||
self.world.get_chunk(&chunk_pos)
|
||||
self.world.chunks.get(&chunk_pos)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ impl<'a> Iterator for BlockCollisions<'a> {
|
|||
let pos = item.pos;
|
||||
let block_state: BlockState = chunk
|
||||
.read()
|
||||
.get(&(&pos).into(), self.world.min_y())
|
||||
.get(&(&pos).into(), self.world.chunks.min_y)
|
||||
.unwrap_or(BlockState::Air);
|
||||
|
||||
// TODO: continue if self.only_suffocating_blocks and the block is not
|
||||
|
|
|
@ -5,16 +5,14 @@ pub mod collision;
|
|||
use azalea_block::{Block, BlockState};
|
||||
use azalea_core::{BlockPos, Vec3};
|
||||
use azalea_world::{
|
||||
entity::{self, EntityId},
|
||||
entity::{self},
|
||||
World,
|
||||
};
|
||||
use collision::{move_colliding, MoverType};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Move the entity with the given acceleration while handling friction,
|
||||
/// gravity, collisions, and some other stuff.
|
||||
fn travel(
|
||||
entity_id: EntityId,
|
||||
world: &World,
|
||||
physics: &mut entity::Physics,
|
||||
position: &mut entity::Position,
|
||||
|
@ -38,6 +36,7 @@ fn travel(
|
|||
let block_pos_below = get_block_pos_below_that_affects_movement(position);
|
||||
|
||||
let block_state_below = world
|
||||
.chunks
|
||||
.get_block_state(&block_pos_below)
|
||||
.unwrap_or(BlockState::Air);
|
||||
let block_below: Box<dyn Block> = block_state_below.into();
|
||||
|
@ -53,7 +52,6 @@ fn travel(
|
|||
let mut movement = handle_relative_friction_and_calculate_movement(
|
||||
acceleration,
|
||||
block_friction,
|
||||
entity_id,
|
||||
world,
|
||||
physics,
|
||||
position,
|
||||
|
@ -83,7 +81,6 @@ fn travel(
|
|||
/// applies air resistance, calls self.travel(), and some other random
|
||||
/// stuff.
|
||||
pub fn ai_step(
|
||||
entity_id: EntityId,
|
||||
world: &World,
|
||||
physics: &mut entity::Physics,
|
||||
position: &mut entity::Position,
|
||||
|
@ -115,7 +112,6 @@ pub fn ai_step(
|
|||
physics.zza *= 0.98;
|
||||
|
||||
travel(
|
||||
entity_id,
|
||||
world,
|
||||
physics,
|
||||
position,
|
||||
|
@ -168,7 +164,6 @@ fn get_block_pos_below_that_affects_movement(position: &entity::Position) -> Blo
|
|||
fn handle_relative_friction_and_calculate_movement(
|
||||
acceleration: &Vec3,
|
||||
block_friction: f32,
|
||||
entity_id: EntityId,
|
||||
world: &World,
|
||||
physics: &mut entity::Physics,
|
||||
position: &mut entity::Position,
|
||||
|
@ -183,7 +178,6 @@ fn handle_relative_friction_and_calculate_movement(
|
|||
move_colliding(
|
||||
&MoverType::Own,
|
||||
&physics.delta.clone(),
|
||||
entity_id,
|
||||
world,
|
||||
position,
|
||||
physics,
|
||||
|
@ -221,8 +215,10 @@ fn get_friction_influenced_speed(
|
|||
/// Returns the what the entity's jump should be multiplied by based on the
|
||||
/// block they're standing on.
|
||||
fn block_jump_factor(world: &World, position: &entity::Position) -> f32 {
|
||||
let block_at_pos = world.get_block_state(&position.into());
|
||||
let block_below = world.get_block_state(&get_block_pos_below_that_affects_movement(position));
|
||||
let block_at_pos = world.chunks.get_block_state(&position.into());
|
||||
let block_below = world
|
||||
.chunks
|
||||
.get_block_state(&get_block_pos_below_that_affects_movement(position));
|
||||
|
||||
let block_at_pos_jump_factor = if let Some(block) = block_at_pos {
|
||||
Box::<dyn Block>::from(block).behavior().jump_factor
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use azalea_buf::McBuf;
|
||||
use azalea_core::Vec3;
|
||||
use azalea_core::{ResourceLocation, Vec3};
|
||||
use azalea_protocol_macros::ClientboundGamePacket;
|
||||
use azalea_world::entity::{
|
||||
metadata::{apply_default_metadata, PlayerMetadataBundle, UpdateMetadataError},
|
||||
|
@ -41,8 +41,11 @@ pub struct ClientboundAddEntityPacket {
|
|||
// }
|
||||
|
||||
impl ClientboundAddEntityPacket {
|
||||
pub fn as_entity_bundle(&self) -> EntityBundle {
|
||||
EntityBundle::new(self.uuid, self.position, self.entity_type)
|
||||
/// Make the entity into a bundle that can be inserted into the ECS. You
|
||||
/// must apply the metadata after inserting the bundle with
|
||||
/// [`Self::apply_metadata`].
|
||||
pub fn as_entity_bundle(&self, world_name: ResourceLocation) -> EntityBundle {
|
||||
EntityBundle::new(self.uuid, self.position, self.entity_type, world_name)
|
||||
}
|
||||
|
||||
pub fn apply_metadata(&self, entity: &mut bevy_ecs::world::EntityMut) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use azalea_buf::McBuf;
|
||||
use azalea_core::Vec3;
|
||||
use azalea_core::{ResourceLocation, Vec3};
|
||||
use azalea_protocol_macros::ClientboundGamePacket;
|
||||
use azalea_registry::EntityKind;
|
||||
use azalea_world::entity::{metadata::PlayerMetadataBundle, EntityBundle, PlayerBundle};
|
||||
|
@ -18,9 +18,9 @@ pub struct ClientboundAddPlayerPacket {
|
|||
}
|
||||
|
||||
impl ClientboundAddPlayerPacket {
|
||||
pub fn as_player_bundle(&self) -> PlayerBundle {
|
||||
pub fn as_player_bundle(&self, world_name: ResourceLocation) -> PlayerBundle {
|
||||
PlayerBundle {
|
||||
entity: EntityBundle::new(self.uuid, self.position, EntityKind::Player),
|
||||
entity: EntityBundle::new(self.uuid, self.position, EntityKind::Player, world_name),
|
||||
metadata: PlayerMetadataBundle::default(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,13 +15,8 @@ use std::{
|
|||
const SECTION_HEIGHT: u32 = 16;
|
||||
|
||||
/// An efficient storage of chunks for a client that has a limited render
|
||||
/// distance. This has support for using a shared [`WeakChunkStorage`]. If you
|
||||
/// have an infinite render distance (like a server), you should use
|
||||
/// [`ChunkStorage`] instead.
|
||||
/// distance. This has support for using a shared [`ChunkStorage`].
|
||||
pub struct PartialChunkStorage {
|
||||
/// Chunk storage that can be shared by clients.
|
||||
pub shared: Arc<RwLock<WeakChunkStorage>>,
|
||||
|
||||
pub view_center: ChunkPos,
|
||||
chunk_radius: u32,
|
||||
view_range: u32,
|
||||
|
@ -33,23 +28,16 @@ pub struct PartialChunkStorage {
|
|||
/// actively being used somewhere else they'll be forgotten. This is used for
|
||||
/// shared worlds.
|
||||
#[derive(Debug)]
|
||||
pub struct WeakChunkStorage {
|
||||
pub struct ChunkStorage {
|
||||
pub height: u32,
|
||||
pub min_y: i32,
|
||||
pub chunks: HashMap<ChunkPos, Weak<RwLock<Chunk>>>,
|
||||
}
|
||||
|
||||
/// A storage of potentially infinite chunks in a world. Chunks are stored as
|
||||
/// an `Arc<Mutex>` so they can be shared across threads.
|
||||
pub struct ChunkStorage {
|
||||
pub height: u32,
|
||||
pub min_y: i32,
|
||||
pub chunks: HashMap<ChunkPos, Arc<RwLock<Chunk>>>,
|
||||
}
|
||||
|
||||
/// A single chunk in a world (16*?*16 blocks). This only contains the blocks
|
||||
/// and biomes. You can derive the height of the chunk from the number of
|
||||
/// sections, but you need a [`ChunkStorage`] to get the minimum Y coordinate.
|
||||
/// sections, but you need a [`ChunkStorage`] to get the minimum Y
|
||||
/// coordinate.
|
||||
#[derive(Debug)]
|
||||
pub struct Chunk {
|
||||
pub sections: Vec<Section>,
|
||||
|
@ -82,10 +70,9 @@ impl Default for Chunk {
|
|||
}
|
||||
|
||||
impl PartialChunkStorage {
|
||||
pub fn new(chunk_radius: u32, shared: Arc<RwLock<WeakChunkStorage>>) -> Self {
|
||||
pub fn new(chunk_radius: u32) -> Self {
|
||||
let view_range = chunk_radius * 2 + 1;
|
||||
PartialChunkStorage {
|
||||
shared,
|
||||
view_center: ChunkPos::new(0, 0),
|
||||
chunk_radius,
|
||||
view_range,
|
||||
|
@ -93,13 +80,6 @@ impl PartialChunkStorage {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn min_y(&self) -> i32 {
|
||||
self.shared.read().min_y
|
||||
}
|
||||
pub fn height(&self) -> u32 {
|
||||
self.shared.read().height
|
||||
}
|
||||
|
||||
fn get_index(&self, chunk_pos: &ChunkPos) -> usize {
|
||||
(i32::rem_euclid(chunk_pos.x, self.view_range as i32) * (self.view_range as i32)
|
||||
+ i32::rem_euclid(chunk_pos.z, self.view_range as i32)) as usize
|
||||
|
@ -110,20 +90,28 @@ impl PartialChunkStorage {
|
|||
&& (chunk_pos.z - self.view_center.z).unsigned_abs() <= self.chunk_radius
|
||||
}
|
||||
|
||||
pub fn set_block_state(&self, pos: &BlockPos, state: BlockState) -> Option<BlockState> {
|
||||
if pos.y < self.min_y() || pos.y >= (self.min_y() + self.height() as i32) {
|
||||
pub fn set_block_state(
|
||||
&self,
|
||||
pos: &BlockPos,
|
||||
state: BlockState,
|
||||
chunk_storage: &mut ChunkStorage,
|
||||
) -> Option<BlockState> {
|
||||
if pos.y < chunk_storage.min_y
|
||||
|| pos.y >= (chunk_storage.min_y + chunk_storage.height as i32)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let chunk_pos = ChunkPos::from(pos);
|
||||
let chunk = self.get(&chunk_pos)?;
|
||||
let mut chunk = chunk.write();
|
||||
Some(chunk.get_and_set(&ChunkBlockPos::from(pos), state, self.min_y()))
|
||||
let chunk_lock = chunk_storage.get(&chunk_pos)?;
|
||||
let mut chunk = chunk_lock.write();
|
||||
Some(chunk.get_and_set(&ChunkBlockPos::from(pos), state, chunk_storage.min_y))
|
||||
}
|
||||
|
||||
pub fn replace_with_packet_data(
|
||||
&mut self,
|
||||
pos: &ChunkPos,
|
||||
data: &mut Cursor<&[u8]>,
|
||||
chunk_storage: &mut ChunkStorage,
|
||||
) -> Result<(), BufReadError> {
|
||||
debug!("Replacing chunk at {:?}", pos);
|
||||
if !self.in_range(pos) {
|
||||
|
@ -137,11 +125,11 @@ impl PartialChunkStorage {
|
|||
|
||||
let chunk = Arc::new(RwLock::new(Chunk::read_with_dimension_height(
|
||||
data,
|
||||
self.height(),
|
||||
chunk_storage.height,
|
||||
)?));
|
||||
|
||||
trace!("Loaded chunk {:?}", pos);
|
||||
self.set(pos, Some(chunk));
|
||||
self.set(pos, Some(chunk), chunk_storage);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -172,26 +160,19 @@ impl PartialChunkStorage {
|
|||
Some(&mut self.chunks[index])
|
||||
}
|
||||
|
||||
/// Get a chunk,
|
||||
pub fn get(&self, pos: &ChunkPos) -> Option<Arc<RwLock<Chunk>>> {
|
||||
self.shared
|
||||
.read()
|
||||
.chunks
|
||||
.get(pos)
|
||||
.and_then(|chunk| chunk.upgrade())
|
||||
}
|
||||
|
||||
/// Set a chunk in the shared storage and reference it from the limited
|
||||
/// storage.
|
||||
///
|
||||
/// # Panics
|
||||
/// If the chunk is not in the render distance.
|
||||
pub fn set(&mut self, pos: &ChunkPos, chunk: Option<Arc<RwLock<Chunk>>>) {
|
||||
pub fn set(
|
||||
&mut self,
|
||||
pos: &ChunkPos,
|
||||
chunk: Option<Arc<RwLock<Chunk>>>,
|
||||
chunk_storage: &mut ChunkStorage,
|
||||
) {
|
||||
if let Some(chunk) = &chunk {
|
||||
self.shared
|
||||
.write()
|
||||
.chunks
|
||||
.insert(*pos, Arc::downgrade(chunk));
|
||||
chunk_storage.chunks.insert(*pos, Arc::downgrade(chunk));
|
||||
} else {
|
||||
// don't remove it from the shared storage, since it'll be removed
|
||||
// automatically if this was the last reference
|
||||
|
@ -201,9 +182,9 @@ impl PartialChunkStorage {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl WeakChunkStorage {
|
||||
impl ChunkStorage {
|
||||
pub fn new(height: u32, min_y: i32) -> Self {
|
||||
WeakChunkStorage {
|
||||
ChunkStorage {
|
||||
height,
|
||||
min_y,
|
||||
chunks: HashMap::new(),
|
||||
|
@ -295,12 +276,10 @@ impl McBufWritable for Chunk {
|
|||
|
||||
impl Debug for PartialChunkStorage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ChunkStorage")
|
||||
f.debug_struct("PartialChunkStorage")
|
||||
.field("view_center", &self.view_center)
|
||||
.field("chunk_radius", &self.chunk_radius)
|
||||
.field("view_range", &self.view_range)
|
||||
.field("height", &self.height())
|
||||
.field("min_y", &self.min_y())
|
||||
// .field("chunks", &self.chunks)
|
||||
.field("chunks", &format_args!("{} items", self.chunks.len()))
|
||||
.finish()
|
||||
|
@ -373,10 +352,10 @@ impl Section {
|
|||
|
||||
impl Default for PartialChunkStorage {
|
||||
fn default() -> Self {
|
||||
Self::new(8, Arc::new(RwLock::new(WeakChunkStorage::default())))
|
||||
Self::new(8)
|
||||
}
|
||||
}
|
||||
impl Default for WeakChunkStorage {
|
||||
impl Default for ChunkStorage {
|
||||
fn default() -> Self {
|
||||
Self::new(384, -64)
|
||||
}
|
||||
|
@ -408,34 +387,26 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_out_of_bounds_y() {
|
||||
let mut chunk_storage = PartialChunkStorage::default();
|
||||
chunk_storage.set(
|
||||
let mut chunk_storage = ChunkStorage::default();
|
||||
let mut partial_chunk_storage = PartialChunkStorage::default();
|
||||
partial_chunk_storage.set(
|
||||
&ChunkPos { x: 0, z: 0 },
|
||||
Some(Arc::new(RwLock::new(Chunk::default()))),
|
||||
&mut chunk_storage,
|
||||
);
|
||||
assert!(chunk_storage
|
||||
.shared
|
||||
.read()
|
||||
.get_block_state(&BlockPos { x: 0, y: 319, z: 0 })
|
||||
.is_some());
|
||||
assert!(chunk_storage
|
||||
.shared
|
||||
.read()
|
||||
.get_block_state(&BlockPos { x: 0, y: 320, z: 0 })
|
||||
.is_none());
|
||||
assert!(chunk_storage
|
||||
.shared
|
||||
.read()
|
||||
.get_block_state(&BlockPos { x: 0, y: 338, z: 0 })
|
||||
.is_none());
|
||||
assert!(chunk_storage
|
||||
.shared
|
||||
.read()
|
||||
.get_block_state(&BlockPos { x: 0, y: -64, z: 0 })
|
||||
.is_some());
|
||||
assert!(chunk_storage
|
||||
.shared
|
||||
.read()
|
||||
.get_block_state(&BlockPos { x: 0, y: -65, z: 0 })
|
||||
.is_none());
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
use azalea_core::ResourceLocation;
|
||||
use bevy_ecs::system::Resource;
|
||||
use log::error;
|
||||
use parking_lot::Mutex;
|
||||
use parking_lot::RwLock;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
use crate::World;
|
||||
use crate::{ChunkStorage, World};
|
||||
|
||||
/// A container of [`WeakWorld`]s. Worlds are stored as a Weak pointer here, so
|
||||
/// A container of [`World`]s. Worlds are stored as a Weak pointer here, so
|
||||
/// if no clients are using a world it will be forgotten.
|
||||
#[derive(Default, Resource)]
|
||||
pub struct WeakWorldContainer {
|
||||
pub struct WorldContainer {
|
||||
// We just refer to the chunks here and don't include entities because there's not that many
|
||||
// cases where we'd want to get every entity in the world (just getting the entities in chunks
|
||||
// should work fine).
|
||||
|
@ -23,49 +23,52 @@ pub struct WeakWorldContainer {
|
|||
// If it looks like we're relying on the server giving us unique world names, that's because we
|
||||
// are. An evil server could give us two worlds with the same name and then we'd have no way of
|
||||
// telling them apart. We hope most servers are nice and don't do that though. It's only an
|
||||
// issue when there's multiple clients with the same WorldContainer in different worlds
|
||||
// issue when there's multiple clients with the same WeakWorldContainer in different worlds
|
||||
// anyways.
|
||||
pub worlds: HashMap<ResourceLocation, WeakChunkStorage>,
|
||||
|
||||
/// The ECS world that contains all of the entities in all of the worlds.
|
||||
pub ecs: Arc<Mutex<bevy_ecs::world::World>>,
|
||||
pub worlds: HashMap<ResourceLocation, Weak<RwLock<World>>>,
|
||||
}
|
||||
|
||||
impl WeakWorldContainer {
|
||||
impl WorldContainer {
|
||||
pub fn new() -> Self {
|
||||
WeakWorldContainer {
|
||||
WorldContainer {
|
||||
worlds: HashMap::new(),
|
||||
ecs: Arc::new(Mutex::new(bevy_ecs::world::World::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a world from the container.
|
||||
pub fn get(&self, name: &ResourceLocation) -> Option<Arc<World>> {
|
||||
pub fn get(&self, name: &ResourceLocation) -> Option<Arc<RwLock<World>>> {
|
||||
self.worlds.get(name).and_then(|world| world.upgrade())
|
||||
}
|
||||
|
||||
/// Add an empty world to the container (or not if it already exists) and
|
||||
/// returns a strong reference to the world.
|
||||
#[must_use = "the world will be immediately forgotten if unused"]
|
||||
pub fn insert(&mut self, name: ResourceLocation, height: u32, min_y: i32) -> Arc<World> {
|
||||
if let Some(existing) = self.worlds.get(&name).and_then(|world| world.upgrade()) {
|
||||
if existing.height() != height {
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
name: ResourceLocation,
|
||||
height: u32,
|
||||
min_y: i32,
|
||||
) -> Arc<RwLock<World>> {
|
||||
if let Some(existing_lock) = self.worlds.get(&name).and_then(|world| world.upgrade()) {
|
||||
let existing = existing_lock.read();
|
||||
if existing.chunks.height != height {
|
||||
error!(
|
||||
"Shared dimension height mismatch: {} != {}",
|
||||
existing.height(),
|
||||
height,
|
||||
existing.chunks.height, height,
|
||||
);
|
||||
}
|
||||
if existing.min_y() != min_y {
|
||||
if existing.chunks.min_y != min_y {
|
||||
error!(
|
||||
"Shared world min_y mismatch: {} != {}",
|
||||
existing.min_y(),
|
||||
min_y,
|
||||
existing.chunks.min_y, min_y,
|
||||
);
|
||||
}
|
||||
existing
|
||||
existing_lock.clone()
|
||||
} else {
|
||||
let world = Arc::new(World::new(height, min_y, self.ecs.clone()));
|
||||
let world = Arc::new(RwLock::new(World {
|
||||
chunks: ChunkStorage::new(height, min_y),
|
||||
entities_by_chunk: HashMap::new(),
|
||||
}));
|
||||
self.worlds.insert(name, Arc::downgrade(&world));
|
||||
world
|
||||
}
|
||||
|
|
|
@ -3,17 +3,17 @@ mod data;
|
|||
mod dimensions;
|
||||
pub mod metadata;
|
||||
|
||||
use crate::ChunkStorage;
|
||||
|
||||
use self::{attributes::AttributeInstance, metadata::UpdateMetadataError};
|
||||
pub use attributes::Attributes;
|
||||
use azalea_block::BlockState;
|
||||
use azalea_core::{BlockPos, ChunkPos, Vec3, AABB};
|
||||
use bevy_ecs::{
|
||||
bundle::Bundle, component::Component, query::Changed, system::Query, world::EntityMut,
|
||||
};
|
||||
use azalea_core::{BlockPos, ChunkPos, ResourceLocation, Vec3, AABB};
|
||||
use bevy_ecs::{bundle::Bundle, component::Component, query::Changed, system::Query};
|
||||
pub use data::*;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
pub use dimensions::EntityDimensions;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::fmt::Debug;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// A lightweight identifier of an entity.
|
||||
|
@ -41,8 +41,8 @@ impl nohash_hasher::IsEnabled for MinecraftEntityId {}
|
|||
///
|
||||
/// # Safety
|
||||
/// Cached position in the world must be updated.
|
||||
pub fn update_bounding_box(query: Query<(&mut Position, &Physics), Changed<Position>>) {
|
||||
for (mut position, physics) in query.iter_mut() {
|
||||
pub fn update_bounding_box(mut query: Query<(&Position, &mut Physics), Changed<Position>>) {
|
||||
for (position, mut physics) in query.iter_mut() {
|
||||
let bounding_box = physics.dimensions.make_bounding_box(&position);
|
||||
physics.bounding_box = bounding_box;
|
||||
}
|
||||
|
@ -95,8 +95,12 @@ pub fn make_bounding_box(pos: &Position, physics: &Physics) -> AABB {
|
|||
}
|
||||
|
||||
/// Get the position of the block below the entity, but a little lower.
|
||||
pub fn on_pos_legacy(world: &World, position: &Position, physics: &Physics) -> BlockPos {
|
||||
on_pos(world, position, physics, 0.2)
|
||||
pub fn on_pos_legacy(
|
||||
chunk_storage: &ChunkStorage,
|
||||
position: &Position,
|
||||
physics: &Physics,
|
||||
) -> BlockPos {
|
||||
on_pos(0.2, chunk_storage, position, physics)
|
||||
}
|
||||
|
||||
// int x = Mth.floor(this.position.x);
|
||||
|
@ -111,7 +115,12 @@ pub fn on_pos_legacy(world: &World, position: &Position, physics: &Physics) -> B
|
|||
// }
|
||||
// }
|
||||
// return var5;
|
||||
pub fn on_pos(world: &World, pos: &Position, physics: &Physics, offset: f32) -> BlockPos {
|
||||
pub fn on_pos(
|
||||
offset: f32,
|
||||
chunk_storage: &ChunkStorage,
|
||||
pos: &Position,
|
||||
physics: &Physics,
|
||||
) -> BlockPos {
|
||||
let x = pos.x.floor() as i32;
|
||||
let y = (pos.y - offset as f64).floor() as i32;
|
||||
let z = pos.z.floor() as i32;
|
||||
|
@ -119,10 +128,10 @@ pub fn on_pos(world: &World, pos: &Position, physics: &Physics, offset: f32) ->
|
|||
|
||||
// TODO: check if block below is a fence, wall, or fence gate
|
||||
let block_pos = pos.down(1);
|
||||
let block_state = world.get_block_state(&block_pos);
|
||||
let block_state = chunk_storage.get_block_state(&block_pos);
|
||||
if block_state == Some(BlockState::Air) {
|
||||
let block_pos_below = block_pos.down(1);
|
||||
let block_state_below = world.get_block_state(&block_pos_below);
|
||||
let block_state_below = chunk_storage.get_block_state(&block_pos_below);
|
||||
if let Some(_block_state_below) = block_state_below {
|
||||
// if block_state_below.is_fence()
|
||||
// || block_state_below.is_wall()
|
||||
|
@ -154,30 +163,45 @@ impl From<Position> for BlockPos {
|
|||
BlockPos::from(&value.0)
|
||||
}
|
||||
}
|
||||
impl From<&Position> for ChunkPos {
|
||||
fn from(value: &Position) -> Self {
|
||||
ChunkPos::from(value.0)
|
||||
}
|
||||
}
|
||||
impl From<&Position> for BlockPos {
|
||||
fn from(value: &Position) -> Self {
|
||||
BlockPos::from(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// The position of the entity last tick.
|
||||
/// The last position of the entity that was sent to the network.
|
||||
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Deref, DerefMut)]
|
||||
pub struct LastPosition(Vec3);
|
||||
impl From<LastPosition> for ChunkPos {
|
||||
fn from(value: LastPosition) -> Self {
|
||||
pub struct LastSentPosition(Vec3);
|
||||
impl From<LastSentPosition> for ChunkPos {
|
||||
fn from(value: LastSentPosition) -> Self {
|
||||
ChunkPos::from(&value.0)
|
||||
}
|
||||
}
|
||||
impl From<LastPosition> for BlockPos {
|
||||
fn from(value: LastPosition) -> Self {
|
||||
impl From<LastSentPosition> for BlockPos {
|
||||
fn from(value: LastSentPosition) -> Self {
|
||||
BlockPos::from(&value.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the [`LastPosition`] component to the current [`Position`] component.
|
||||
/// This should happen at the end of every tick.
|
||||
pub fn update_last_position(
|
||||
mut query: Query<(&mut Position, &mut LastPosition), Changed<Position>>,
|
||||
) {
|
||||
for (mut position, mut last_position) in query.iter_mut() {
|
||||
*last_position = LastPosition(**position);
|
||||
impl From<&LastSentPosition> for ChunkPos {
|
||||
fn from(value: &LastSentPosition) -> Self {
|
||||
ChunkPos::from(value.0)
|
||||
}
|
||||
}
|
||||
impl From<&LastSentPosition> for BlockPos {
|
||||
fn from(value: &LastSentPosition) -> Self {
|
||||
BlockPos::from(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// The name of the world the entity is in. If two entities share the same world
|
||||
/// name, we assume they're in the same world.
|
||||
#[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)]
|
||||
pub struct WorldName(ResourceLocation);
|
||||
|
||||
/// The physics data relating to the entity, such as position, velocity, and
|
||||
/// bounding box.
|
||||
|
@ -227,14 +251,20 @@ pub struct EntityKind(azalea_registry::EntityKind);
|
|||
pub struct EntityBundle {
|
||||
pub kind: EntityKind,
|
||||
pub uuid: EntityUuid,
|
||||
pub world_name: WorldName,
|
||||
pub position: Position,
|
||||
pub last_position: LastPosition,
|
||||
pub last_sent_position: LastSentPosition,
|
||||
pub physics: Physics,
|
||||
pub attributes: Attributes,
|
||||
}
|
||||
|
||||
impl EntityBundle {
|
||||
pub fn new(uuid: Uuid, pos: Vec3, kind: azalea_registry::EntityKind) -> Self {
|
||||
pub fn new(
|
||||
uuid: Uuid,
|
||||
pos: Vec3,
|
||||
kind: azalea_registry::EntityKind,
|
||||
world_name: ResourceLocation,
|
||||
) -> Self {
|
||||
let dimensions = EntityDimensions {
|
||||
width: 0.6,
|
||||
height: 1.8,
|
||||
|
@ -243,8 +273,9 @@ impl EntityBundle {
|
|||
Self {
|
||||
kind: EntityKind(kind),
|
||||
uuid: EntityUuid(uuid),
|
||||
world_name: WorldName(world_name),
|
||||
position: Position(pos),
|
||||
last_position: Position(pos),
|
||||
last_sent_position: LastSentPosition(pos),
|
||||
physics: Physics {
|
||||
delta: Vec3::default(),
|
||||
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
use crate::entity::{self, Entity, EntityUuid, MinecraftEntityId, Position};
|
||||
use azalea_core::{ChunkPos, ResourceLocation, Vec3};
|
||||
use crate::{
|
||||
entity::{self, update_bounding_box, Entity, MinecraftEntityId},
|
||||
MaybeRemovedEntity, World, WorldContainer,
|
||||
};
|
||||
use azalea_core::ChunkPos;
|
||||
use bevy_app::{App, CoreStage, Plugin};
|
||||
use bevy_ecs::{
|
||||
query::{Changed, QueryEntityError, QueryState, ReadOnlyWorldQuery, WorldQuery},
|
||||
query::Changed,
|
||||
schedule::SystemSet,
|
||||
system::{Query, ResMut, Resource},
|
||||
world::{EntityMut, EntityRef},
|
||||
system::{Query, Res, ResMut, Resource},
|
||||
};
|
||||
use log::warn;
|
||||
use nohash_hasher::{IntMap, IntSet};
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::Debug,
|
||||
sync::{Arc, Weak},
|
||||
ops::DerefMut,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -23,8 +24,11 @@ pub struct EntityPlugin;
|
|||
impl Plugin for EntityPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_system_set_to_stage(
|
||||
CoreStage::Last,
|
||||
SystemSet::new().with_system(update_entity_chunk_positions),
|
||||
CoreStage::PostUpdate,
|
||||
SystemSet::new()
|
||||
.with_system(update_entity_chunk_positions)
|
||||
.with_system(remove_despawned_entities_from_indexes)
|
||||
.with_system(update_bounding_box),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -115,21 +119,25 @@ impl PartialEntityInfos {
|
|||
/// we WILL update it if it's true. Don't call this unless you actually
|
||||
/// got an entity update that all other clients within render distance
|
||||
/// will get too.
|
||||
pub fn maybe_update(&mut self, id: MinecraftEntityId, entity_infos: &mut EntityInfos) -> bool {
|
||||
let this_client_updates_received = self.updates_received.get(&id).copied();
|
||||
pub fn maybe_update(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
id: &MinecraftEntityId,
|
||||
entity_infos: &mut EntityInfos,
|
||||
) -> bool {
|
||||
if Some(entity) == self.owner_entity {
|
||||
// the owner of the entity is always allowed to update it
|
||||
return true;
|
||||
};
|
||||
|
||||
let entity = entity_infos
|
||||
.minecraft_entity_ids_to_azalea_entities
|
||||
.get(&id)
|
||||
.expect("entity should be in minecraft_entity_ids_to_azalea_entities")
|
||||
.clone();
|
||||
let this_client_updates_received = self.updates_received.get(&id).copied();
|
||||
|
||||
let shared_updates_received = entity_infos.updates_received.get(&entity).copied();
|
||||
|
||||
let can_update = this_client_updates_received == shared_updates_received;
|
||||
if can_update {
|
||||
let new_updates_received = this_client_updates_received.unwrap_or(0) + 1;
|
||||
self.updates_received.insert(id, new_updates_received);
|
||||
self.updates_received.insert(*id, new_updates_received);
|
||||
entity_infos
|
||||
.updates_received
|
||||
.insert(entity, new_updates_received);
|
||||
|
@ -140,10 +148,6 @@ impl PartialEntityInfos {
|
|||
}
|
||||
}
|
||||
|
||||
/// This is useful so functions that return an IntSet of entity ids can return a
|
||||
/// reference to nothing.
|
||||
static EMPTY_ENTITY_ID_INTSET: Lazy<HashSet<Entity>> = Lazy::new(|| HashSet::default());
|
||||
|
||||
// TODO: optimization: switch out the `HashMap<Entity, _>`s for `IntMap`s
|
||||
|
||||
/// Things that are shared between all the partial worlds.
|
||||
|
@ -154,9 +158,6 @@ pub struct EntityInfos {
|
|||
/// The number of `PartialWorld`s that have this entity loaded.
|
||||
/// (this is reference counting)
|
||||
pub(crate) entity_reference_count: HashMap<Entity, usize>,
|
||||
/// An index of all the entities we know are in a chunk
|
||||
pub(crate) entities_by_world_name_and_chunk:
|
||||
HashMap<ResourceLocation, HashMap<ChunkPos, HashSet<Entity>>>,
|
||||
/// An index of entities by their UUIDs
|
||||
pub(crate) entity_by_uuid: HashMap<Uuid, Entity>,
|
||||
|
||||
|
@ -171,7 +172,6 @@ impl EntityInfos {
|
|||
pub fn new() -> Self {
|
||||
Self {
|
||||
entity_reference_count: HashMap::default(),
|
||||
entities_by_world_name_and_chunk: HashMap::default(),
|
||||
entity_by_uuid: HashMap::default(),
|
||||
updates_received: HashMap::default(),
|
||||
|
||||
|
@ -185,7 +185,13 @@ impl EntityInfos {
|
|||
/// storage if there's no more references to it.
|
||||
///
|
||||
/// Returns whether the entity was removed.
|
||||
pub fn remove_entity_if_unused(&mut self, entity: Entity, uuid: Uuid, chunk: ChunkPos) -> bool {
|
||||
pub fn remove_entity_if_unused(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
uuid: Uuid,
|
||||
chunk: ChunkPos,
|
||||
world: &mut World,
|
||||
) -> bool {
|
||||
if let Some(count) = self.entity_reference_count.get_mut(&entity) {
|
||||
*count -= 1;
|
||||
if *count == 0 {
|
||||
|
@ -196,7 +202,7 @@ impl EntityInfos {
|
|||
warn!("Tried to remove entity but it was not found.");
|
||||
return false;
|
||||
}
|
||||
if self.entities_by_chunk.remove(&chunk).is_none() {
|
||||
if world.entities_by_chunk.remove(&chunk).is_none() {
|
||||
warn!("Tried to remove entity from chunk {chunk:?} but it was not found.");
|
||||
}
|
||||
if self.entity_by_uuid.remove(&uuid).is_none() {
|
||||
|
@ -210,16 +216,6 @@ impl EntityInfos {
|
|||
true
|
||||
}
|
||||
|
||||
/// Remove a chunk from the storage if the entities in it have no strong
|
||||
/// references left.
|
||||
pub fn remove_chunk_if_unused(&mut self, chunk: &ChunkPos) {
|
||||
if let Some(entities) = self.entities_by_chunk.get(chunk) {
|
||||
if entities.is_empty() {
|
||||
self.entities_by_chunk.remove(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the entity is in the shared storage. To check if a Minecraft
|
||||
/// entity ID is in the storage, you'll have to use
|
||||
/// [`PartialEntityInfo::limited_contains_id`].
|
||||
|
@ -228,13 +224,6 @@ impl EntityInfos {
|
|||
self.entity_reference_count.contains_key(&id)
|
||||
}
|
||||
|
||||
/// Returns a set of entities in the given chunk.
|
||||
pub fn entities_in_chunk<F>(&self, chunk: &ChunkPos) -> &HashSet<Entity> {
|
||||
self.entities_by_chunk
|
||||
.get(chunk)
|
||||
.unwrap_or(&EMPTY_ENTITY_ID_INTSET)
|
||||
}
|
||||
|
||||
pub fn entity_by_uuid(&self, uuid: &Uuid) -> Option<&Entity> {
|
||||
self.entity_by_uuid.get(uuid)
|
||||
}
|
||||
|
@ -242,27 +231,30 @@ impl EntityInfos {
|
|||
|
||||
/// Update the chunk position indexes in [`EntityInfos`].
|
||||
fn update_entity_chunk_positions(
|
||||
query: Query<
|
||||
mut query: Query<
|
||||
(
|
||||
Entity,
|
||||
&entity::Position,
|
||||
&mut entity::LastPosition,
|
||||
&mut entity::Physics,
|
||||
&mut entity::LastSentPosition,
|
||||
&entity::WorldName,
|
||||
),
|
||||
Changed<entity::Position>,
|
||||
>,
|
||||
entity_infos: ResMut<EntityInfos>,
|
||||
world_container: Res<WorldContainer>,
|
||||
) {
|
||||
for (entity, pos, last_pos, mut physics) in query.iter_mut() {
|
||||
for (entity, pos, last_pos, world_name) in query.iter_mut() {
|
||||
let world_lock = world_container.get(&**world_name).unwrap();
|
||||
let mut world = world_lock.write();
|
||||
|
||||
let old_chunk = ChunkPos::from(*last_pos);
|
||||
let new_chunk = ChunkPos::from(*pos);
|
||||
|
||||
if old_chunk != new_chunk {
|
||||
// move the entity from the old chunk to the new one
|
||||
if let Some(entities) = entity_infos.entities_by_chunk.get_mut(&old_chunk) {
|
||||
if let Some(entities) = world.entities_by_chunk.get_mut(&old_chunk) {
|
||||
entities.remove(&entity);
|
||||
}
|
||||
entity_infos
|
||||
world
|
||||
.entities_by_chunk
|
||||
.entry(new_chunk)
|
||||
.or_default()
|
||||
|
@ -271,6 +263,40 @@ fn update_entity_chunk_positions(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn remove_despawned_entities_from_indexes(
|
||||
mut entity_infos: ResMut<EntityInfos>,
|
||||
world_container: Res<WorldContainer>,
|
||||
query: Query<
|
||||
(
|
||||
Entity,
|
||||
&entity::EntityUuid,
|
||||
&entity::Position,
|
||||
&entity::WorldName,
|
||||
),
|
||||
&MaybeRemovedEntity,
|
||||
>,
|
||||
) {
|
||||
for (entity, uuid, position, world_name) in &query {
|
||||
let world = world_container.get(world_name).unwrap();
|
||||
entity_infos.remove_entity_if_unused(
|
||||
entity,
|
||||
**uuid,
|
||||
(*position).into(),
|
||||
world.write().deref_mut(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a chunk from the storage if the entities in it have no strong
|
||||
/// references left.
|
||||
pub fn remove_chunk_if_unused(world: &mut World, chunk: &ChunkPos) {
|
||||
if let Some(entities) = world.entities_by_chunk.get(chunk) {
|
||||
if entities.is_empty() {
|
||||
world.entities_by_chunk.remove(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for EntityInfos {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("EntityInfos").finish()
|
||||
|
|
|
@ -13,7 +13,7 @@ mod world;
|
|||
use std::backtrace::Backtrace;
|
||||
|
||||
pub use bit_storage::BitStorage;
|
||||
pub use chunk_storage::{Chunk, ChunkStorage, PartialChunkStorage, WeakChunkStorage};
|
||||
pub use chunk_storage::{Chunk, ChunkStorage, PartialChunkStorage};
|
||||
pub use container::*;
|
||||
pub use entity_info::{EntityInfos, PartialEntityInfos};
|
||||
use thiserror::Error;
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
use crate::{
|
||||
entity::{self, Entity, MinecraftEntityId},
|
||||
Chunk, EntityInfos, MoveEntityError, PartialChunkStorage, PartialEntityInfos, WeakChunkStorage,
|
||||
entity::{self, Entity, MinecraftEntityId, WorldName},
|
||||
Chunk, ChunkStorage, EntityInfos, MoveEntityError, PartialChunkStorage, PartialEntityInfos,
|
||||
WorldContainer,
|
||||
};
|
||||
use azalea_core::{ChunkPos, PositionDelta8};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
system::{Commands, Query},
|
||||
};
|
||||
use std::fmt::Formatter;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::Debug,
|
||||
};
|
||||
use azalea_block::BlockState;
|
||||
use azalea_buf::BufReadError;
|
||||
use azalea_core::{BlockPos, ChunkPos, PositionDelta8, Vec3};
|
||||
use log::warn;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use std::{backtrace::Backtrace, fmt::Debug};
|
||||
use std::{fmt::Formatter, io::Cursor, sync::Arc};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// PartialWorlds are usually owned by clients, and hold strong references to
|
||||
/// chunks and entities in [`WeakWorld`]s.
|
||||
|
@ -20,265 +23,127 @@ use uuid::Uuid;
|
|||
///
|
||||
/// This is primarily useful for having multiple clients in the same world.
|
||||
pub struct PartialWorld {
|
||||
// we just need to keep a strong reference to `shared` so it doesn't get
|
||||
// dropped, we don't need to do anything with it
|
||||
pub shared: Arc<World>,
|
||||
|
||||
pub chunks: PartialChunkStorage,
|
||||
/// Some metadata about entities, like what entities are in certain chunks.
|
||||
/// This does not contain the entity data itself, that's in the ECS.
|
||||
pub entity_infos: PartialEntityInfos,
|
||||
}
|
||||
|
||||
impl PartialWorld {
|
||||
pub fn new(
|
||||
chunk_radius: u32,
|
||||
shared: Arc<World>,
|
||||
owner_entity: Option<Entity>,
|
||||
entity_infos: &mut EntityInfos,
|
||||
) -> Self {
|
||||
PartialWorld {
|
||||
shared: shared.clone(),
|
||||
chunks: PartialChunkStorage::new(chunk_radius, shared.chunks.clone()),
|
||||
chunks: PartialChunkStorage::new(chunk_radius),
|
||||
entity_infos: PartialEntityInfos::new(owner_entity, entity_infos),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_with_packet_data(
|
||||
&mut self,
|
||||
pos: &ChunkPos,
|
||||
data: &mut Cursor<&[u8]>,
|
||||
) -> Result<(), BufReadError> {
|
||||
self.chunks.replace_with_packet_data(pos, data)
|
||||
}
|
||||
|
||||
pub fn get_chunk(&self, pos: &ChunkPos) -> Option<Arc<RwLock<Chunk>>> {
|
||||
self.chunks.get(pos)
|
||||
}
|
||||
|
||||
pub fn set_chunk(&mut self, pos: &ChunkPos, chunk: Option<Chunk>) -> Result<(), BufReadError> {
|
||||
self.chunks
|
||||
.set(pos, chunk.map(|c| Arc::new(RwLock::new(c))));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_view_center(&mut self, pos: &ChunkPos) {
|
||||
self.chunks.view_center = *pos;
|
||||
}
|
||||
|
||||
pub fn set_block_state(&mut self, pos: &BlockPos, state: BlockState) -> Option<BlockState> {
|
||||
self.chunks.set_block_state(pos, state)
|
||||
}
|
||||
|
||||
/// Whether we're allowed to update the entity with the given ID.
|
||||
///
|
||||
/// Only call this if you're actually updating the entity, otherwise it'll
|
||||
/// cause the update tracker to get out of sync.
|
||||
fn maybe_update_entity(&mut self, entity: Entity, entity_infos: &mut EntityInfos) -> bool {
|
||||
// no entity for you (we're processing this entity somewhere else)
|
||||
if Some(entity) != self.entity_infos.owner_entity
|
||||
&& !self.entity_infos.maybe_update(id, entity_infos)
|
||||
{
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes sure we can modify this EntityId, and returns it if we can.
|
||||
///
|
||||
/// Only call this if you're actually updating the entity, otherwise it'll
|
||||
/// cause the update tracker to get out of sync.
|
||||
pub fn entity_mut(&mut self, id: EntityId) -> Option<EntityId> {
|
||||
if self.maybe_update_entity(id) {
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_entity_pos(
|
||||
&mut self,
|
||||
entity_id: EntityId,
|
||||
new_pos: Vec3,
|
||||
pos: &mut entity::Position,
|
||||
physics: &mut entity::Physics,
|
||||
) -> Result<(), MoveEntityError> {
|
||||
if self.maybe_update_entity(entity_id) {
|
||||
self.shared
|
||||
.entity_infos
|
||||
.write()
|
||||
.set_entity_pos(entity_id, new_pos, pos, physics);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(MoveEntityError::EntityDoesNotExist(Backtrace::capture()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_entity_with_delta(
|
||||
&mut self,
|
||||
entity_id: EntityId,
|
||||
delta: &PositionDelta8,
|
||||
) -> Result<(), MoveEntityError> {
|
||||
let global_ecs_lock = self.shared.global_ecs.clone();
|
||||
let mut ecs = global_ecs_lock.lock();
|
||||
let mut query = ecs.query::<(&mut entity::Position, &mut entity::Physics)>();
|
||||
let (mut pos, mut physics) = query
|
||||
.get_mut(&mut ecs, entity_id.into())
|
||||
.map_err(|_| MoveEntityError::EntityDoesNotExist(Backtrace::capture()))?;
|
||||
|
||||
let new_pos = pos.with_delta(delta);
|
||||
|
||||
self.set_entity_pos(entity_id, new_pos, &mut pos, &mut physics)
|
||||
}
|
||||
|
||||
/// Add an entity to the storage.
|
||||
#[inline]
|
||||
pub fn add_entity(&mut self, id: EntityId, bundle: impl bevy_ecs::bundle::Bundle) {
|
||||
// if the entity is already in the shared world, we don't need to do
|
||||
// anything
|
||||
if self.shared.contains_entity(id) {
|
||||
return;
|
||||
pub fn add_entity(
|
||||
&mut self,
|
||||
commands: &mut Commands,
|
||||
bundle: impl bevy_ecs::bundle::Bundle,
|
||||
entity_infos: &mut EntityInfos,
|
||||
world: &mut World,
|
||||
query: Query<(&entity::Position, &MinecraftEntityId, &entity::EntityUuid)>,
|
||||
id_query: Query<&MinecraftEntityId>,
|
||||
) {
|
||||
let mut entity_commands = commands.spawn(bundle);
|
||||
let entity = entity_commands.id();
|
||||
let (position, &id, uuid) = query.get(entity).unwrap();
|
||||
let chunk_pos = ChunkPos::from(*position);
|
||||
|
||||
// check every entity in this entitys chunk to make sure it doesn't already
|
||||
// exist there
|
||||
if let Some(entities_in_chunk) = world.entities_by_chunk.get(&chunk_pos) {
|
||||
for entity in entities_in_chunk {
|
||||
if id_query.get(*entity).unwrap() == &id {
|
||||
// the entity is already in the world, so remove that extra entity we just made
|
||||
entity_commands.despawn();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut ecs = self.shared.global_ecs.lock();
|
||||
entity::new_entity(&mut ecs, id, bundle);
|
||||
|
||||
let mut query = ecs.query::<(&entity::EntityUuid, &entity::Position)>();
|
||||
let (&uuid, &pos) = query.get(&mut ecs, id.into()).unwrap();
|
||||
|
||||
let partial_entity_infos = &mut self.entity_infos;
|
||||
let mut shared_entity_infos = self.shared.entity_infos.write();
|
||||
partial_entity_infos.loaded_entity_ids.insert(id);
|
||||
|
||||
// add the entity to the indexes
|
||||
shared_entity_infos
|
||||
.ids_by_chunk
|
||||
.entry(ChunkPos::from(&pos))
|
||||
world
|
||||
.entities_by_chunk
|
||||
.entry(chunk_pos)
|
||||
.or_default()
|
||||
.insert(id);
|
||||
shared_entity_infos.id_by_uuid.insert(*uuid, id);
|
||||
partial_entity_infos.loaded_entity_ids.insert(id);
|
||||
.insert(entity);
|
||||
entity_infos.entity_by_uuid.insert(**uuid, entity);
|
||||
// set our updates_received to the shared updates_received, unless it's
|
||||
// not there in which case set both to 1
|
||||
if let Some(&shared_updates_received) = shared_entity_infos.updates_received.get(&id) {
|
||||
if let Some(&shared_updates_received) = entity_infos.updates_received.get(&entity) {
|
||||
// 0 means we're never tracking updates for this entity
|
||||
if shared_updates_received != 0 || Some(id) == partial_entity_infos.owner_entity_id {
|
||||
partial_entity_infos.updates_received.insert(id, 1);
|
||||
if shared_updates_received != 0 || Some(entity) == partial_entity_infos.owner_entity {
|
||||
partial_entity_infos
|
||||
.updates_received
|
||||
.insert(id, shared_updates_received);
|
||||
}
|
||||
} else {
|
||||
shared_entity_infos.updates_received.insert(id, 1);
|
||||
entity_infos.updates_received.insert(entity, 1);
|
||||
partial_entity_infos.updates_received.insert(id, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove an entity from this storage by its id. It will only be removed
|
||||
/// from the shared storage if there are no other references to it.
|
||||
#[inline]
|
||||
pub fn remove_entity_by_id(&mut self, id: EntityId) {
|
||||
let partial_entity_infos = &mut self.entity_infos;
|
||||
let mut shared_entity_infos = self.shared.entity_infos.write();
|
||||
pub unsafe fn move_entity_with_delta(delta: &PositionDelta8, position: &mut entity::Position) {
|
||||
let new_pos = position.with_delta(delta);
|
||||
**position = new_pos;
|
||||
}
|
||||
|
||||
if partial_entity_infos.loaded_entity_ids.remove(&id) {
|
||||
let mut ecs = self.shared.global_ecs.lock();
|
||||
/// A component marker signifying that the entity may have been removed from the
|
||||
/// world, but we're not entirely sure.
|
||||
#[derive(Component)]
|
||||
pub struct MaybeRemovedEntity;
|
||||
|
||||
let mut query = ecs.query::<(&entity::EntityUuid, &entity::Position)>();
|
||||
let (uuid, pos) = query.get(&mut ecs, id.into()).expect(
|
||||
"If the entity was being loaded by this storage, it must be in the shared
|
||||
storage.",
|
||||
);
|
||||
let chunk = ChunkPos::from(pos);
|
||||
let uuid = **uuid;
|
||||
/// Clear all entities in a chunk. This will not clear them from the
|
||||
/// shared storage unless there are no other references to them.
|
||||
pub fn clear_entities_in_chunk(
|
||||
commands: &mut Commands,
|
||||
partial_entity_infos: &mut PartialEntityInfos,
|
||||
chunk: &ChunkPos,
|
||||
world_container: &WorldContainer,
|
||||
world_name: &WorldName,
|
||||
query: Query<&MinecraftEntityId>,
|
||||
) {
|
||||
let world_lock = world_container.get(world_name).unwrap();
|
||||
let world = world_lock.read();
|
||||
|
||||
// TODO: is this line actually necessary? test if it doesn't immediately
|
||||
// panic/deadlock without this line
|
||||
drop(query);
|
||||
|
||||
partial_entity_infos.updates_received.remove(&id);
|
||||
shared_entity_infos.remove_entity_if_unused(id, uuid, chunk);
|
||||
} else {
|
||||
warn!("Tried to remove entity with id {id} but it was not found.")
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all entities in a chunk. This will not clear them from the
|
||||
/// shared storage, unless there are no other references to them.
|
||||
pub fn clear_entities_in_chunk(&mut self, chunk: &ChunkPos) {
|
||||
let partial_entity_infos = &mut self.entity_infos;
|
||||
let mut shared_entity_infos = self.shared.entity_infos.write();
|
||||
|
||||
let mut ecs = self.shared.global_ecs.lock();
|
||||
|
||||
let mut query = ecs.query::<&entity::EntityUuid>();
|
||||
if let Some(entities) = shared_entity_infos.ids_by_chunk.get(chunk).cloned() {
|
||||
for &id in &entities {
|
||||
if partial_entity_infos.loaded_entity_ids.remove(&id) {
|
||||
let uuid = **query.get(&ecs, id.into()).unwrap();
|
||||
// maybe remove it from the storage
|
||||
shared_entity_infos.remove_entity_if_unused(id, uuid, *chunk);
|
||||
}
|
||||
if let Some(entities) = world.entities_by_chunk.get(chunk).cloned() {
|
||||
for &entity in &entities {
|
||||
let id = query.get(entity).unwrap();
|
||||
if partial_entity_infos.loaded_entity_ids.remove(&id) {
|
||||
// maybe remove it from the storage
|
||||
commands.entity(entity).insert(MaybeRemovedEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /// A world where the chunks are stored as weak pointers. This is used for
|
||||
// /// shared worlds.
|
||||
// #[derive(Default, Debug)]
|
||||
// pub struct World {
|
||||
// pub chunks: Arc<RwLock<WeakChunkStorage>>,
|
||||
// }
|
||||
/// A world where the chunks are stored as weak pointers. This is used for
|
||||
/// shared worlds.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct World {
|
||||
pub chunks: ChunkStorage,
|
||||
|
||||
// impl World {
|
||||
// pub fn new(height: u32, min_y: i32) -> Self {
|
||||
// World {
|
||||
// chunks: Arc::new(RwLock::new(WeakChunkStorage::new(height,
|
||||
// min_y))), }
|
||||
// }
|
||||
|
||||
// /// Read the total height of the world. You can add this to
|
||||
// [`Self::min_y`] /// to get the highest possible y coordinate a block can
|
||||
// be placed at. pub fn height(&self) -> u32 {
|
||||
// self.chunks.read().height
|
||||
// }
|
||||
|
||||
// /// Get the lowest possible y coordinate a block can be placed at.
|
||||
// pub fn min_y(&self) -> i32 {
|
||||
// self.chunks.read().min_y
|
||||
// }
|
||||
|
||||
// pub fn contains_entity(&self, id: EntityId) -> bool {
|
||||
// self.entity_infos.read().contains_entity(id)
|
||||
// }
|
||||
|
||||
// pub fn id_by_uuid(&self, uuid: &Uuid) -> Option<EntityId> {
|
||||
// self.entity_infos.read().id_by_uuid(uuid).copied()
|
||||
// }
|
||||
|
||||
// pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> {
|
||||
// self.chunks.read().get_block_state(pos)
|
||||
// }
|
||||
|
||||
// pub fn get_chunk(&self, pos: &ChunkPos) -> Option<Arc<RwLock<Chunk>>> {
|
||||
// self.chunks.read().get(pos)
|
||||
// }
|
||||
|
||||
// pub fn set_entity_pos(
|
||||
// &self,
|
||||
// entity_id: EntityId,
|
||||
// new_pos: Vec3,
|
||||
// pos: &mut entity::Position,
|
||||
// physics: &mut entity::Physics,
|
||||
// ) {
|
||||
// self.entity_infos
|
||||
// .write()
|
||||
// .set_entity_pos(entity_id, new_pos, pos, physics);
|
||||
// }
|
||||
// }
|
||||
/// An index of all the entities we know are in the chunks of the world
|
||||
pub entities_by_chunk: HashMap<ChunkPos, HashSet<Entity>>,
|
||||
}
|
||||
|
||||
impl Debug for PartialWorld {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("World")
|
||||
.field("chunk_storage", &self.chunks)
|
||||
.field("entity_storage", &self.entity_infos)
|
||||
.field("shared", &self.shared)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
@ -290,11 +155,6 @@ impl Default for PartialWorld {
|
|||
let chunk_storage = PartialChunkStorage::default();
|
||||
let entity_storage = PartialEntityInfos::default();
|
||||
Self {
|
||||
shared: Arc::new(World {
|
||||
chunks: chunk_storage.shared.clone(),
|
||||
entity_infos: entity_storage.shared.clone(),
|
||||
global_ecs: Arc::new(Mutex::new(bevy_ecs::world::World::default())),
|
||||
}),
|
||||
chunks: chunk_storage,
|
||||
entity_infos: entity_storage,
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use azalea_protocol::{
|
|||
resolver::{self, ResolverError},
|
||||
ServerAddress,
|
||||
};
|
||||
use azalea_world::WeakWorldContainer;
|
||||
use azalea_world::WorldContainer;
|
||||
use futures::future::join_all;
|
||||
use log::error;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
|
@ -52,7 +52,7 @@ pub struct Swarm<S> {
|
|||
|
||||
resolved_address: SocketAddr,
|
||||
address: ServerAddress,
|
||||
pub worlds: Arc<RwLock<WeakWorldContainer>>,
|
||||
pub worlds: Arc<RwLock<WorldContainer>>,
|
||||
/// Plugins that are set for new bots
|
||||
plugins: Plugins,
|
||||
|
||||
|
@ -228,7 +228,7 @@ pub async fn start_swarm<
|
|||
// resolve the address
|
||||
let resolved_address = resolver::resolve_address(&address).await?;
|
||||
|
||||
let world_container = Arc::new(RwLock::new(WeakWorldContainer::default()));
|
||||
let world_container = Arc::new(RwLock::new(WorldContainer::default()));
|
||||
|
||||
let mut plugins = options.plugins;
|
||||
let swarm_plugins = options.swarm_plugins;
|
||||
|
|
Loading…
Add table
Reference in a new issue