mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
separate minecraft entity ids from azalea entity ids + more ecs stuff
i guess i'm using bevy_app now too huh it's necessary for plugins and it lets us control the tick rate anyways so it's fine i think i'm still not 100% sure how packet handling that interacts with the world will work, but i think if i can sneak the ecs world into there it'll be fine. Can't put packet handling in the schedule because that'd make it tick-bound, which it's not (technically it'd still work but it'd be wrong and anticheats might realize).
This commit is contained in:
parent
2de5a39f31
commit
9193c1b408
13 changed files with 973 additions and 990 deletions
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -422,6 +422,7 @@ dependencies = [
|
|||
"azalea-core",
|
||||
"azalea-nbt",
|
||||
"azalea-registry",
|
||||
"bevy_app",
|
||||
"bevy_ecs",
|
||||
"derive_more",
|
||||
"enum-as-inner",
|
||||
|
@ -454,6 +455,31 @@ version = "0.13.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "bevy_app"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "536e4d0018347478545ed8b6cb6e57b9279ee984868e81b7c0e78e0fb3222e42"
|
||||
dependencies = [
|
||||
"bevy_derive",
|
||||
"bevy_ecs",
|
||||
"bevy_utils",
|
||||
"downcast-rs",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_derive"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7baf73c58d41c353c6fd08e6764a2e7420c9f19e8227b391c50981db6d0282a6"
|
||||
dependencies = [
|
||||
"bevy_macro_utils",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_ecs"
|
||||
version = "0.9.1"
|
||||
|
|
|
@ -36,7 +36,7 @@ use azalea_world::{
|
|||
metadata::{self, PlayerMetadataBundle},
|
||||
EntityId,
|
||||
},
|
||||
PartialChunkStorage, PartialWorld, WeakWorld, WeakWorldContainer,
|
||||
PartialChunkStorage, PartialWorld, WeakWorldContainer, World,
|
||||
};
|
||||
use bevy_ecs::{
|
||||
prelude::Component,
|
||||
|
@ -96,6 +96,8 @@ pub enum Event {
|
|||
Death(Option<Arc<ClientboundPlayerCombatKillPacket>>),
|
||||
}
|
||||
|
||||
/// Client has the things that a user interacting with the library will want.
|
||||
/// 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>>,
|
||||
|
@ -150,7 +152,6 @@ impl Client {
|
|||
world: Arc::new(RwLock::new(PartialWorld::default())),
|
||||
world_container: world_container
|
||||
.unwrap_or_else(|| Arc::new(RwLock::new(WeakWorldContainer::new()))),
|
||||
world_name: Arc::new(RwLock::new(None)),
|
||||
// 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()),
|
||||
|
@ -196,12 +197,19 @@ impl Client {
|
|||
|
||||
// we got the GameConnection, so the server is now connected :)
|
||||
let client = Client::new(game_profile.clone(), None);
|
||||
let local_player =
|
||||
crate::local_player::LocalPlayer::new(game_profile, conn, client.world.clone(), tx);
|
||||
|
||||
let (read_conn, write_conn) = conn.into_split();
|
||||
|
||||
let local_player = crate::local_player::LocalPlayer::new(
|
||||
game_profile,
|
||||
write_conn,
|
||||
client.world.clone(),
|
||||
tx,
|
||||
);
|
||||
|
||||
// just start up the game loop and we're ready!
|
||||
|
||||
client.start_tasks();
|
||||
client.start_tasks(read_conn);
|
||||
|
||||
Ok((client, rx))
|
||||
}
|
||||
|
@ -326,7 +334,7 @@ impl Client {
|
|||
|
||||
/// Write a packet directly to the server.
|
||||
pub async fn write_packet(&self, packet: ServerboundGamePacket) -> Result<(), std::io::Error> {
|
||||
self.local_player_mut().write_packet(packet).await
|
||||
self.local_player_mut().write_packet_async(packet).await
|
||||
}
|
||||
|
||||
/// Disconnect this client from the server, ending all tasks.
|
||||
|
@ -353,37 +361,26 @@ impl Client {
|
|||
|
||||
/// Start the protocol and game tick loop.
|
||||
#[doc(hidden)]
|
||||
pub fn start_tasks(&self) {
|
||||
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(Client::protocol_loop(
|
||||
self.clone(),
|
||||
tx.clone(),
|
||||
)));
|
||||
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(local_player: LocalPlayer, tx: Sender<Event>) {
|
||||
async fn protocol_loop(
|
||||
&self,
|
||||
tx: Sender<Event>,
|
||||
read_conn: ReadConnection<ClientboundGamePacket>,
|
||||
) {
|
||||
loop {
|
||||
let r = local_player.read_conn.lock().await.read().await;
|
||||
let r = read_conn.lock().await.read().await;
|
||||
match r {
|
||||
Ok(packet) => {
|
||||
match {
|
||||
LocalPlayer::send_event(Event::Packet(packet.clone()), &tx);
|
||||
LocalPlayer::handle_packet(&packet, &client, &tx)
|
||||
} {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("Error handling packet: {}", e);
|
||||
if !IGNORE_ERRORS {
|
||||
panic!("Error handling packet: {e}");
|
||||
}
|
||||
}
|
||||
},
|
||||
Ok(packet) => LocalPlayer::send_event(Event::Packet(packet.clone()), &tx),
|
||||
Err(e) => {
|
||||
let e = *e;
|
||||
if let ReadPacketError::ConnectionClosed = e {
|
||||
|
@ -408,9 +405,590 @@ impl Client {
|
|||
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!
|
||||
|
@ -443,7 +1021,7 @@ impl Client {
|
|||
/// 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<WeakWorld> {
|
||||
pub fn world(&self) -> Arc<World> {
|
||||
self.world.read().shared.clone()
|
||||
}
|
||||
|
||||
|
|
|
@ -30,8 +30,7 @@ use crate::{ChatPacket, ClientInformation, Event, PlayerInfo, WalkDirection};
|
|||
#[derive(Component)]
|
||||
pub struct LocalPlayer {
|
||||
pub profile: GameProfile,
|
||||
// Arc<tokio::sync::Mutex<
|
||||
pub read_conn: ReadConnection<ClientboundGamePacket>,
|
||||
|
||||
pub write_conn: WriteConnection<ServerboundGamePacket>,
|
||||
// pub world: Arc<RwLock<PartialWorld>>,
|
||||
pub physics_state: PhysicsState,
|
||||
|
@ -72,26 +71,21 @@ impl LocalPlayer {
|
|||
/// defaults, otherwise use [`Client::join`].
|
||||
pub fn new(
|
||||
profile: GameProfile,
|
||||
conn: Connection<ClientboundGamePacket, ServerboundGamePacket>,
|
||||
write_conn: WriteConnection<ServerboundGamePacket>,
|
||||
world: Arc<RwLock<PartialWorld>>,
|
||||
tx: mpsc::Sender<Event>,
|
||||
) -> Self {
|
||||
let (read_conn, write_conn) = conn.into_split();
|
||||
let (read_conn, write_conn) = (
|
||||
// Arc::new(tokio::sync::Mutex::new(read_conn)),
|
||||
// Arc::new(tokio::sync::Mutex::new(write_conn)),
|
||||
read_conn, write_conn,
|
||||
);
|
||||
|
||||
LocalPlayer {
|
||||
profile,
|
||||
read_conn,
|
||||
write_conn,
|
||||
physics_state: PhysicsState::default(),
|
||||
client_information: ClientInformation::default(),
|
||||
dead: false,
|
||||
players: HashMap::new(),
|
||||
|
||||
world,
|
||||
world_name: Arc::new(RwLock::new(None)),
|
||||
|
||||
tx,
|
||||
world_name: None,
|
||||
}
|
||||
|
@ -146,590 +140,6 @@ impl LocalPlayer {
|
|||
Self::send_event(Event::Tick, &tx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_packet(
|
||||
mut event_reader: EventReader<(EntityId, ClientboundGamePacket)>,
|
||||
query: Query<(&mut LocalPlayer,)>,
|
||||
world_container: ResMut<WeakWorldContainer>,
|
||||
// client: &Client,
|
||||
// tx: &mpsc::Sender<Event>,
|
||||
) -> Result<(), HandlePacketError> {
|
||||
for (player_entity_id, packet) in event_reader.iter() {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
|
|
@ -7,7 +7,7 @@ mod world_collisions;
|
|||
use azalea_core::{Axis, Vec3, AABB, EPSILON};
|
||||
use azalea_world::{
|
||||
entity::{self, EntityId},
|
||||
MoveEntityError, WeakWorld,
|
||||
MoveEntityError, World,
|
||||
};
|
||||
pub use blocks::BlockWithShape;
|
||||
pub use discrete_voxel_shape::*;
|
||||
|
@ -53,7 +53,7 @@ pub enum MoverType {
|
|||
|
||||
// return var4;
|
||||
// }
|
||||
fn collide(movement: &Vec3, world: &WeakWorld, physics: &entity::Physics) -> Vec3 {
|
||||
fn collide(movement: &Vec3, world: &World, physics: &entity::Physics) -> Vec3 {
|
||||
let entity_bounding_box = physics.bounding_box;
|
||||
// TODO: get_entity_collisions
|
||||
// let entity_collisions = world.get_entity_collisions(self,
|
||||
|
@ -75,7 +75,7 @@ pub fn move_colliding(
|
|||
_mover_type: &MoverType,
|
||||
movement: &Vec3,
|
||||
entity_id: EntityId,
|
||||
world: &WeakWorld,
|
||||
world: &World,
|
||||
position: &mut entity::Position,
|
||||
physics: &mut entity::Physics,
|
||||
) -> Result<(), MoveEntityError> {
|
||||
|
@ -188,7 +188,7 @@ pub fn move_colliding(
|
|||
fn collide_bounding_box(
|
||||
movement: &Vec3,
|
||||
entity_bounding_box: &AABB,
|
||||
world: &WeakWorld,
|
||||
world: &World,
|
||||
entity_collisions: Vec<VoxelShape>,
|
||||
) -> Vec3 {
|
||||
let mut collision_boxes: Vec<VoxelShape> = Vec::with_capacity(entity_collisions.len() + 1);
|
||||
|
|
|
@ -2,16 +2,16 @@ 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, WeakWorld};
|
||||
use azalea_world::{entity::EntityId, Chunk, World};
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn get_block_collisions<'a>(world: &'a WeakWorld, aabb: AABB) -> BlockCollisions<'a> {
|
||||
pub fn get_block_collisions<'a>(world: &'a World, aabb: AABB) -> BlockCollisions<'a> {
|
||||
BlockCollisions::new(world, aabb)
|
||||
}
|
||||
|
||||
pub struct BlockCollisions<'a> {
|
||||
pub world: &'a WeakWorld,
|
||||
pub world: &'a World,
|
||||
pub aabb: AABB,
|
||||
pub entity_shape: VoxelShape,
|
||||
pub cursor: Cursor3d,
|
||||
|
@ -19,7 +19,7 @@ pub struct BlockCollisions<'a> {
|
|||
}
|
||||
|
||||
impl<'a> BlockCollisions<'a> {
|
||||
pub fn new(world: &'a WeakWorld, aabb: AABB) -> Self {
|
||||
pub fn new(world: &'a World, aabb: AABB) -> Self {
|
||||
let origin_x = (aabb.min_x - EPSILON) as i32 - 1;
|
||||
let origin_y = (aabb.min_y - EPSILON) as i32 - 1;
|
||||
let origin_z = (aabb.min_z - EPSILON) as i32 - 1;
|
||||
|
|
|
@ -6,7 +6,7 @@ use azalea_block::{Block, BlockState};
|
|||
use azalea_core::{BlockPos, Vec3};
|
||||
use azalea_world::{
|
||||
entity::{self, EntityId},
|
||||
WeakWorld,
|
||||
World,
|
||||
};
|
||||
use collision::{move_colliding, MoverType};
|
||||
use std::ops::Deref;
|
||||
|
@ -15,7 +15,7 @@ use std::ops::Deref;
|
|||
/// gravity, collisions, and some other stuff.
|
||||
fn travel(
|
||||
entity_id: EntityId,
|
||||
world: &WeakWorld,
|
||||
world: &World,
|
||||
physics: &mut entity::Physics,
|
||||
position: &mut entity::Position,
|
||||
attributes: &entity::Attributes,
|
||||
|
@ -84,7 +84,7 @@ fn travel(
|
|||
/// stuff.
|
||||
pub fn ai_step(
|
||||
entity_id: EntityId,
|
||||
world: &WeakWorld,
|
||||
world: &World,
|
||||
physics: &mut entity::Physics,
|
||||
position: &mut entity::Position,
|
||||
sprinting: &entity::metadata::Sprinting,
|
||||
|
@ -132,7 +132,7 @@ pub fn ai_step(
|
|||
}
|
||||
|
||||
fn jump_from_ground(
|
||||
world: &WeakWorld,
|
||||
world: &World,
|
||||
physics: &mut entity::Physics,
|
||||
position: &entity::Position,
|
||||
sprinting: &entity::metadata::Sprinting,
|
||||
|
@ -169,7 +169,7 @@ fn handle_relative_friction_and_calculate_movement(
|
|||
acceleration: &Vec3,
|
||||
block_friction: f32,
|
||||
entity_id: EntityId,
|
||||
world: &WeakWorld,
|
||||
world: &World,
|
||||
physics: &mut entity::Physics,
|
||||
position: &mut entity::Position,
|
||||
attributes: &entity::Attributes,
|
||||
|
@ -220,7 +220,7 @@ 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: &WeakWorld, position: &entity::Position) -> f32 {
|
||||
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));
|
||||
|
||||
|
@ -246,7 +246,7 @@ fn block_jump_factor(world: &WeakWorld, position: &entity::Position) -> f32 {
|
|||
// public double getJumpBoostPower() {
|
||||
// return this.hasEffect(MobEffects.JUMP) ? (double)(0.1F *
|
||||
// (float)(this.getEffect(MobEffects.JUMP).getAmplifier() + 1)) : 0.0D; }
|
||||
fn jump_power(world: &WeakWorld, position: &entity::Position) -> f32 {
|
||||
fn jump_power(world: &World, position: &entity::Position) -> f32 {
|
||||
0.42 * block_jump_factor(world, position)
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ azalea-chat = {path = "../azalea-chat", version = "^0.5.0"}
|
|||
azalea-core = {path = "../azalea-core", version = "^0.5.0", features = ["bevy_ecs"]}
|
||||
azalea-nbt = {path = "../azalea-nbt", version = "^0.5.0"}
|
||||
azalea-registry = {path = "../azalea-registry", version = "^0.5.0"}
|
||||
bevy_app = { version = "0.9.1", default-features = false }
|
||||
bevy_ecs = {version = "0.9.1", default-features = false}
|
||||
derive_more = {version = "0.99.17", features = ["deref", "deref_mut"]}
|
||||
enum-as-inner = "0.5.1"
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use crate::WeakWorld;
|
||||
use azalea_core::ResourceLocation;
|
||||
use bevy_ecs::system::Resource;
|
||||
use log::error;
|
||||
|
@ -8,11 +7,25 @@ use std::{
|
|||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
use crate::World;
|
||||
|
||||
/// A container of [`WeakWorld`]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 worlds: HashMap<ResourceLocation, Weak<WeakWorld>>,
|
||||
// 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).
|
||||
|
||||
// Entities are garbage collected (by manual reference counting in EntityInfos) so we don't
|
||||
// need to worry about them here.
|
||||
|
||||
// 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
|
||||
// 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>>,
|
||||
|
@ -27,14 +40,14 @@ impl WeakWorldContainer {
|
|||
}
|
||||
|
||||
/// Get a world from the container.
|
||||
pub fn get(&self, name: &ResourceLocation) -> Option<Arc<WeakWorld>> {
|
||||
pub fn get(&self, name: &ResourceLocation) -> Option<Arc<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<WeakWorld> {
|
||||
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 {
|
||||
error!(
|
||||
|
@ -52,7 +65,7 @@ impl WeakWorldContainer {
|
|||
}
|
||||
existing
|
||||
} else {
|
||||
let world = Arc::new(WeakWorld::new(height, min_y, self.ecs.clone()));
|
||||
let world = Arc::new(World::new(height, min_y, self.ecs.clone()));
|
||||
self.worlds.insert(name, Arc::downgrade(&world));
|
||||
world
|
||||
}
|
||||
|
|
|
@ -4,112 +4,49 @@ mod dimensions;
|
|||
pub mod metadata;
|
||||
|
||||
use self::{attributes::AttributeInstance, metadata::UpdateMetadataError};
|
||||
use crate::WeakWorld;
|
||||
pub use attributes::Attributes;
|
||||
use azalea_block::BlockState;
|
||||
use azalea_core::{BlockPos, ChunkPos, Vec3, AABB};
|
||||
use bevy_ecs::{bundle::Bundle, component::Component, world::EntityMut};
|
||||
use bevy_ecs::{
|
||||
bundle::Bundle, component::Component, query::Changed, system::Query, world::EntityMut,
|
||||
};
|
||||
pub use data::*;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
pub use dimensions::EntityDimensions;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// An entity ID that's used by ECS library.
|
||||
pub type EcsEntityId = bevy_ecs::entity::Entity;
|
||||
/// A lightweight identifier of an entity.
|
||||
///
|
||||
/// Don't rely on the index of this being the same as a Minecraft entity id!
|
||||
/// (unless you're implementin a server, in which case you can decide your
|
||||
/// entity ids however you want)
|
||||
///
|
||||
/// If you want to refer to a Minecraft entity id, use [`MinecraftEntityId`].
|
||||
pub type Entity = bevy_ecs::entity::Entity;
|
||||
|
||||
/// The unique 32-bit unsigned id of an entity.
|
||||
#[derive(Deref, Eq, PartialEq, DerefMut, Copy, Clone)]
|
||||
pub struct EntityId(pub u32);
|
||||
impl From<EntityId> for EcsEntityId {
|
||||
// bevy_ecs `Entity`s also store the "generation" which adds another 32 bits,
|
||||
// but we don't care about the generation
|
||||
fn from(id: EntityId) -> Self {
|
||||
Self::from_raw(*id)
|
||||
}
|
||||
}
|
||||
impl From<EcsEntityId> for EntityId {
|
||||
fn from(id: EcsEntityId) -> Self {
|
||||
Self(id.index())
|
||||
}
|
||||
}
|
||||
impl Debug for EntityId {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "EntityId({})", **self)
|
||||
}
|
||||
}
|
||||
impl Display for EntityId {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", **self)
|
||||
}
|
||||
}
|
||||
impl std::hash::Hash for EntityId {
|
||||
/// An entity ID used by Minecraft. These are not guaranteed to be unique in
|
||||
/// shared worlds, that's what [`Entity`] is for.
|
||||
#[derive(Component, Copy, Clone, Debug, PartialEq, Eq, Deref, DerefMut)]
|
||||
pub struct MinecraftEntityId(pub u32);
|
||||
impl std::hash::Hash for MinecraftEntityId {
|
||||
fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
|
||||
hasher.write_u32(self.0)
|
||||
}
|
||||
}
|
||||
impl nohash_hasher::IsEnabled for EntityId {}
|
||||
|
||||
// /// A mutable reference to an entity in a world.
|
||||
// pub struct Entity<'w, W = &'w WeakWorld> {
|
||||
// /// The [`WeakWorld`] this entity is in.
|
||||
// pub world: W,
|
||||
// /// The incrementing numerical id of the entity.
|
||||
// pub id: u32,
|
||||
// /// The ECS data for the entity.
|
||||
// pub data: bevy_ecs::world::EntityMut<'w>,
|
||||
// }
|
||||
|
||||
/// Create an entity if you only have a [`bevy_ecs::world::World`].
|
||||
///
|
||||
/// If you do have access to a [`PartialEntityStorage`] though then just call
|
||||
/// [`PartialEntityStorage::insert`].
|
||||
pub(crate) fn new_entity<'w>(
|
||||
ecs: &'w mut bevy_ecs::world::World,
|
||||
id: EntityId,
|
||||
bundle: impl bevy_ecs::bundle::Bundle,
|
||||
) -> EntityMut<'w> {
|
||||
// bevy_ecs only returns None if the entity only exists with a different
|
||||
// generation, which shouldn't be possible here
|
||||
let mut entity = ecs
|
||||
.get_or_spawn(id.into())
|
||||
.expect("Entities should always be generation 0 if we're manually spawning from ids");
|
||||
entity.insert(bundle);
|
||||
entity
|
||||
}
|
||||
|
||||
// impl<'d, D: Deref<Target = WeakWorld>> Entity<'d, D> {
|
||||
// /// Create an Entity when we already know its id and data.
|
||||
// pub(crate) fn new(world: D, id: u32, bundle: impl
|
||||
// bevy_ecs::bundle::Bundle) -> Self { let ecs =
|
||||
// world.entity_storage.write().ecs; let data = new_entity(&mut ecs, id,
|
||||
// bundle); Self { world, id, data }
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl<'d, D: Deref<Target = WeakWorld>> Entity<'d, D> {
|
||||
// // todo: write more here and add an example too
|
||||
// /// Get data from the entity.
|
||||
// pub fn get<T: bevy_ecs::component::Component>(&self) -> Option<&T> {
|
||||
// self.data.get()
|
||||
// }
|
||||
// pub fn get_mut<T: bevy_ecs::component::Component>(
|
||||
// &mut self,
|
||||
// ) -> Option<bevy_ecs::world::Mut<T>> {
|
||||
// self.data.get_mut()
|
||||
// }
|
||||
// }
|
||||
impl nohash_hasher::IsEnabled for MinecraftEntityId {}
|
||||
|
||||
/// Sets the position of the entity. This doesn't update the cache in
|
||||
/// azalea-world, and should only be used within azalea-world!
|
||||
///
|
||||
/// # Safety
|
||||
/// Cached position in the world must be updated.
|
||||
pub unsafe fn move_unchecked(pos: &mut Position, physics: &mut Physics, new_pos: Vec3) {
|
||||
*pos = Position(new_pos);
|
||||
let bounding_box = make_bounding_box(&pos, &physics);
|
||||
pub fn update_bounding_box(query: Query<(&mut Position, &Physics), Changed<Position>>) {
|
||||
for (mut position, physics) in query.iter_mut() {
|
||||
let bounding_box = physics.dimensions.make_bounding_box(&position);
|
||||
physics.bounding_box = bounding_box;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_rotation(physics: &mut Physics, y_rot: f32, x_rot: f32) {
|
||||
physics.y_rot = y_rot % 360.0;
|
||||
|
@ -158,7 +95,7 @@ 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: &WeakWorld, position: &Position, physics: &Physics) -> BlockPos {
|
||||
pub fn on_pos_legacy(world: &World, position: &Position, physics: &Physics) -> BlockPos {
|
||||
on_pos(world, position, physics, 0.2)
|
||||
}
|
||||
|
||||
|
@ -174,7 +111,7 @@ pub fn on_pos_legacy(world: &WeakWorld, position: &Position, physics: &Physics)
|
|||
// }
|
||||
// }
|
||||
// return var5;
|
||||
pub fn on_pos(world: &WeakWorld, pos: &Position, physics: &Physics, offset: f32) -> BlockPos {
|
||||
pub fn on_pos(world: &World, pos: &Position, physics: &Physics, offset: f32) -> 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;
|
||||
|
@ -207,23 +144,45 @@ pub struct EntityUuid(Uuid);
|
|||
/// world.move_entity
|
||||
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Deref, DerefMut)]
|
||||
pub struct Position(Vec3);
|
||||
impl From<&Position> for ChunkPos {
|
||||
fn from(value: &Position) -> Self {
|
||||
impl From<Position> for ChunkPos {
|
||||
fn from(value: Position) -> Self {
|
||||
ChunkPos::from(&value.0)
|
||||
}
|
||||
}
|
||||
impl From<&Position> for BlockPos {
|
||||
fn from(value: &Position) -> Self {
|
||||
impl From<Position> for BlockPos {
|
||||
fn from(value: Position) -> Self {
|
||||
BlockPos::from(&value.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// The position of the entity last tick.
|
||||
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Deref, DerefMut)]
|
||||
pub struct LastPosition(Vec3);
|
||||
impl From<LastPosition> for ChunkPos {
|
||||
fn from(value: LastPosition) -> Self {
|
||||
ChunkPos::from(&value.0)
|
||||
}
|
||||
}
|
||||
impl From<LastPosition> for BlockPos {
|
||||
fn from(value: LastPosition) -> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// The physics data relating to the entity, such as position, velocity, and
|
||||
/// bounding box.
|
||||
#[derive(Debug, Component)]
|
||||
pub struct Physics {
|
||||
/// The position of the entity last tick.
|
||||
pub last_pos: Vec3,
|
||||
pub delta: Vec3,
|
||||
|
||||
/// X acceleration.
|
||||
|
@ -269,6 +228,7 @@ pub struct EntityBundle {
|
|||
pub kind: EntityKind,
|
||||
pub uuid: EntityUuid,
|
||||
pub position: Position,
|
||||
pub last_position: LastPosition,
|
||||
pub physics: Physics,
|
||||
pub attributes: Attributes,
|
||||
}
|
||||
|
@ -284,8 +244,8 @@ impl EntityBundle {
|
|||
kind: EntityKind(kind),
|
||||
uuid: EntityUuid(uuid),
|
||||
position: Position(pos),
|
||||
last_position: Position(pos),
|
||||
physics: Physics {
|
||||
last_pos: pos,
|
||||
delta: Vec3::default(),
|
||||
|
||||
xxa: 0.,
|
||||
|
@ -326,24 +286,24 @@ pub struct PlayerBundle {
|
|||
pub metadata: metadata::PlayerMetadataBundle,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::PartialWorld;
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use crate::PartialWorld;
|
||||
|
||||
#[test]
|
||||
fn from_mut_entity_to_ref_entity() {
|
||||
let mut world = PartialWorld::default();
|
||||
let uuid = Uuid::from_u128(100);
|
||||
world.add_entity(
|
||||
0,
|
||||
EntityData::new(
|
||||
uuid,
|
||||
Vec3::default(),
|
||||
EntityMetadata::Player(metadata::Player::default()),
|
||||
),
|
||||
);
|
||||
let entity: Entity = world.entity_mut(0).unwrap();
|
||||
assert_eq!(entity.uuid, uuid);
|
||||
}
|
||||
}
|
||||
// #[test]
|
||||
// fn from_mut_entity_to_ref_entity() {
|
||||
// let mut world = PartialWorld::default();
|
||||
// let uuid = Uuid::from_u128(100);
|
||||
// world.add_entity(
|
||||
// 0,
|
||||
// EntityData::new(
|
||||
// uuid,
|
||||
// Vec3::default(),
|
||||
// EntityMetadata::Player(metadata::Player::default()),
|
||||
// ),
|
||||
// );
|
||||
// let entity: Entity = world.entity_mut(0).unwrap();
|
||||
// assert_eq!(entity.uuid, uuid);
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -1,20 +1,34 @@
|
|||
use crate::entity::{self, new_entity, EcsEntityId, EntityId, EntityUuid, Position};
|
||||
use azalea_core::{ChunkPos, Vec3};
|
||||
use crate::entity::{self, Entity, EntityUuid, MinecraftEntityId, Position};
|
||||
use azalea_core::{ChunkPos, ResourceLocation, Vec3};
|
||||
use bevy_app::{App, CoreStage, Plugin};
|
||||
use bevy_ecs::{
|
||||
query::{QueryEntityError, QueryState, ReadOnlyWorldQuery, WorldQuery},
|
||||
query::{Changed, QueryEntityError, QueryState, ReadOnlyWorldQuery, WorldQuery},
|
||||
schedule::SystemSet,
|
||||
system::{Query, ResMut, Resource},
|
||||
world::{EntityMut, EntityRef},
|
||||
};
|
||||
use log::warn;
|
||||
use nohash_hasher::{IntMap, IntSet};
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::RwLock;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::Debug,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Plugin handling some basic entity functionality.
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// How entity updates are processed (to avoid issues with shared worlds)
|
||||
// - each bot contains a map of { entity id: updates received }
|
||||
// - the shared world also contains a canonical "true" updates received for each
|
||||
|
@ -40,46 +54,31 @@ use uuid::Uuid;
|
|||
/// You can access the shared storage with `world.shared.read()`.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PartialEntityInfos {
|
||||
pub shared: Arc<RwLock<WeakEntityInfos>>,
|
||||
|
||||
// note: using MinecraftEntityId for entity ids is acceptable here since there's no chance of
|
||||
// collisions here
|
||||
/// The entity id of the player that owns this partial world. This will
|
||||
/// make [`PartialWorld::entity_mut`] pretend the entity doesn't exist so
|
||||
/// it doesn't get modified from outside sources.
|
||||
///
|
||||
/// [`PartialWorld::entity_mut`]: crate::PartialWorld::entity_mut
|
||||
pub owner_entity_id: Option<EntityId>,
|
||||
pub owner_entity: Option<Entity>,
|
||||
/// A counter for each entity that tracks how many updates we've observed
|
||||
/// for it.
|
||||
///
|
||||
/// This is used for shared worlds (i.e. swarms), to make sure we don't
|
||||
/// update entities twice on accident.
|
||||
pub updates_received: IntMap<EntityId, u32>,
|
||||
pub updates_received: IntMap<MinecraftEntityId, u32>,
|
||||
/// A set of all the entity ids in render distance.
|
||||
pub(crate) loaded_entity_ids: IntSet<EntityId>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WeakEntityInfos {
|
||||
/// The number of `PartialWorld`s that have this entity loaded.
|
||||
/// (this is reference counting)
|
||||
pub(crate) entity_reference_count: IntMap<EntityId, usize>,
|
||||
/// An index of all the entity ids we know are in a chunk
|
||||
pub(crate) ids_by_chunk: HashMap<ChunkPos, IntSet<EntityId>>,
|
||||
/// An index of entity ids by their UUIDs
|
||||
pub(crate) id_by_uuid: HashMap<Uuid, EntityId>,
|
||||
|
||||
/// The canonical number of updates we've gotten for every entity.
|
||||
pub updates_received: IntMap<EntityId, u32>,
|
||||
pub(crate) loaded_entity_ids: IntSet<MinecraftEntityId>,
|
||||
}
|
||||
|
||||
impl PartialEntityInfos {
|
||||
pub fn new(shared: Arc<RwLock<WeakEntityInfos>>, owner_entity_id: Option<EntityId>) -> Self {
|
||||
if let Some(owner_entity_id) = owner_entity_id {
|
||||
shared.write().updates_received.insert(owner_entity_id, 0);
|
||||
pub fn new(owner_entity: Option<Entity>, entity_infos: &mut EntityInfos) -> Self {
|
||||
if let Some(owner_entity) = owner_entity {
|
||||
entity_infos.updates_received.insert(owner_entity, 0);
|
||||
}
|
||||
Self {
|
||||
shared,
|
||||
owner_entity_id,
|
||||
owner_entity,
|
||||
updates_received: IntMap::default(),
|
||||
loaded_entity_ids: IntSet::default(),
|
||||
}
|
||||
|
@ -89,19 +88,23 @@ impl PartialEntityInfos {
|
|||
/// If you want to check whether the entity is in the shared storage, use
|
||||
/// [`WeakEntityStorage::contains_id`].
|
||||
#[inline]
|
||||
pub fn limited_contains_id(&self, id: &EntityId) -> bool {
|
||||
self.loaded_entity_ids.contains(id)
|
||||
pub fn limited_contains_id(&self, id: MinecraftEntityId) -> bool {
|
||||
self.loaded_entity_ids.contains(&id)
|
||||
}
|
||||
|
||||
/// Get an [`EntityId`] from this u32 entity ID if the entity is being
|
||||
/// loaded by this storage.
|
||||
///
|
||||
/// Note that you can just create an `EntityId` directly if you want, and
|
||||
/// it'll work if the entity is being loaded by any storage.
|
||||
/// Get an [`Entity`] from the given [`MinecraftEntityId`] (which is just a
|
||||
/// u32 internally) if the entity is being loaded by this storage.
|
||||
#[inline]
|
||||
pub fn limited_get_by_id(&self, id: u32) -> Option<EntityId> {
|
||||
if self.limited_contains_id(&EntityId(id)) {
|
||||
Some(EntityId(id))
|
||||
pub fn limited_get_by_id(
|
||||
&self,
|
||||
id: MinecraftEntityId,
|
||||
entity_infos: &mut EntityInfos,
|
||||
) -> Option<Entity> {
|
||||
if self.limited_contains_id(id) {
|
||||
entity_infos
|
||||
.minecraft_entity_ids_to_azalea_entities
|
||||
.get(&id)
|
||||
.copied()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -112,91 +115,97 @@ 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: EntityId) -> bool {
|
||||
pub fn maybe_update(&mut self, id: MinecraftEntityId, entity_infos: &mut EntityInfos) -> bool {
|
||||
let this_client_updates_received = self.updates_received.get(&id).copied();
|
||||
let shared_updates_received = self.shared.read().updates_received.get(&id).copied();
|
||||
|
||||
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 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.shared
|
||||
.write()
|
||||
entity_infos
|
||||
.updates_received
|
||||
.insert(id, new_updates_received);
|
||||
.insert(entity, new_updates_received);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to an entity by its UUID, if it's being loaded by this
|
||||
/// storage.
|
||||
#[inline]
|
||||
pub fn limited_get_by_uuid(&self, uuid: &Uuid) -> Option<EntityId> {
|
||||
let shared = self.shared.read();
|
||||
let entity_id = shared.id_by_uuid.get(uuid)?;
|
||||
self.limited_get_by_id(entity_id.0)
|
||||
}
|
||||
|
||||
/// Move an entity from its old chunk to a new chunk.
|
||||
#[inline]
|
||||
pub fn update_entity_chunk(
|
||||
&mut self,
|
||||
entity_id: EntityId,
|
||||
old_chunk: &ChunkPos,
|
||||
new_chunk: &ChunkPos,
|
||||
) {
|
||||
self.shared
|
||||
.write()
|
||||
.update_entity_chunk(entity_id, old_chunk, new_chunk);
|
||||
}
|
||||
}
|
||||
|
||||
/// This is useful so functions that return an IntSet of entity ids can return a
|
||||
/// reference to nothing.
|
||||
static EMPTY_ENTITY_ID_INTSET: Lazy<IntSet<EntityId>> = Lazy::new(|| IntSet::default());
|
||||
static EMPTY_ENTITY_ID_INTSET: Lazy<HashSet<Entity>> = Lazy::new(|| HashSet::default());
|
||||
|
||||
impl WeakEntityInfos {
|
||||
// TODO: optimization: switch out the `HashMap<Entity, _>`s for `IntMap`s
|
||||
|
||||
/// Things that are shared between all the partial worlds.
|
||||
#[derive(Resource, Default)]
|
||||
pub struct EntityInfos {
|
||||
// in WeakEntityInfos, we have to use [`Entity`] since there *is* a chance of collision if
|
||||
// we'd have used Minecraft entity IDs
|
||||
/// 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>,
|
||||
|
||||
/// The canonical number of updates we've gotten for every entity.
|
||||
pub updates_received: HashMap<Entity, u32>,
|
||||
|
||||
/// The map of Minecraft entity ids to Azalea ECS entities.
|
||||
pub minecraft_entity_ids_to_azalea_entities: HashMap<u32, Entity>,
|
||||
}
|
||||
|
||||
impl EntityInfos {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
entity_reference_count: IntMap::default(),
|
||||
ids_by_chunk: HashMap::default(),
|
||||
id_by_uuid: HashMap::default(),
|
||||
updates_received: IntMap::default(),
|
||||
entity_reference_count: HashMap::default(),
|
||||
entities_by_world_name_and_chunk: HashMap::default(),
|
||||
entity_by_uuid: HashMap::default(),
|
||||
updates_received: HashMap::default(),
|
||||
|
||||
minecraft_entity_ids_to_azalea_entities: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Call this if a [`PartialEntityStorage`] just removed an entity.
|
||||
///
|
||||
/// It'll
|
||||
/// decrease the reference count and remove the entity from the storage if
|
||||
/// there's no more references to it.
|
||||
/// It'll decrease the reference count and remove the entity from the
|
||||
/// storage if there's no more references to it.
|
||||
///
|
||||
/// Returns whether the entity was removed.
|
||||
pub fn remove_entity_if_unused(&mut self, id: EntityId, uuid: Uuid, chunk: ChunkPos) -> bool {
|
||||
if let Some(count) = self.entity_reference_count.get_mut(&id) {
|
||||
pub fn remove_entity_if_unused(&mut self, entity: Entity, uuid: Uuid, chunk: ChunkPos) -> bool {
|
||||
if let Some(count) = self.entity_reference_count.get_mut(&entity) {
|
||||
*count -= 1;
|
||||
if *count == 0 {
|
||||
self.entity_reference_count.remove(&id);
|
||||
self.entity_reference_count.remove(&entity);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
warn!("Tried to remove entity with id {id} but it was not found.");
|
||||
warn!("Tried to remove entity but it was not found.");
|
||||
return false;
|
||||
}
|
||||
if self.ids_by_chunk.remove(&chunk).is_none() {
|
||||
warn!("Tried to remove entity with id {id} from chunk {chunk:?} but it was not found.");
|
||||
if self.entities_by_chunk.remove(&chunk).is_none() {
|
||||
warn!("Tried to remove entity from chunk {chunk:?} but it was not found.");
|
||||
}
|
||||
if self.id_by_uuid.remove(&uuid).is_none() {
|
||||
warn!("Tried to remove entity with id {id} from uuid {uuid:?} but it was not found.");
|
||||
if self.entity_by_uuid.remove(&uuid).is_none() {
|
||||
warn!("Tried to remove entity from uuid {uuid:?} but it was not found.");
|
||||
}
|
||||
if self.updates_received.remove(&id).is_none() {
|
||||
if self.updates_received.remove(&entity).is_none() {
|
||||
// if this happens it means we weren't tracking the updates_received for the
|
||||
// client (bad)
|
||||
warn!(
|
||||
"Tried to remove entity with id {id} from updates_received but it was not found."
|
||||
);
|
||||
warn!("Tried to remove entity from updates_received but it was not found.");
|
||||
}
|
||||
true
|
||||
}
|
||||
|
@ -204,105 +213,97 @@ impl WeakEntityInfos {
|
|||
/// 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.ids_by_chunk.get(chunk) {
|
||||
if let Some(entities) = self.entities_by_chunk.get(chunk) {
|
||||
if entities.is_empty() {
|
||||
self.ids_by_chunk.remove(chunk);
|
||||
self.entities_by_chunk.remove(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the entity with the given id is in the shared storage.
|
||||
/// 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`].
|
||||
#[inline]
|
||||
pub fn contains_entity(&self, id: EntityId) -> bool {
|
||||
pub fn contains_entity(&self, id: Entity) -> bool {
|
||||
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) -> &IntSet<EntityId> {
|
||||
self.ids_by_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 id_by_uuid(&self, uuid: &Uuid) -> Option<&EntityId> {
|
||||
self.id_by_uuid.get(uuid)
|
||||
}
|
||||
|
||||
/// Move an entity from its old chunk to a new chunk.
|
||||
#[inline]
|
||||
pub fn update_entity_chunk(
|
||||
&mut self,
|
||||
entity_id: EntityId,
|
||||
old_chunk: &ChunkPos,
|
||||
new_chunk: &ChunkPos,
|
||||
) {
|
||||
if let Some(entities) = self.ids_by_chunk.get_mut(old_chunk) {
|
||||
entities.remove(&entity_id);
|
||||
}
|
||||
self.ids_by_chunk
|
||||
.entry(*new_chunk)
|
||||
.or_default()
|
||||
.insert(entity_id);
|
||||
}
|
||||
|
||||
/// Set an entity's position in the world when we already have references
|
||||
/// to the [`Position`] and [`Physics`] components.
|
||||
pub fn set_entity_pos(
|
||||
&mut self,
|
||||
entity_id: EntityId,
|
||||
new_pos: Vec3,
|
||||
pos: &mut entity::Position,
|
||||
physics: &mut entity::Physics,
|
||||
) {
|
||||
let old_chunk = ChunkPos::from(&*pos);
|
||||
let new_chunk = ChunkPos::from(&new_pos);
|
||||
// this is fine because we update the chunk below
|
||||
unsafe { entity::move_unchecked(pos, physics, new_pos) };
|
||||
if old_chunk != new_chunk {
|
||||
self.update_entity_chunk(entity_id, &old_chunk, &new_chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for WeakEntityInfos {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("WeakEntityStorage")
|
||||
// .field("ecs", &self.ecs)
|
||||
.field("entity_reference_count", &self.entity_reference_count)
|
||||
.field("ids_by_chunk", &self.ids_by_chunk)
|
||||
.field("id_by_uuid", &self.id_by_uuid)
|
||||
.field("updates_received", &self.updates_received)
|
||||
.finish()
|
||||
pub fn entity_by_uuid(&self, uuid: &Uuid) -> Option<&Entity> {
|
||||
self.entity_by_uuid.get(uuid)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::entity::metadata;
|
||||
|
||||
use super::*;
|
||||
use azalea_core::Vec3;
|
||||
|
||||
#[test]
|
||||
fn test_store_entity() {
|
||||
let mut storage = PartialEntityInfos::default();
|
||||
assert!(storage.limited_get_by_id(0).is_none());
|
||||
assert!(storage.shared.read().get_by_id(0).is_none());
|
||||
|
||||
let uuid = Uuid::from_u128(100);
|
||||
storage.insert(
|
||||
0,
|
||||
EntityData::new(
|
||||
uuid,
|
||||
Vec3::default(),
|
||||
EntityMetadata::Player(metadata::Player::default()),
|
||||
/// Update the chunk position indexes in [`EntityInfos`].
|
||||
fn update_entity_chunk_positions(
|
||||
query: Query<
|
||||
(
|
||||
Entity,
|
||||
&entity::Position,
|
||||
&mut entity::LastPosition,
|
||||
&mut entity::Physics,
|
||||
),
|
||||
);
|
||||
assert_eq!(storage.limited_get_by_id(0).unwrap().uuid, uuid);
|
||||
assert_eq!(storage.shared.read().get_by_id(0).unwrap().uuid, uuid);
|
||||
Changed<entity::Position>,
|
||||
>,
|
||||
entity_infos: ResMut<EntityInfos>,
|
||||
) {
|
||||
for (entity, pos, last_pos, mut physics) in query.iter_mut() {
|
||||
let old_chunk = ChunkPos::from(*last_pos);
|
||||
let new_chunk = ChunkPos::from(*pos);
|
||||
|
||||
storage.remove_by_id(0);
|
||||
assert!(storage.limited_get_by_id(0).is_none());
|
||||
assert!(storage.shared.read().get_by_id(0).is_none());
|
||||
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) {
|
||||
entities.remove(&entity);
|
||||
}
|
||||
entity_infos
|
||||
.entities_by_chunk
|
||||
.entry(new_chunk)
|
||||
.or_default()
|
||||
.insert(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for EntityInfos {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("EntityInfos").finish()
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use crate::entity::metadata;
|
||||
|
||||
// use super::*;
|
||||
// use azalea_core::Vec3;
|
||||
|
||||
// #[test]
|
||||
// fn test_store_entity() {
|
||||
// let mut storage = PartialEntityInfos::default();
|
||||
// assert!(storage.limited_get_by_id(0).is_none());
|
||||
// assert!(storage.shared.read().get_by_id(0).is_none());
|
||||
|
||||
// let uuid = Uuid::from_u128(100);
|
||||
// storage.insert(
|
||||
// 0,
|
||||
// EntityData::new(
|
||||
// uuid,
|
||||
// Vec3::default(),
|
||||
// EntityMetadata::Player(metadata::Player::default()),
|
||||
// ),
|
||||
// );
|
||||
// assert_eq!(storage.limited_get_by_id(0).unwrap().uuid, uuid);
|
||||
// assert_eq!(storage.shared.read().get_by_id(0).unwrap().uuid, uuid);
|
||||
|
||||
// storage.remove_by_id(0);
|
||||
// assert!(storage.limited_get_by_id(0).is_none());
|
||||
// assert!(storage.shared.read().get_by_id(0).is_none());
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -15,7 +15,7 @@ use std::backtrace::Backtrace;
|
|||
pub use bit_storage::BitStorage;
|
||||
pub use chunk_storage::{Chunk, ChunkStorage, PartialChunkStorage, WeakChunkStorage};
|
||||
pub use container::*;
|
||||
pub use entity_info::{PartialEntityInfos, WeakEntityInfos};
|
||||
pub use entity_info::{EntityInfos, PartialEntityInfos};
|
||||
use thiserror::Error;
|
||||
pub use world::*;
|
||||
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
use crate::{
|
||||
entity::{self, move_unchecked, EntityId},
|
||||
Chunk, MoveEntityError, PartialChunkStorage, PartialEntityInfos, WeakChunkStorage,
|
||||
WeakEntityInfos,
|
||||
entity::{self, Entity, MinecraftEntityId},
|
||||
Chunk, EntityInfos, MoveEntityError, PartialChunkStorage, PartialEntityInfos, WeakChunkStorage,
|
||||
};
|
||||
use azalea_block::BlockState;
|
||||
use azalea_buf::BufReadError;
|
||||
use azalea_core::{BlockPos, ChunkPos, PositionDelta8, Vec3};
|
||||
use bevy_ecs::query::{QueryState, WorldQuery};
|
||||
use log::warn;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use std::{backtrace::Backtrace, fmt::Debug};
|
||||
|
@ -24,7 +22,7 @@ use uuid::Uuid;
|
|||
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<WeakWorld>,
|
||||
pub shared: Arc<World>,
|
||||
|
||||
pub chunks: PartialChunkStorage,
|
||||
pub entity_infos: PartialEntityInfos,
|
||||
|
@ -33,13 +31,14 @@ pub struct PartialWorld {
|
|||
impl PartialWorld {
|
||||
pub fn new(
|
||||
chunk_radius: u32,
|
||||
shared: Arc<WeakWorld>,
|
||||
owner_entity_id: Option<EntityId>,
|
||||
shared: Arc<World>,
|
||||
owner_entity: Option<Entity>,
|
||||
entity_infos: &mut EntityInfos,
|
||||
) -> Self {
|
||||
PartialWorld {
|
||||
shared: shared.clone(),
|
||||
chunks: PartialChunkStorage::new(chunk_radius, shared.chunks.clone()),
|
||||
entity_infos: PartialEntityInfos::new(shared.entity_infos.clone(), owner_entity_id),
|
||||
entity_infos: PartialEntityInfos::new(owner_entity, entity_infos),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,9 +72,11 @@ impl PartialWorld {
|
|||
///
|
||||
/// 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, id: EntityId) -> bool {
|
||||
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(id) != self.entity_infos.owner_entity_id && !self.entity_infos.maybe_update(id) {
|
||||
if Some(entity) != self.entity_infos.owner_entity
|
||||
&& !self.entity_infos.maybe_update(id, entity_infos)
|
||||
{
|
||||
false
|
||||
} else {
|
||||
true
|
||||
|
@ -218,66 +219,59 @@ impl PartialWorld {
|
|||
}
|
||||
}
|
||||
|
||||
/// A world where the chunks are stored as weak pointers. This is used for
|
||||
/// shared worlds.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct WeakWorld {
|
||||
pub chunks: Arc<RwLock<WeakChunkStorage>>,
|
||||
pub(crate) entity_infos: Arc<RwLock<WeakEntityInfos>>,
|
||||
// /// 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 reference to the ECS world that contains all of the entities in all of
|
||||
/// the worlds.
|
||||
pub(crate) global_ecs: Arc<Mutex<bevy_ecs::world::World>>,
|
||||
}
|
||||
// impl World {
|
||||
// pub fn new(height: u32, min_y: i32) -> Self {
|
||||
// World {
|
||||
// chunks: Arc::new(RwLock::new(WeakChunkStorage::new(height,
|
||||
// min_y))), }
|
||||
// }
|
||||
|
||||
impl WeakWorld {
|
||||
pub fn new(height: u32, min_y: i32, global_ecs: Arc<Mutex<bevy_ecs::world::World>>) -> Self {
|
||||
WeakWorld {
|
||||
chunks: Arc::new(RwLock::new(WeakChunkStorage::new(height, min_y))),
|
||||
entity_infos: Arc::new(RwLock::new(WeakEntityInfos::new())),
|
||||
global_ecs,
|
||||
}
|
||||
}
|
||||
// /// 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
|
||||
// }
|
||||
|
||||
/// 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
|
||||
// }
|
||||
|
||||
/// 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 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 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_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 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);
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Debug for PartialWorld {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
|
@ -296,7 +290,7 @@ impl Default for PartialWorld {
|
|||
let chunk_storage = PartialChunkStorage::default();
|
||||
let entity_storage = PartialEntityInfos::default();
|
||||
Self {
|
||||
shared: Arc::new(WeakWorld {
|
||||
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())),
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use super::{Node, VerticalVel};
|
||||
use azalea_core::{BlockPos, CardinalDirection};
|
||||
use azalea_physics::collision::{self, BlockWithShape};
|
||||
use azalea_world::WeakWorld;
|
||||
use azalea_world::World;
|
||||
|
||||
/// whether this block is passable
|
||||
fn is_block_passable(pos: &BlockPos, world: &WeakWorld) -> bool {
|
||||
fn is_block_passable(pos: &BlockPos, world: &World) -> bool {
|
||||
if let Some(block) = world.get_block_state(pos) {
|
||||
block.shape() == &collision::empty_shape()
|
||||
} else {
|
||||
|
@ -13,7 +13,7 @@ fn is_block_passable(pos: &BlockPos, world: &WeakWorld) -> bool {
|
|||
}
|
||||
|
||||
/// whether this block has a solid hitbox (i.e. we can stand on it)
|
||||
fn is_block_solid(pos: &BlockPos, world: &WeakWorld) -> bool {
|
||||
fn is_block_solid(pos: &BlockPos, world: &World) -> bool {
|
||||
if let Some(block) = world.get_block_state(pos) {
|
||||
block.shape() == &collision::block_shape()
|
||||
} else {
|
||||
|
@ -22,14 +22,14 @@ fn is_block_solid(pos: &BlockPos, world: &WeakWorld) -> bool {
|
|||
}
|
||||
|
||||
/// Whether this block and the block above are passable
|
||||
fn is_passable(pos: &BlockPos, world: &WeakWorld) -> bool {
|
||||
fn is_passable(pos: &BlockPos, world: &World) -> bool {
|
||||
is_block_passable(pos, world) && is_block_passable(&pos.up(1), world)
|
||||
}
|
||||
|
||||
/// Whether we can stand in this position. Checks if the block below is solid,
|
||||
/// and that the two blocks above that are passable.
|
||||
|
||||
fn is_standable(pos: &BlockPos, world: &WeakWorld) -> bool {
|
||||
fn is_standable(pos: &BlockPos, world: &World) -> bool {
|
||||
is_block_solid(&pos.down(1), world) && is_passable(pos, world)
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ const JUMP_COST: f32 = 0.5;
|
|||
const WALK_ONE_BLOCK_COST: f32 = 1.0;
|
||||
|
||||
pub trait Move {
|
||||
fn cost(&self, world: &WeakWorld, node: &Node) -> f32;
|
||||
fn cost(&self, world: &World, node: &Node) -> f32;
|
||||
/// Returns by how much the entity's position should be changed when this
|
||||
/// move is executed.
|
||||
fn offset(&self) -> BlockPos;
|
||||
|
@ -51,7 +51,7 @@ pub trait Move {
|
|||
|
||||
pub struct ForwardMove(pub CardinalDirection);
|
||||
impl Move for ForwardMove {
|
||||
fn cost(&self, world: &WeakWorld, node: &Node) -> f32 {
|
||||
fn cost(&self, world: &World, node: &Node) -> f32 {
|
||||
if is_standable(&(node.pos + self.offset()), world)
|
||||
&& node.vertical_vel == VerticalVel::None
|
||||
{
|
||||
|
@ -67,7 +67,7 @@ impl Move for ForwardMove {
|
|||
|
||||
pub struct AscendMove(pub CardinalDirection);
|
||||
impl Move for AscendMove {
|
||||
fn cost(&self, world: &WeakWorld, node: &Node) -> f32 {
|
||||
fn cost(&self, world: &World, node: &Node) -> f32 {
|
||||
if node.vertical_vel == VerticalVel::None
|
||||
&& is_block_passable(&node.pos.up(2), world)
|
||||
&& is_standable(&(node.pos + self.offset()), world)
|
||||
|
@ -89,7 +89,7 @@ impl Move for AscendMove {
|
|||
}
|
||||
pub struct DescendMove(pub CardinalDirection);
|
||||
impl Move for DescendMove {
|
||||
fn cost(&self, world: &WeakWorld, node: &Node) -> f32 {
|
||||
fn cost(&self, world: &World, node: &Node) -> f32 {
|
||||
// check whether 3 blocks vertically forward are passable
|
||||
if node.vertical_vel == VerticalVel::None
|
||||
&& is_standable(&(node.pos + self.offset()), world)
|
||||
|
@ -112,7 +112,7 @@ impl Move for DescendMove {
|
|||
}
|
||||
pub struct DiagonalMove(pub CardinalDirection);
|
||||
impl Move for DiagonalMove {
|
||||
fn cost(&self, world: &WeakWorld, node: &Node) -> f32 {
|
||||
fn cost(&self, world: &World, node: &Node) -> f32 {
|
||||
if node.vertical_vel != VerticalVel::None {
|
||||
return f32::INFINITY;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue