1
2
Fork 0
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:
mat 2023-01-01 03:12:48 -06:00
parent 2de5a39f31
commit 9193c1b408
13 changed files with 973 additions and 990 deletions

26
Cargo.lock generated
View file

@ -422,6 +422,7 @@ dependencies = [
"azalea-core", "azalea-core",
"azalea-nbt", "azalea-nbt",
"azalea-registry", "azalea-registry",
"bevy_app",
"bevy_ecs", "bevy_ecs",
"derive_more", "derive_more",
"enum-as-inner", "enum-as-inner",
@ -454,6 +455,31 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 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]] [[package]]
name = "bevy_ecs" name = "bevy_ecs"
version = "0.9.1" version = "0.9.1"

View file

@ -36,7 +36,7 @@ use azalea_world::{
metadata::{self, PlayerMetadataBundle}, metadata::{self, PlayerMetadataBundle},
EntityId, EntityId,
}, },
PartialChunkStorage, PartialWorld, WeakWorld, WeakWorldContainer, PartialChunkStorage, PartialWorld, WeakWorldContainer, World,
}; };
use bevy_ecs::{ use bevy_ecs::{
prelude::Component, prelude::Component,
@ -96,6 +96,8 @@ pub enum Event {
Death(Option<Arc<ClientboundPlayerCombatKillPacket>>), 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 struct Client {
pub profile: GameProfile, pub profile: GameProfile,
pub entity_id: Arc<RwLock<EntityId>>, pub entity_id: Arc<RwLock<EntityId>>,
@ -150,7 +152,6 @@ impl Client {
world: Arc::new(RwLock::new(PartialWorld::default())), world: Arc::new(RwLock::new(PartialWorld::default())),
world_container: world_container world_container: world_container
.unwrap_or_else(|| Arc::new(RwLock::new(WeakWorldContainer::new()))), .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 // 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(). // field right after this. No Mutex so the user doesn't need to .lock().
plugins: Arc::new(PluginStates::default()), plugins: Arc::new(PluginStates::default()),
@ -196,12 +197,19 @@ impl Client {
// we got the GameConnection, so the server is now connected :) // we got the GameConnection, so the server is now connected :)
let client = Client::new(game_profile.clone(), None); 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! // just start up the game loop and we're ready!
client.start_tasks(); client.start_tasks(read_conn);
Ok((client, rx)) Ok((client, rx))
} }
@ -326,7 +334,7 @@ impl Client {
/// Write a packet directly to the server. /// Write a packet directly to the server.
pub async fn write_packet(&self, packet: ServerboundGamePacket) -> Result<(), std::io::Error> { 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. /// Disconnect this client from the server, ending all tasks.
@ -353,37 +361,26 @@ impl Client {
/// Start the protocol and game tick loop. /// Start the protocol and game tick loop.
#[doc(hidden)] #[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 // 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 // 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 // you might be able to just drop the lock or put it in its own scope to fix
let mut tasks = self.tasks.lock(); let mut tasks = self.tasks.lock();
tasks.push(tokio::spawn(Client::protocol_loop( tasks.push(tokio::spawn(self.protocol_loop(tx.clone(), read_conn)));
self.clone(),
tx.clone(),
)));
let ecs = self.world_container.clone().read().ecs; let ecs = self.world_container.clone().read().ecs;
tasks.push(tokio::spawn(Client::game_tick_loop(ecs.clone()))); 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 { loop {
let r = local_player.read_conn.lock().await.read().await; let r = read_conn.lock().await.read().await;
match r { match r {
Ok(packet) => { Ok(packet) => LocalPlayer::send_event(Event::Packet(packet.clone()), &tx),
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}");
}
}
},
Err(e) => { Err(e) => {
let e = *e; let e = *e;
if let ReadPacketError::ConnectionClosed = e { if let ReadPacketError::ConnectionClosed = e {
@ -408,10 +405,591 @@ impl Client {
panic!("{e}\n{backtrace}") panic!("{e}\n{backtrace}")
} }
} }
}; }
} }
} }
// handling packets is outside of the ecs schedule since it's not bound to
// ticks.
pub fn handle_packet(
client: &Client,
packet: ClientboundGamePacket,
ecs: &mut bevy_ecs::world::World,
tx: &mpsc::Sender<Event>,
) -> Result<(), HandlePacketError> {
let (mut local_player,) = query.get_mut((*player_entity_id).into()).unwrap();
match &packet {
ClientboundGamePacket::Login(p) => {
debug!("Got login packet");
{
// // write p into login.txt
// std::io::Write::write_all(
// &mut std::fs::File::create("login.txt").unwrap(),
// format!("{:#?}", p).as_bytes(),
// )
// .unwrap();
// TODO: have registry_holder be a struct because this sucks rn
// best way would be to add serde support to azalea-nbt
let registry_holder = p
.registry_holder
.as_compound()
.expect("Registry holder is not a compound")
.get("")
.expect("No \"\" tag")
.as_compound()
.expect("\"\" tag is not a compound");
let dimension_types = registry_holder
.get("minecraft:dimension_type")
.expect("No dimension_type tag")
.as_compound()
.expect("dimension_type is not a compound")
.get("value")
.expect("No dimension_type value")
.as_list()
.expect("dimension_type value is not a list");
let dimension_type = dimension_types
.iter()
.find(|t| {
t.as_compound()
.expect("dimension_type value is not a compound")
.get("name")
.expect("No name tag")
.as_string()
.expect("name is not a string")
== p.dimension_type.to_string()
})
.unwrap_or_else(|| {
panic!("No dimension_type with name {}", p.dimension_type)
})
.as_compound()
.unwrap()
.get("element")
.expect("No element tag")
.as_compound()
.expect("element is not a compound");
let height = (*dimension_type
.get("height")
.expect("No height tag")
.as_int()
.expect("height tag is not an int"))
.try_into()
.expect("height is not a u32");
let min_y = *dimension_type
.get("min_y")
.expect("No min_y tag")
.as_int()
.expect("min_y tag is not an int");
let world_name = p.dimension.clone();
local_player.world_name = Some(world_name.clone());
// add this world to the world_container (or don't if it's already there)
let weak_world = world_container.insert(world_name, height, min_y);
// set the loaded_world to an empty world
// (when we add chunks or entities those will be in the world_container)
let mut world_lock = local_player.world.write();
*world_lock = PartialWorld::new(
local_player.client_information.view_distance.into(),
weak_world,
Some(EntityId(p.player_id)),
);
let player_bundle = entity::PlayerBundle {
entity: entity::EntityBundle::new(
local_player.profile.uuid,
Vec3::default(),
azalea_registry::EntityKind::Player,
),
metadata: PlayerMetadataBundle::default(),
};
// let entity = EntityData::new(
// client.profile.uuid,
// Vec3::default(),
// EntityMetadata::Player(metadata::Player::default()),
// );
// the first argument makes it so other entities don't update this entity in
// a shared world
world_lock.add_entity(EntityId(p.player_id), player_bundle);
*client.entity_id.write() = EntityId(p.player_id);
}
// send the client information that we have set
let client_information_packet: ClientInformation =
client.local_player().client_information.clone();
log::debug!(
"Sending client information because login: {:?}",
client_information_packet
);
client.write_packet(client_information_packet.get()).await?;
// brand
client
.write_packet(
ServerboundCustomPayloadPacket {
identifier: ResourceLocation::new("brand").unwrap(),
// they don't have to know :)
data: "vanilla".into(),
}
.get(),
)
.await?;
tx.send(Event::Login).await?;
}
ClientboundGamePacket::SetChunkCacheRadius(p) => {
debug!("Got set chunk cache radius packet {:?}", p);
}
ClientboundGamePacket::CustomPayload(p) => {
debug!("Got custom payload packet {:?}", p);
}
ClientboundGamePacket::ChangeDifficulty(p) => {
debug!("Got difficulty packet {:?}", p);
}
ClientboundGamePacket::Commands(_p) => {
debug!("Got declare commands packet");
}
ClientboundGamePacket::PlayerAbilities(p) => {
debug!("Got player abilities packet {:?}", p);
}
ClientboundGamePacket::SetCarriedItem(p) => {
debug!("Got set carried item packet {:?}", p);
}
ClientboundGamePacket::UpdateTags(_p) => {
debug!("Got update tags packet");
}
ClientboundGamePacket::Disconnect(p) => {
debug!("Got disconnect packet {:?}", p);
client.disconnect().await?;
}
ClientboundGamePacket::UpdateRecipes(_p) => {
debug!("Got update recipes packet");
}
ClientboundGamePacket::EntityEvent(_p) => {
// debug!("Got entity event packet {:?}", p);
}
ClientboundGamePacket::Recipe(_p) => {
debug!("Got recipe packet");
}
ClientboundGamePacket::PlayerPosition(p) => {
// TODO: reply with teleport confirm
debug!("Got player position packet {:?}", p);
let (new_pos, y_rot, x_rot) = {
let player_entity_id = *client.entity();
let world = client.world();
// let mut player_entity = world.entity_mut(player_entity_id).unwrap();
let (mut physics, position) =
client.query::<(&mut entity::Physics, &mut entity::Position)>();
let delta_movement = physics.delta;
let is_x_relative = p.relative_arguments.x;
let is_y_relative = p.relative_arguments.y;
let is_z_relative = p.relative_arguments.z;
let (delta_x, new_pos_x) = if is_x_relative {
physics.last_pos.x += p.x;
(delta_movement.x, position.x + p.x)
} else {
physics.last_pos.x = p.x;
(0.0, p.x)
};
let (delta_y, new_pos_y) = if is_y_relative {
physics.last_pos.y += p.y;
(delta_movement.y, position.y + p.y)
} else {
physics.last_pos.y = p.y;
(0.0, p.y)
};
let (delta_z, new_pos_z) = if is_z_relative {
physics.last_pos.z += p.z;
(delta_movement.z, position.z + p.z)
} else {
physics.last_pos.z = p.z;
(0.0, p.z)
};
let mut y_rot = p.y_rot;
let mut x_rot = p.x_rot;
if p.relative_arguments.x_rot {
x_rot += physics.x_rot;
}
if p.relative_arguments.y_rot {
y_rot += physics.y_rot;
}
physics.delta = Vec3 {
x: delta_x,
y: delta_y,
z: delta_z,
};
entity::set_rotation(physics.into_inner(), y_rot, x_rot);
// TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means
// so investigate that ig
let new_pos = Vec3 {
x: new_pos_x,
y: new_pos_y,
z: new_pos_z,
};
world
.set_entity_pos(
player_entity_id,
new_pos,
position.into_inner(),
physics.into_inner(),
)
.expect("The player entity should always exist");
(new_pos, y_rot, x_rot)
};
client
.write_packet(ServerboundAcceptTeleportationPacket { id: p.id }.get())
.await?;
client
.write_packet(
ServerboundMovePlayerPosRotPacket {
x: new_pos.x,
y: new_pos.y,
z: new_pos.z,
y_rot,
x_rot,
// this is always false
on_ground: false,
}
.get(),
)
.await?;
}
ClientboundGamePacket::PlayerInfoUpdate(p) => {
debug!("Got player info packet {:?}", p);
let mut events = Vec::new();
{
let mut players_lock = client.players.write();
for updated_info in &p.entries {
// add the new player maybe
if p.actions.add_player {
let player_info = PlayerInfo {
profile: updated_info.profile.clone(),
uuid: updated_info.profile.uuid,
gamemode: updated_info.game_mode,
latency: updated_info.latency,
display_name: updated_info.display_name.clone(),
};
players_lock.insert(updated_info.profile.uuid, player_info.clone());
events.push(Event::AddPlayer(player_info));
} else if let Some(info) = players_lock.get_mut(&updated_info.profile.uuid)
{
// `else if` because the block for add_player above
// already sets all the fields
if p.actions.update_game_mode {
info.gamemode = updated_info.game_mode;
}
if p.actions.update_latency {
info.latency = updated_info.latency;
}
if p.actions.update_display_name {
info.display_name = updated_info.display_name.clone();
}
events.push(Event::UpdatePlayer(info.clone()));
} else {
warn!(
"Ignoring PlayerInfoUpdate for unknown player {}",
updated_info.profile.uuid
);
}
}
}
for event in events {
tx.send(event).await?;
}
}
ClientboundGamePacket::PlayerInfoRemove(p) => {
let mut events = Vec::new();
{
let mut players_lock = client.players.write();
for uuid in &p.profile_ids {
if let Some(info) = players_lock.remove(uuid) {
events.push(Event::RemovePlayer(info));
}
}
}
for event in events {
tx.send(event).await?;
}
}
ClientboundGamePacket::SetChunkCacheCenter(p) => {
debug!("Got chunk cache center packet {:?}", p);
client
.world
.write()
.update_view_center(&ChunkPos::new(p.x, p.z));
}
ClientboundGamePacket::LevelChunkWithLight(p) => {
// debug!("Got chunk with light packet {} {}", p.x, p.z);
let pos = ChunkPos::new(p.x, p.z);
// OPTIMIZATION: if we already know about the chunk from the
// shared world (and not ourselves), then we don't need to
// parse it again. This is only used when we have a shared
// world, since we check that the chunk isn't currently owned
// by this client.
let shared_has_chunk = client.world.read().get_chunk(&pos).is_some();
let this_client_has_chunk = client.world.read().chunks.limited_get(&pos).is_some();
if shared_has_chunk && !this_client_has_chunk {
trace!(
"Skipping parsing chunk {:?} because we already know about it",
pos
);
return Ok(());
}
// let chunk = Chunk::read_with_world_height(&mut p.chunk_data);
// debug("chunk {:?}")
if let Err(e) = client
.world
.write()
.replace_with_packet_data(&pos, &mut Cursor::new(&p.chunk_data.data))
{
error!("Couldn't set chunk data: {}", e);
}
}
ClientboundGamePacket::LightUpdate(_p) => {
// debug!("Got light update packet {:?}", p);
}
ClientboundGamePacket::AddEntity(p) => {
debug!("Got add entity packet {:?}", p);
let bundle = p.as_entity_bundle();
let mut world = client.world.write();
world.add_entity(EntityId(p.id), bundle);
// the bundle doesn't include the default entity metadata so we add that
// separately
let mut entities = world.entity_infos.shared.write();
let mut entity = entities.ecs_entity_mut(EntityId(p.id)).unwrap();
p.apply_metadata(&mut entity);
}
ClientboundGamePacket::SetEntityData(p) => {
debug!("Got set entity data packet {:?}", p);
let world = client.world.write();
let mut entities = world.entity_infos.shared.write();
let entity = entities.ecs_entity_mut(EntityId(p.id));
if let Some(mut entity) = entity {
entity::metadata::apply_metadata(&mut entity, p.packed_items.0.clone());
} else {
// warn!("Server sent an entity data packet for an
// entity id ({}) that we don't
// know about", p.id);
}
}
ClientboundGamePacket::UpdateAttributes(_p) => {
// debug!("Got update attributes packet {:?}", p);
}
ClientboundGamePacket::SetEntityMotion(_p) => {
// debug!("Got entity velocity packet {:?}", p);
}
ClientboundGamePacket::SetEntityLink(p) => {
debug!("Got set entity link packet {:?}", p);
}
ClientboundGamePacket::AddPlayer(p) => {
debug!("Got add player packet {:?}", p);
let bundle = p.as_player_bundle();
let mut world = client.world.write();
world.add_entity(EntityId(p.id), bundle);
// the default metadata was already included in the bundle
// for us
}
ClientboundGamePacket::InitializeBorder(p) => {
debug!("Got initialize border packet {:?}", p);
}
ClientboundGamePacket::SetTime(p) => {
debug!("Got set time packet {:?}", p);
}
ClientboundGamePacket::SetDefaultSpawnPosition(p) => {
debug!("Got set default spawn position packet {:?}", p);
}
ClientboundGamePacket::ContainerSetContent(p) => {
debug!("Got container set content packet {:?}", p);
}
ClientboundGamePacket::SetHealth(p) => {
debug!("Got set health packet {:?}", p);
if p.health == 0.0 {
// we can't define a variable here with client.dead.lock()
// because of https://github.com/rust-lang/rust/issues/57478
if !*client.dead.lock() {
*client.dead.lock() = true;
tx.send(Event::Death(None)).await?;
}
}
}
ClientboundGamePacket::SetExperience(p) => {
debug!("Got set experience packet {:?}", p);
}
ClientboundGamePacket::TeleportEntity(p) => {
let mut world = client.world.write();
let (pos, physics) = self.query::<(&entity::Position, &entity::Physics)>();
let _ = world.set_entity_pos(
EntityId(p.id),
Vec3 {
x: p.x,
y: p.y,
z: p.z,
},
pos,
physics,
);
}
ClientboundGamePacket::UpdateAdvancements(p) => {
debug!("Got update advancements packet {:?}", p);
}
ClientboundGamePacket::RotateHead(_p) => {
// debug!("Got rotate head packet {:?}", p);
}
ClientboundGamePacket::MoveEntityPos(p) => {
let mut world = client.world.write();
let _ = world.move_entity_with_delta(EntityId(p.entity_id), &p.delta);
}
ClientboundGamePacket::MoveEntityPosRot(p) => {
let mut world = client.world.write();
let _ = world.move_entity_with_delta(EntityId(p.entity_id), &p.delta);
}
ClientboundGamePacket::MoveEntityRot(_p) => {
// debug!("Got move entity rot packet {:?}", p);
}
ClientboundGamePacket::KeepAlive(p) => {
debug!("Got keep alive packet {:?}", p);
client
.write_packet(ServerboundKeepAlivePacket { id: p.id }.get())
.await?;
}
ClientboundGamePacket::RemoveEntities(p) => {
debug!("Got remove entities packet {:?}", p);
}
ClientboundGamePacket::PlayerChat(p) => {
debug!("Got player chat packet {:?}", p);
tx.send(Event::Chat(ChatPacket::Player(Arc::new(p.clone()))))
.await?;
}
ClientboundGamePacket::SystemChat(p) => {
debug!("Got system chat packet {:?}", p);
tx.send(Event::Chat(ChatPacket::System(Arc::new(p.clone()))))
.await?;
}
ClientboundGamePacket::Sound(_p) => {
// debug!("Got sound packet {:?}", p);
}
ClientboundGamePacket::LevelEvent(p) => {
debug!("Got level event packet {:?}", p);
}
ClientboundGamePacket::BlockUpdate(p) => {
debug!("Got block update packet {:?}", p);
let mut world = client.world.write();
world.set_block_state(&p.pos, p.block_state);
}
ClientboundGamePacket::Animate(p) => {
debug!("Got animate packet {:?}", p);
}
ClientboundGamePacket::SectionBlocksUpdate(p) => {
debug!("Got section blocks update packet {:?}", p);
let mut world = client.world.write();
for state in &p.states {
world.set_block_state(&(p.section_pos + state.pos.clone()), state.state);
}
}
ClientboundGamePacket::GameEvent(p) => {
debug!("Got game event packet {:?}", p);
}
ClientboundGamePacket::LevelParticles(p) => {
debug!("Got level particles packet {:?}", p);
}
ClientboundGamePacket::ServerData(p) => {
debug!("Got server data packet {:?}", p);
}
ClientboundGamePacket::SetEquipment(p) => {
debug!("Got set equipment packet {:?}", p);
}
ClientboundGamePacket::UpdateMobEffect(p) => {
debug!("Got update mob effect packet {:?}", p);
}
ClientboundGamePacket::AddExperienceOrb(_) => {}
ClientboundGamePacket::AwardStats(_) => {}
ClientboundGamePacket::BlockChangedAck(_) => {}
ClientboundGamePacket::BlockDestruction(_) => {}
ClientboundGamePacket::BlockEntityData(_) => {}
ClientboundGamePacket::BlockEvent(_) => {}
ClientboundGamePacket::BossEvent(_) => {}
ClientboundGamePacket::CommandSuggestions(_) => {}
ClientboundGamePacket::ContainerSetData(_) => {}
ClientboundGamePacket::ContainerSetSlot(_) => {}
ClientboundGamePacket::Cooldown(_) => {}
ClientboundGamePacket::CustomChatCompletions(_) => {}
ClientboundGamePacket::DeleteChat(_) => {}
ClientboundGamePacket::Explode(_) => {}
ClientboundGamePacket::ForgetLevelChunk(_) => {}
ClientboundGamePacket::HorseScreenOpen(_) => {}
ClientboundGamePacket::MapItemData(_) => {}
ClientboundGamePacket::MerchantOffers(_) => {}
ClientboundGamePacket::MoveVehicle(_) => {}
ClientboundGamePacket::OpenBook(_) => {}
ClientboundGamePacket::OpenScreen(_) => {}
ClientboundGamePacket::OpenSignEditor(_) => {}
ClientboundGamePacket::Ping(_) => {}
ClientboundGamePacket::PlaceGhostRecipe(_) => {}
ClientboundGamePacket::PlayerCombatEnd(_) => {}
ClientboundGamePacket::PlayerCombatEnter(_) => {}
ClientboundGamePacket::PlayerCombatKill(p) => {
debug!("Got player kill packet {:?}", p);
if client.entity() == EntityId(p.player_id) {
// we can't define a variable here with client.dead.lock()
// because of https://github.com/rust-lang/rust/issues/57478
if !*client.dead.lock() {
*client.dead.lock() = true;
tx.send(Event::Death(Some(Arc::new(p.clone())))).await?;
}
}
}
ClientboundGamePacket::PlayerLookAt(_) => {}
ClientboundGamePacket::RemoveMobEffect(_) => {}
ClientboundGamePacket::ResourcePack(_) => {}
ClientboundGamePacket::Respawn(p) => {
debug!("Got respawn packet {:?}", p);
// Sets clients dead state to false.
let mut dead_lock = client.dead.lock();
*dead_lock = false;
}
ClientboundGamePacket::SelectAdvancementsTab(_) => {}
ClientboundGamePacket::SetActionBarText(_) => {}
ClientboundGamePacket::SetBorderCenter(_) => {}
ClientboundGamePacket::SetBorderLerpSize(_) => {}
ClientboundGamePacket::SetBorderSize(_) => {}
ClientboundGamePacket::SetBorderWarningDelay(_) => {}
ClientboundGamePacket::SetBorderWarningDistance(_) => {}
ClientboundGamePacket::SetCamera(_) => {}
ClientboundGamePacket::SetDisplayObjective(_) => {}
ClientboundGamePacket::SetObjective(_) => {}
ClientboundGamePacket::SetPassengers(_) => {}
ClientboundGamePacket::SetPlayerTeam(_) => {}
ClientboundGamePacket::SetScore(_) => {}
ClientboundGamePacket::SetSimulationDistance(_) => {}
ClientboundGamePacket::SetSubtitleText(_) => {}
ClientboundGamePacket::SetTitleText(_) => {}
ClientboundGamePacket::SetTitlesAnimation(_) => {}
ClientboundGamePacket::SoundEntity(_) => {}
ClientboundGamePacket::StopSound(_) => {}
ClientboundGamePacket::TabList(_) => {}
ClientboundGamePacket::TagQuery(_) => {}
ClientboundGamePacket::TakeItemEntity(_) => {}
ClientboundGamePacket::DisguisedChat(_) => {}
ClientboundGamePacket::UpdateEnabledFeatures(_) => {}
ClientboundGamePacket::ContainerClose(_) => {}
}
Ok(())
}
/// Start the game tick loop for every client in the shared world. This /// Start the game tick loop for every client in the shared world. This
/// should only be run once per shared world! /// should only be run once per shared world!
async fn game_tick_loop(ecs: Arc<Mutex<bevy_ecs::world::World>>) { async fn game_tick_loop(ecs: Arc<Mutex<bevy_ecs::world::World>>) {
@ -443,7 +1021,7 @@ impl Client {
/// client, then it'll be the same as the world the client has loaded. /// 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 /// If the client using a shared world, then the shared world will be a
/// superset of the client's world. /// superset of the client's world.
pub fn world(&self) -> Arc<WeakWorld> { pub fn world(&self) -> Arc<World> {
self.world.read().shared.clone() self.world.read().shared.clone()
} }

View file

@ -30,8 +30,7 @@ use crate::{ChatPacket, ClientInformation, Event, PlayerInfo, WalkDirection};
#[derive(Component)] #[derive(Component)]
pub struct LocalPlayer { pub struct LocalPlayer {
pub profile: GameProfile, pub profile: GameProfile,
// Arc<tokio::sync::Mutex<
pub read_conn: ReadConnection<ClientboundGamePacket>,
pub write_conn: WriteConnection<ServerboundGamePacket>, pub write_conn: WriteConnection<ServerboundGamePacket>,
// pub world: Arc<RwLock<PartialWorld>>, // pub world: Arc<RwLock<PartialWorld>>,
pub physics_state: PhysicsState, pub physics_state: PhysicsState,
@ -72,26 +71,21 @@ impl LocalPlayer {
/// defaults, otherwise use [`Client::join`]. /// defaults, otherwise use [`Client::join`].
pub fn new( pub fn new(
profile: GameProfile, profile: GameProfile,
conn: Connection<ClientboundGamePacket, ServerboundGamePacket>, write_conn: WriteConnection<ServerboundGamePacket>,
world: Arc<RwLock<PartialWorld>>, world: Arc<RwLock<PartialWorld>>,
tx: mpsc::Sender<Event>, tx: mpsc::Sender<Event>,
) -> Self { ) -> 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 { LocalPlayer {
profile, profile,
read_conn,
write_conn, write_conn,
physics_state: PhysicsState::default(), physics_state: PhysicsState::default(),
client_information: ClientInformation::default(), client_information: ClientInformation::default(),
dead: false, dead: false,
players: HashMap::new(), players: HashMap::new(),
world, world,
world_name: Arc::new(RwLock::new(None)),
tx, tx,
world_name: None, world_name: None,
} }
@ -146,590 +140,6 @@ impl LocalPlayer {
Self::send_event(Event::Tick, &tx); 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)] #[derive(Error, Debug)]

View file

@ -7,7 +7,7 @@ mod world_collisions;
use azalea_core::{Axis, Vec3, AABB, EPSILON}; use azalea_core::{Axis, Vec3, AABB, EPSILON};
use azalea_world::{ use azalea_world::{
entity::{self, EntityId}, entity::{self, EntityId},
MoveEntityError, WeakWorld, MoveEntityError, World,
}; };
pub use blocks::BlockWithShape; pub use blocks::BlockWithShape;
pub use discrete_voxel_shape::*; pub use discrete_voxel_shape::*;
@ -53,7 +53,7 @@ pub enum MoverType {
// return var4; // 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; let entity_bounding_box = physics.bounding_box;
// TODO: get_entity_collisions // TODO: get_entity_collisions
// let entity_collisions = world.get_entity_collisions(self, // let entity_collisions = world.get_entity_collisions(self,
@ -75,7 +75,7 @@ pub fn move_colliding(
_mover_type: &MoverType, _mover_type: &MoverType,
movement: &Vec3, movement: &Vec3,
entity_id: EntityId, entity_id: EntityId,
world: &WeakWorld, world: &World,
position: &mut entity::Position, position: &mut entity::Position,
physics: &mut entity::Physics, physics: &mut entity::Physics,
) -> Result<(), MoveEntityError> { ) -> Result<(), MoveEntityError> {
@ -188,7 +188,7 @@ pub fn move_colliding(
fn collide_bounding_box( fn collide_bounding_box(
movement: &Vec3, movement: &Vec3,
entity_bounding_box: &AABB, entity_bounding_box: &AABB,
world: &WeakWorld, world: &World,
entity_collisions: Vec<VoxelShape>, entity_collisions: Vec<VoxelShape>,
) -> Vec3 { ) -> Vec3 {
let mut collision_boxes: Vec<VoxelShape> = Vec::with_capacity(entity_collisions.len() + 1); let mut collision_boxes: Vec<VoxelShape> = Vec::with_capacity(entity_collisions.len() + 1);

View file

@ -2,16 +2,16 @@ use super::Shapes;
use crate::collision::{BlockWithShape, VoxelShape, AABB}; use crate::collision::{BlockWithShape, VoxelShape, AABB};
use azalea_block::BlockState; use azalea_block::BlockState;
use azalea_core::{ChunkPos, ChunkSectionPos, Cursor3d, CursorIterationType, EPSILON}; 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 parking_lot::RwLock;
use std::sync::Arc; 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) BlockCollisions::new(world, aabb)
} }
pub struct BlockCollisions<'a> { pub struct BlockCollisions<'a> {
pub world: &'a WeakWorld, pub world: &'a World,
pub aabb: AABB, pub aabb: AABB,
pub entity_shape: VoxelShape, pub entity_shape: VoxelShape,
pub cursor: Cursor3d, pub cursor: Cursor3d,
@ -19,7 +19,7 @@ pub struct BlockCollisions<'a> {
} }
impl<'a> 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_x = (aabb.min_x - EPSILON) as i32 - 1;
let origin_y = (aabb.min_y - EPSILON) as i32 - 1; let origin_y = (aabb.min_y - EPSILON) as i32 - 1;
let origin_z = (aabb.min_z - EPSILON) as i32 - 1; let origin_z = (aabb.min_z - EPSILON) as i32 - 1;

View file

@ -6,7 +6,7 @@ use azalea_block::{Block, BlockState};
use azalea_core::{BlockPos, Vec3}; use azalea_core::{BlockPos, Vec3};
use azalea_world::{ use azalea_world::{
entity::{self, EntityId}, entity::{self, EntityId},
WeakWorld, World,
}; };
use collision::{move_colliding, MoverType}; use collision::{move_colliding, MoverType};
use std::ops::Deref; use std::ops::Deref;
@ -15,7 +15,7 @@ use std::ops::Deref;
/// gravity, collisions, and some other stuff. /// gravity, collisions, and some other stuff.
fn travel( fn travel(
entity_id: EntityId, entity_id: EntityId,
world: &WeakWorld, world: &World,
physics: &mut entity::Physics, physics: &mut entity::Physics,
position: &mut entity::Position, position: &mut entity::Position,
attributes: &entity::Attributes, attributes: &entity::Attributes,
@ -84,7 +84,7 @@ fn travel(
/// stuff. /// stuff.
pub fn ai_step( pub fn ai_step(
entity_id: EntityId, entity_id: EntityId,
world: &WeakWorld, world: &World,
physics: &mut entity::Physics, physics: &mut entity::Physics,
position: &mut entity::Position, position: &mut entity::Position,
sprinting: &entity::metadata::Sprinting, sprinting: &entity::metadata::Sprinting,
@ -132,7 +132,7 @@ pub fn ai_step(
} }
fn jump_from_ground( fn jump_from_ground(
world: &WeakWorld, world: &World,
physics: &mut entity::Physics, physics: &mut entity::Physics,
position: &entity::Position, position: &entity::Position,
sprinting: &entity::metadata::Sprinting, sprinting: &entity::metadata::Sprinting,
@ -169,7 +169,7 @@ fn handle_relative_friction_and_calculate_movement(
acceleration: &Vec3, acceleration: &Vec3,
block_friction: f32, block_friction: f32,
entity_id: EntityId, entity_id: EntityId,
world: &WeakWorld, world: &World,
physics: &mut entity::Physics, physics: &mut entity::Physics,
position: &mut entity::Position, position: &mut entity::Position,
attributes: &entity::Attributes, 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 /// Returns the what the entity's jump should be multiplied by based on the
/// block they're standing on. /// 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_at_pos = world.get_block_state(&position.into());
let block_below = world.get_block_state(&get_block_pos_below_that_affects_movement(position)); let block_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() { // public double getJumpBoostPower() {
// return this.hasEffect(MobEffects.JUMP) ? (double)(0.1F * // return this.hasEffect(MobEffects.JUMP) ? (double)(0.1F *
// (float)(this.getEffect(MobEffects.JUMP).getAmplifier() + 1)) : 0.0D; } // (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) 0.42 * block_jump_factor(world, position)
} }

View file

@ -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-core = {path = "../azalea-core", version = "^0.5.0", features = ["bevy_ecs"]}
azalea-nbt = {path = "../azalea-nbt", version = "^0.5.0"} azalea-nbt = {path = "../azalea-nbt", version = "^0.5.0"}
azalea-registry = {path = "../azalea-registry", 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} bevy_ecs = {version = "0.9.1", default-features = false}
derive_more = {version = "0.99.17", features = ["deref", "deref_mut"]} derive_more = {version = "0.99.17", features = ["deref", "deref_mut"]}
enum-as-inner = "0.5.1" enum-as-inner = "0.5.1"

View file

@ -1,4 +1,3 @@
use crate::WeakWorld;
use azalea_core::ResourceLocation; use azalea_core::ResourceLocation;
use bevy_ecs::system::Resource; use bevy_ecs::system::Resource;
use log::error; use log::error;
@ -8,11 +7,25 @@ use std::{
sync::{Arc, Weak}, sync::{Arc, Weak},
}; };
use crate::World;
/// A container of [`WeakWorld`]s. Worlds are stored as a Weak pointer here, so /// 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. /// if no clients are using a world it will be forgotten.
#[derive(Default, Resource)] #[derive(Default, Resource)]
pub struct WeakWorldContainer { 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. /// The ECS world that contains all of the entities in all of the worlds.
pub ecs: Arc<Mutex<bevy_ecs::world::World>>, pub ecs: Arc<Mutex<bevy_ecs::world::World>>,
@ -27,14 +40,14 @@ impl WeakWorldContainer {
} }
/// Get a world from the container. /// 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()) self.worlds.get(name).and_then(|world| world.upgrade())
} }
/// Add an empty world to the container (or not if it already exists) and /// Add an empty world to the container (or not if it already exists) and
/// returns a strong reference to the world. /// returns a strong reference to the world.
#[must_use = "the world will be immediately forgotten if unused"] #[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 let Some(existing) = self.worlds.get(&name).and_then(|world| world.upgrade()) {
if existing.height() != height { if existing.height() != height {
error!( error!(
@ -52,7 +65,7 @@ impl WeakWorldContainer {
} }
existing existing
} else { } 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)); self.worlds.insert(name, Arc::downgrade(&world));
world world
} }

View file

@ -4,111 +4,48 @@ mod dimensions;
pub mod metadata; pub mod metadata;
use self::{attributes::AttributeInstance, metadata::UpdateMetadataError}; use self::{attributes::AttributeInstance, metadata::UpdateMetadataError};
use crate::WeakWorld;
pub use attributes::Attributes; pub use attributes::Attributes;
use azalea_block::BlockState; use azalea_block::BlockState;
use azalea_core::{BlockPos, ChunkPos, Vec3, AABB}; 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::*; pub use data::*;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
pub use dimensions::EntityDimensions; pub use dimensions::EntityDimensions;
use std::fmt::{Debug, Display, Formatter}; use std::fmt::{Debug, Display, Formatter};
use uuid::Uuid; use uuid::Uuid;
/// An entity ID that's used by ECS library. /// A lightweight identifier of an entity.
pub type EcsEntityId = bevy_ecs::entity::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. /// An entity ID used by Minecraft. These are not guaranteed to be unique in
#[derive(Deref, Eq, PartialEq, DerefMut, Copy, Clone)] /// shared worlds, that's what [`Entity`] is for.
pub struct EntityId(pub u32); #[derive(Component, Copy, Clone, Debug, PartialEq, Eq, Deref, DerefMut)]
impl From<EntityId> for EcsEntityId { pub struct MinecraftEntityId(pub u32);
// bevy_ecs `Entity`s also store the "generation" which adds another 32 bits, impl std::hash::Hash for MinecraftEntityId {
// 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 {
fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) { fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
hasher.write_u32(self.0) hasher.write_u32(self.0)
} }
} }
impl nohash_hasher::IsEnabled for EntityId {} impl nohash_hasher::IsEnabled for MinecraftEntityId {}
// /// 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()
// }
// }
/// Sets the position of the entity. This doesn't update the cache in /// Sets the position of the entity. This doesn't update the cache in
/// azalea-world, and should only be used within azalea-world! /// azalea-world, and should only be used within azalea-world!
/// ///
/// # Safety /// # Safety
/// Cached position in the world must be updated. /// Cached position in the world must be updated.
pub unsafe fn move_unchecked(pos: &mut Position, physics: &mut Physics, new_pos: Vec3) { pub fn update_bounding_box(query: Query<(&mut Position, &Physics), Changed<Position>>) {
*pos = Position(new_pos); for (mut position, physics) in query.iter_mut() {
let bounding_box = make_bounding_box(&pos, &physics); let bounding_box = physics.dimensions.make_bounding_box(&position);
physics.bounding_box = bounding_box; physics.bounding_box = bounding_box;
}
} }
pub fn set_rotation(physics: &mut Physics, y_rot: f32, x_rot: f32) { pub fn set_rotation(physics: &mut Physics, y_rot: f32, x_rot: f32) {
@ -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. /// 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) on_pos(world, position, physics, 0.2)
} }
@ -174,7 +111,7 @@ pub fn on_pos_legacy(world: &WeakWorld, position: &Position, physics: &Physics)
// } // }
// } // }
// return var5; // 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 x = pos.x.floor() as i32;
let y = (pos.y - offset as f64).floor() as i32; let y = (pos.y - offset as f64).floor() as i32;
let z = pos.z.floor() as i32; let z = pos.z.floor() as i32;
@ -207,23 +144,45 @@ pub struct EntityUuid(Uuid);
/// world.move_entity /// world.move_entity
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Deref, DerefMut)] #[derive(Component, Clone, Copy, Debug, Default, PartialEq, Deref, DerefMut)]
pub struct Position(Vec3); pub struct Position(Vec3);
impl From<&Position> for ChunkPos { impl From<Position> for ChunkPos {
fn from(value: &Position) -> Self { fn from(value: Position) -> Self {
ChunkPos::from(&value.0) ChunkPos::from(&value.0)
} }
} }
impl From<&Position> for BlockPos { impl From<Position> for BlockPos {
fn from(value: &Position) -> Self { fn from(value: Position) -> Self {
BlockPos::from(&value.0) 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 /// The physics data relating to the entity, such as position, velocity, and
/// bounding box. /// bounding box.
#[derive(Debug, Component)] #[derive(Debug, Component)]
pub struct Physics { pub struct Physics {
/// The position of the entity last tick.
pub last_pos: Vec3,
pub delta: Vec3, pub delta: Vec3,
/// X acceleration. /// X acceleration.
@ -269,6 +228,7 @@ pub struct EntityBundle {
pub kind: EntityKind, pub kind: EntityKind,
pub uuid: EntityUuid, pub uuid: EntityUuid,
pub position: Position, pub position: Position,
pub last_position: LastPosition,
pub physics: Physics, pub physics: Physics,
pub attributes: Attributes, pub attributes: Attributes,
} }
@ -284,8 +244,8 @@ impl EntityBundle {
kind: EntityKind(kind), kind: EntityKind(kind),
uuid: EntityUuid(uuid), uuid: EntityUuid(uuid),
position: Position(pos), position: Position(pos),
last_position: Position(pos),
physics: Physics { physics: Physics {
last_pos: pos,
delta: Vec3::default(), delta: Vec3::default(),
xxa: 0., xxa: 0.,
@ -326,24 +286,24 @@ pub struct PlayerBundle {
pub metadata: metadata::PlayerMetadataBundle, pub metadata: metadata::PlayerMetadataBundle,
} }
#[cfg(test)] // #[cfg(test)]
mod tests { // mod tests {
use super::*; // use super::*;
use crate::PartialWorld; // use crate::PartialWorld;
#[test] // #[test]
fn from_mut_entity_to_ref_entity() { // fn from_mut_entity_to_ref_entity() {
let mut world = PartialWorld::default(); // let mut world = PartialWorld::default();
let uuid = Uuid::from_u128(100); // let uuid = Uuid::from_u128(100);
world.add_entity( // world.add_entity(
0, // 0,
EntityData::new( // EntityData::new(
uuid, // uuid,
Vec3::default(), // Vec3::default(),
EntityMetadata::Player(metadata::Player::default()), // EntityMetadata::Player(metadata::Player::default()),
), // ),
); // );
let entity: Entity = world.entity_mut(0).unwrap(); // let entity: Entity = world.entity_mut(0).unwrap();
assert_eq!(entity.uuid, uuid); // assert_eq!(entity.uuid, uuid);
} // }
} // }

View file

@ -1,20 +1,34 @@
use crate::entity::{self, new_entity, EcsEntityId, EntityId, EntityUuid, Position}; use crate::entity::{self, Entity, EntityUuid, MinecraftEntityId, Position};
use azalea_core::{ChunkPos, Vec3}; use azalea_core::{ChunkPos, ResourceLocation, Vec3};
use bevy_app::{App, CoreStage, Plugin};
use bevy_ecs::{ use bevy_ecs::{
query::{QueryEntityError, QueryState, ReadOnlyWorldQuery, WorldQuery}, query::{Changed, QueryEntityError, QueryState, ReadOnlyWorldQuery, WorldQuery},
schedule::SystemSet,
system::{Query, ResMut, Resource},
world::{EntityMut, EntityRef}, world::{EntityMut, EntityRef},
}; };
use log::warn; use log::warn;
use nohash_hasher::{IntMap, IntSet}; use nohash_hasher::{IntMap, IntSet};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use parking_lot::RwLock; use parking_lot::{Mutex, RwLock};
use std::{ use std::{
collections::HashMap, collections::{HashMap, HashSet},
fmt::Debug, fmt::Debug,
sync::{Arc, Weak}, sync::{Arc, Weak},
}; };
use uuid::Uuid; 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) // How entity updates are processed (to avoid issues with shared worlds)
// - each bot contains a map of { entity id: updates received } // - each bot contains a map of { entity id: updates received }
// - the shared world also contains a canonical "true" updates received for each // - 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()`. /// You can access the shared storage with `world.shared.read()`.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct PartialEntityInfos { 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 /// The entity id of the player that owns this partial world. This will
/// make [`PartialWorld::entity_mut`] pretend the entity doesn't exist so /// make [`PartialWorld::entity_mut`] pretend the entity doesn't exist so
/// it doesn't get modified from outside sources. /// it doesn't get modified from outside sources.
/// ///
/// [`PartialWorld::entity_mut`]: crate::PartialWorld::entity_mut /// [`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 /// A counter for each entity that tracks how many updates we've observed
/// for it. /// for it.
/// ///
/// This is used for shared worlds (i.e. swarms), to make sure we don't /// This is used for shared worlds (i.e. swarms), to make sure we don't
/// update entities twice on accident. /// 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. /// A set of all the entity ids in render distance.
pub(crate) loaded_entity_ids: IntSet<EntityId>, pub(crate) loaded_entity_ids: IntSet<MinecraftEntityId>,
}
#[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>,
} }
impl PartialEntityInfos { impl PartialEntityInfos {
pub fn new(shared: Arc<RwLock<WeakEntityInfos>>, owner_entity_id: Option<EntityId>) -> Self { pub fn new(owner_entity: Option<Entity>, entity_infos: &mut EntityInfos) -> Self {
if let Some(owner_entity_id) = owner_entity_id { if let Some(owner_entity) = owner_entity {
shared.write().updates_received.insert(owner_entity_id, 0); entity_infos.updates_received.insert(owner_entity, 0);
} }
Self { Self {
shared, owner_entity,
owner_entity_id,
updates_received: IntMap::default(), updates_received: IntMap::default(),
loaded_entity_ids: IntSet::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 /// If you want to check whether the entity is in the shared storage, use
/// [`WeakEntityStorage::contains_id`]. /// [`WeakEntityStorage::contains_id`].
#[inline] #[inline]
pub fn limited_contains_id(&self, id: &EntityId) -> bool { pub fn limited_contains_id(&self, id: MinecraftEntityId) -> bool {
self.loaded_entity_ids.contains(id) self.loaded_entity_ids.contains(&id)
} }
/// Get an [`EntityId`] from this u32 entity ID if the entity is being /// Get an [`Entity`] from the given [`MinecraftEntityId`] (which is just a
/// loaded by this storage. /// u32 internally) 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.
#[inline] #[inline]
pub fn limited_get_by_id(&self, id: u32) -> Option<EntityId> { pub fn limited_get_by_id(
if self.limited_contains_id(&EntityId(id)) { &self,
Some(EntityId(id)) 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 { } else {
None None
} }
@ -112,91 +115,97 @@ impl PartialEntityInfos {
/// we WILL update it if it's true. Don't call this unless you actually /// 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 /// got an entity update that all other clients within render distance
/// will get too. /// 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 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; let can_update = this_client_updates_received == shared_updates_received;
if can_update { if can_update {
let new_updates_received = this_client_updates_received.unwrap_or(0) + 1; let new_updates_received = this_client_updates_received.unwrap_or(0) + 1;
self.updates_received.insert(id, new_updates_received); self.updates_received.insert(id, new_updates_received);
self.shared entity_infos
.write()
.updates_received .updates_received
.insert(id, new_updates_received); .insert(entity, new_updates_received);
true true
} else { } else {
false 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 /// This is useful so functions that return an IntSet of entity ids can return a
/// reference to nothing. /// 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 { pub fn new() -> Self {
Self { Self {
entity_reference_count: IntMap::default(), entity_reference_count: HashMap::default(),
ids_by_chunk: HashMap::default(), entities_by_world_name_and_chunk: HashMap::default(),
id_by_uuid: HashMap::default(), entity_by_uuid: HashMap::default(),
updates_received: IntMap::default(), updates_received: HashMap::default(),
minecraft_entity_ids_to_azalea_entities: HashMap::default(),
} }
} }
/// Call this if a [`PartialEntityStorage`] just removed an entity. /// Call this if a [`PartialEntityStorage`] just removed an entity.
/// ///
/// It'll /// It'll decrease the reference count and remove the entity from the
/// decrease the reference count and remove the entity from the storage if /// storage if there's no more references to it.
/// there's no more references to it.
/// ///
/// Returns whether the entity was removed. /// Returns whether the entity was removed.
pub fn remove_entity_if_unused(&mut self, id: EntityId, uuid: Uuid, chunk: ChunkPos) -> bool { 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(&id) { if let Some(count) = self.entity_reference_count.get_mut(&entity) {
*count -= 1; *count -= 1;
if *count == 0 { if *count == 0 {
self.entity_reference_count.remove(&id); self.entity_reference_count.remove(&entity);
return true; return true;
} }
} else { } 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; return false;
} }
if self.ids_by_chunk.remove(&chunk).is_none() { if self.entities_by_chunk.remove(&chunk).is_none() {
warn!("Tried to remove entity with id {id} from chunk {chunk:?} but it was not found."); warn!("Tried to remove entity from chunk {chunk:?} but it was not found.");
} }
if self.id_by_uuid.remove(&uuid).is_none() { if self.entity_by_uuid.remove(&uuid).is_none() {
warn!("Tried to remove entity with id {id} from uuid {uuid:?} but it was not found."); 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 // if this happens it means we weren't tracking the updates_received for the
// client (bad) // client (bad)
warn!( warn!("Tried to remove entity from updates_received but it was not found.");
"Tried to remove entity with id {id} from updates_received but it was not found."
);
} }
true true
} }
@ -204,105 +213,97 @@ impl WeakEntityInfos {
/// Remove a chunk from the storage if the entities in it have no strong /// Remove a chunk from the storage if the entities in it have no strong
/// references left. /// references left.
pub fn remove_chunk_if_unused(&mut self, chunk: &ChunkPos) { 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() { 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] #[inline]
pub fn contains_entity(&self, id: EntityId) -> bool { pub fn contains_entity(&self, id: Entity) -> bool {
self.entity_reference_count.contains_key(&id) self.entity_reference_count.contains_key(&id)
} }
/// Returns a set of entities in the given chunk. /// Returns a set of entities in the given chunk.
pub fn entities_in_chunk<F>(&self, chunk: &ChunkPos) -> &IntSet<EntityId> { pub fn entities_in_chunk<F>(&self, chunk: &ChunkPos) -> &HashSet<Entity> {
self.ids_by_chunk self.entities_by_chunk
.get(chunk) .get(chunk)
.unwrap_or(&EMPTY_ENTITY_ID_INTSET) .unwrap_or(&EMPTY_ENTITY_ID_INTSET)
} }
pub fn id_by_uuid(&self, uuid: &Uuid) -> Option<&EntityId> { pub fn entity_by_uuid(&self, uuid: &Uuid) -> Option<&Entity> {
self.id_by_uuid.get(uuid) self.entity_by_uuid.get(uuid)
} }
}
/// Move an entity from its old chunk to a new chunk. /// Update the chunk position indexes in [`EntityInfos`].
#[inline] fn update_entity_chunk_positions(
pub fn update_entity_chunk( query: Query<
&mut self, (
entity_id: EntityId, Entity,
old_chunk: &ChunkPos, &entity::Position,
new_chunk: &ChunkPos, &mut entity::LastPosition,
) { &mut entity::Physics,
if let Some(entities) = self.ids_by_chunk.get_mut(old_chunk) { ),
entities.remove(&entity_id); Changed<entity::Position>,
} >,
self.ids_by_chunk entity_infos: ResMut<EntityInfos>,
.entry(*new_chunk) ) {
.or_default() for (entity, pos, last_pos, mut physics) in query.iter_mut() {
.insert(entity_id); let old_chunk = ChunkPos::from(*last_pos);
} let new_chunk = ChunkPos::from(*pos);
/// 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 { if old_chunk != new_chunk {
self.update_entity_chunk(entity_id, &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 WeakEntityInfos { impl Debug for EntityInfos {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WeakEntityStorage") f.debug_struct("EntityInfos").finish()
// .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()
} }
} }
#[cfg(test)] // #[cfg(test)]
mod tests { // mod tests {
use crate::entity::metadata; // use crate::entity::metadata;
use super::*; // use super::*;
use azalea_core::Vec3; // use azalea_core::Vec3;
#[test] // #[test]
fn test_store_entity() { // fn test_store_entity() {
let mut storage = PartialEntityInfos::default(); // let mut storage = PartialEntityInfos::default();
assert!(storage.limited_get_by_id(0).is_none()); // assert!(storage.limited_get_by_id(0).is_none());
assert!(storage.shared.read().get_by_id(0).is_none()); // assert!(storage.shared.read().get_by_id(0).is_none());
let uuid = Uuid::from_u128(100); // let uuid = Uuid::from_u128(100);
storage.insert( // storage.insert(
0, // 0,
EntityData::new( // EntityData::new(
uuid, // uuid,
Vec3::default(), // Vec3::default(),
EntityMetadata::Player(metadata::Player::default()), // EntityMetadata::Player(metadata::Player::default()),
), // ),
); // );
assert_eq!(storage.limited_get_by_id(0).unwrap().uuid, uuid); // assert_eq!(storage.limited_get_by_id(0).unwrap().uuid, uuid);
assert_eq!(storage.shared.read().get_by_id(0).unwrap().uuid, uuid); // assert_eq!(storage.shared.read().get_by_id(0).unwrap().uuid, uuid);
storage.remove_by_id(0); // storage.remove_by_id(0);
assert!(storage.limited_get_by_id(0).is_none()); // assert!(storage.limited_get_by_id(0).is_none());
assert!(storage.shared.read().get_by_id(0).is_none()); // assert!(storage.shared.read().get_by_id(0).is_none());
} // }
} // }

View file

@ -15,7 +15,7 @@ use std::backtrace::Backtrace;
pub use bit_storage::BitStorage; pub use bit_storage::BitStorage;
pub use chunk_storage::{Chunk, ChunkStorage, PartialChunkStorage, WeakChunkStorage}; pub use chunk_storage::{Chunk, ChunkStorage, PartialChunkStorage, WeakChunkStorage};
pub use container::*; pub use container::*;
pub use entity_info::{PartialEntityInfos, WeakEntityInfos}; pub use entity_info::{EntityInfos, PartialEntityInfos};
use thiserror::Error; use thiserror::Error;
pub use world::*; pub use world::*;

View file

@ -1,12 +1,10 @@
use crate::{ use crate::{
entity::{self, move_unchecked, EntityId}, entity::{self, Entity, MinecraftEntityId},
Chunk, MoveEntityError, PartialChunkStorage, PartialEntityInfos, WeakChunkStorage, Chunk, EntityInfos, MoveEntityError, PartialChunkStorage, PartialEntityInfos, WeakChunkStorage,
WeakEntityInfos,
}; };
use azalea_block::BlockState; use azalea_block::BlockState;
use azalea_buf::BufReadError; use azalea_buf::BufReadError;
use azalea_core::{BlockPos, ChunkPos, PositionDelta8, Vec3}; use azalea_core::{BlockPos, ChunkPos, PositionDelta8, Vec3};
use bevy_ecs::query::{QueryState, WorldQuery};
use log::warn; use log::warn;
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use std::{backtrace::Backtrace, fmt::Debug}; use std::{backtrace::Backtrace, fmt::Debug};
@ -24,7 +22,7 @@ use uuid::Uuid;
pub struct PartialWorld { pub struct PartialWorld {
// we just need to keep a strong reference to `shared` so it doesn't get // 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 // dropped, we don't need to do anything with it
pub shared: Arc<WeakWorld>, pub shared: Arc<World>,
pub chunks: PartialChunkStorage, pub chunks: PartialChunkStorage,
pub entity_infos: PartialEntityInfos, pub entity_infos: PartialEntityInfos,
@ -33,13 +31,14 @@ pub struct PartialWorld {
impl PartialWorld { impl PartialWorld {
pub fn new( pub fn new(
chunk_radius: u32, chunk_radius: u32,
shared: Arc<WeakWorld>, shared: Arc<World>,
owner_entity_id: Option<EntityId>, owner_entity: Option<Entity>,
entity_infos: &mut EntityInfos,
) -> Self { ) -> Self {
PartialWorld { PartialWorld {
shared: shared.clone(), shared: shared.clone(),
chunks: PartialChunkStorage::new(chunk_radius, shared.chunks.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 /// Only call this if you're actually updating the entity, otherwise it'll
/// cause the update tracker to get out of sync. /// 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) // 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 false
} else { } else {
true true
@ -218,66 +219,59 @@ impl PartialWorld {
} }
} }
/// A world where the chunks are stored as weak pointers. This is used for // /// A world where the chunks are stored as weak pointers. This is used for
/// shared worlds. // /// shared worlds.
#[derive(Default, Debug)] // #[derive(Default, Debug)]
pub struct WeakWorld { // pub struct World {
pub chunks: Arc<RwLock<WeakChunkStorage>>, // pub chunks: Arc<RwLock<WeakChunkStorage>>,
pub(crate) entity_infos: Arc<RwLock<WeakEntityInfos>>, // }
/// A reference to the ECS world that contains all of the entities in all of // impl World {
/// the worlds. // pub fn new(height: u32, min_y: i32) -> Self {
pub(crate) global_ecs: Arc<Mutex<bevy_ecs::world::World>>, // World {
} // chunks: Arc::new(RwLock::new(WeakChunkStorage::new(height,
// min_y))), }
// }
impl WeakWorld { // /// Read the total height of the world. You can add this to
pub fn new(height: u32, min_y: i32, global_ecs: Arc<Mutex<bevy_ecs::world::World>>) -> Self { // [`Self::min_y`] /// to get the highest possible y coordinate a block can
WeakWorld { // be placed at. pub fn height(&self) -> u32 {
chunks: Arc::new(RwLock::new(WeakChunkStorage::new(height, min_y))), // self.chunks.read().height
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`] // /// Get the lowest possible y coordinate a block can be placed at.
/// to get the highest possible y coordinate a block can be placed at. // pub fn min_y(&self) -> i32 {
pub fn height(&self) -> u32 { // self.chunks.read().min_y
self.chunks.read().height // }
}
/// Get the lowest possible y coordinate a block can be placed at. // pub fn contains_entity(&self, id: EntityId) -> bool {
pub fn min_y(&self) -> i32 { // self.entity_infos.read().contains_entity(id)
self.chunks.read().min_y // }
}
pub fn contains_entity(&self, id: EntityId) -> bool { // pub fn id_by_uuid(&self, uuid: &Uuid) -> Option<EntityId> {
self.entity_infos.read().contains_entity(id) // self.entity_infos.read().id_by_uuid(uuid).copied()
} // }
pub fn id_by_uuid(&self, uuid: &Uuid) -> Option<EntityId> { // pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> {
self.entity_infos.read().id_by_uuid(uuid).copied() // self.chunks.read().get_block_state(pos)
} // }
pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> { // pub fn get_chunk(&self, pos: &ChunkPos) -> Option<Arc<RwLock<Chunk>>> {
self.chunks.read().get_block_state(pos) // self.chunks.read().get(pos)
} // }
pub fn get_chunk(&self, pos: &ChunkPos) -> Option<Arc<RwLock<Chunk>>> { // pub fn set_entity_pos(
self.chunks.read().get(pos) // &self,
} // entity_id: EntityId,
// new_pos: Vec3,
pub fn set_entity_pos( // pos: &mut entity::Position,
&self, // physics: &mut entity::Physics,
entity_id: EntityId, // ) {
new_pos: Vec3, // self.entity_infos
pos: &mut entity::Position, // .write()
physics: &mut entity::Physics, // .set_entity_pos(entity_id, new_pos, pos, physics);
) { // }
self.entity_infos // }
.write()
.set_entity_pos(entity_id, new_pos, pos, physics);
}
}
impl Debug for PartialWorld { impl Debug for PartialWorld {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
@ -296,7 +290,7 @@ impl Default for PartialWorld {
let chunk_storage = PartialChunkStorage::default(); let chunk_storage = PartialChunkStorage::default();
let entity_storage = PartialEntityInfos::default(); let entity_storage = PartialEntityInfos::default();
Self { Self {
shared: Arc::new(WeakWorld { shared: Arc::new(World {
chunks: chunk_storage.shared.clone(), chunks: chunk_storage.shared.clone(),
entity_infos: entity_storage.shared.clone(), entity_infos: entity_storage.shared.clone(),
global_ecs: Arc::new(Mutex::new(bevy_ecs::world::World::default())), global_ecs: Arc::new(Mutex::new(bevy_ecs::world::World::default())),

View file

@ -1,10 +1,10 @@
use super::{Node, VerticalVel}; use super::{Node, VerticalVel};
use azalea_core::{BlockPos, CardinalDirection}; use azalea_core::{BlockPos, CardinalDirection};
use azalea_physics::collision::{self, BlockWithShape}; use azalea_physics::collision::{self, BlockWithShape};
use azalea_world::WeakWorld; use azalea_world::World;
/// whether this block is passable /// 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) { if let Some(block) = world.get_block_state(pos) {
block.shape() == &collision::empty_shape() block.shape() == &collision::empty_shape()
} else { } 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) /// 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) { if let Some(block) = world.get_block_state(pos) {
block.shape() == &collision::block_shape() block.shape() == &collision::block_shape()
} else { } else {
@ -22,14 +22,14 @@ fn is_block_solid(pos: &BlockPos, world: &WeakWorld) -> bool {
} }
/// Whether this block and the block above are passable /// 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) 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, /// Whether we can stand in this position. Checks if the block below is solid,
/// and that the two blocks above that are passable. /// 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) 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; const WALK_ONE_BLOCK_COST: f32 = 1.0;
pub trait Move { 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 /// Returns by how much the entity's position should be changed when this
/// move is executed. /// move is executed.
fn offset(&self) -> BlockPos; fn offset(&self) -> BlockPos;
@ -51,7 +51,7 @@ pub trait Move {
pub struct ForwardMove(pub CardinalDirection); pub struct ForwardMove(pub CardinalDirection);
impl Move for ForwardMove { 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) if is_standable(&(node.pos + self.offset()), world)
&& node.vertical_vel == VerticalVel::None && node.vertical_vel == VerticalVel::None
{ {
@ -67,7 +67,7 @@ impl Move for ForwardMove {
pub struct AscendMove(pub CardinalDirection); pub struct AscendMove(pub CardinalDirection);
impl Move for AscendMove { 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 if node.vertical_vel == VerticalVel::None
&& is_block_passable(&node.pos.up(2), world) && is_block_passable(&node.pos.up(2), world)
&& is_standable(&(node.pos + self.offset()), world) && is_standable(&(node.pos + self.offset()), world)
@ -89,7 +89,7 @@ impl Move for AscendMove {
} }
pub struct DescendMove(pub CardinalDirection); pub struct DescendMove(pub CardinalDirection);
impl Move for DescendMove { 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 // check whether 3 blocks vertically forward are passable
if node.vertical_vel == VerticalVel::None if node.vertical_vel == VerticalVel::None
&& is_standable(&(node.pos + self.offset()), world) && is_standable(&(node.pos + self.offset()), world)
@ -112,7 +112,7 @@ impl Move for DescendMove {
} }
pub struct DiagonalMove(pub CardinalDirection); pub struct DiagonalMove(pub CardinalDirection);
impl Move for DiagonalMove { 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 { if node.vertical_vel != VerticalVel::None {
return f32::INFINITY; return f32::INFINITY;
} }