aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2022-08-29 20:41:01 -0500
committerGitHub <noreply@github.com>2022-08-29 20:41:01 -0500
commitf42d630544165d11a544224ac273d6aaf89d8095 (patch)
tree94bd73771ecb582d89a87cdca8e21b2d6573ef12
parent2ea804401f54a45765860201d10d0569d07862ec (diff)
downloadazalea-drasl-f42d630544165d11a544224ac273d6aaf89d8095.tar.xz
Physics (#11)
* Put physics module in azalea-entity * port aabb * add more stuff to PositionXYZ * azalea-physics * important collision things * more physics stuff * backup because i'm about to delete shapes * more shape stuff * CubeVoxelShape * no compile errors??? insane * impl VoxelShape for ArrayVoxelShape * Shapes stuff * collide_x but it doesn't work yet * binary_search * it compiles * Entity has bounding box * Update discrete_voxel_shape.rs * Entity::make_bounding_box * ok i'm about to merge az-entity and az-world might be a terrible idea which is why i'm committing first * ok so i moved entity to world * on_pos and move_entity compiles * add send_position * move collision stuff to collision module in az-physics * dimension is no longer an Option * start trying to do collision for the client * collision works :tada: * start adding palette resizing * get_and_set (pain) * it compiles but probably won't work * add a test * remove printlns * add more tests for palette stuff * ClientboundMoveVec3Packet -> ClientboundMoveEntityPosPacket i think i changed this on accident once * palette resizing works todo: remove the printlns * Remove printlns in palette.rs * fix issues from merge * fixes + work a bit more on physics * Better entities (#19) * well it compiles * add tests to entity storage * add suggestions in azalea-brigadier * this probably causes ub * fix brigadiersuggestions * get rid of entityid * test From<EntityMut> for EntityRef * don't mention other libraries since there's too many * fix warnings * do todos in brigadier suggestions * work on physics * more physics stuff * remove trait feature on az-block i think rust gets confused and compiles the macro without the feature * bump ahash * aes tests in az-crypto * optimize aes's deps * fix crashes * fix section_index for negative numbers and test * fix BlockPos protocol implementation * remove some debug prints * prepare to add ai_step * make ai step work * clippy
-rwxr-xr-xCargo.lock33
-rw-r--r--Cargo.toml10
-rw-r--r--README.md3
-rw-r--r--azalea-block/Cargo.toml4
-rw-r--r--azalea-block/block-macros/src/lib.rs4
-rw-r--r--azalea-block/src/behavior.rs7
-rw-r--r--azalea-block/src/lib.rs2
-rwxr-xr-xazalea-brigadier/src/lib.rs1
-rw-r--r--azalea-brigadier/src/suggestion/mod.rs1
-rw-r--r--azalea-brigadier/src/suggestion/suggestions.rs21
-rw-r--r--azalea-buf/src/definitions.rs2
-rwxr-xr-xazalea-client/Cargo.toml15
-rw-r--r--azalea-client/src/client.rs213
-rw-r--r--azalea-client/src/movement.rs173
-rw-r--r--azalea-client/src/player.rs15
-rw-r--r--azalea-core/README.md3
-rw-r--r--azalea-core/src/aabb.rs447
-rw-r--r--azalea-core/src/bitset.rs58
-rw-r--r--azalea-core/src/block_hit_result.rs9
-rw-r--r--azalea-core/src/cursor3d.rs115
-rw-r--r--azalea-core/src/delta.rs77
-rw-r--r--azalea-core/src/direction.rs72
-rwxr-xr-xazalea-core/src/lib.rs43
-rw-r--r--azalea-core/src/position.rs147
-rw-r--r--azalea-entity/src/lib.rs59
-rwxr-xr-xazalea-nbt/Cargo.toml2
-rw-r--r--azalea-physics/Cargo.toml (renamed from azalea-entity/Cargo.toml)8
-rw-r--r--azalea-physics/README.md3
-rw-r--r--azalea-physics/src/collision/dimension_collisions.rs137
-rw-r--r--azalea-physics/src/collision/discrete_voxel_shape.rs156
-rw-r--r--azalea-physics/src/collision/mod.rs273
-rw-r--r--azalea-physics/src/collision/shape.rs254
-rw-r--r--azalea-physics/src/lib.rs127
-rwxr-xr-xazalea-protocol/Cargo.toml2
-rw-r--r--azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs9
-rw-r--r--azalea-protocol/src/packets/game/clientbound_add_player_packet.rs9
-rw-r--r--azalea-protocol/src/packets/game/clientbound_light_update_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs6
-rw-r--r--azalea-protocol/src/packets/game/clientbound_player_info_packet.rs14
-rw-r--r--azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs2
-rw-r--r--[-rwxr-xr-x]azalea-protocol/src/packets/game/mod.rs8
-rw-r--r--azalea-protocol/src/packets/game/serverbound_interact_packet.rs9
-rw-r--r--azalea-protocol/src/packets/game/serverbound_move_player_pos_packet.rs2
-rw-r--r--azalea-protocol/src/packets/game/serverbound_move_player_pos_rot_packet.rs2
-rw-r--r--azalea-protocol/src/packets/game/serverbound_move_player_rot_packet.rs2
-rw-r--r--azalea-protocol/src/packets/game/serverbound_move_player_status_only_packet.rs2
-rw-r--r--azalea-protocol/src/packets/game/serverbound_use_item_on_packet.rs6
-rw-r--r--azalea-world/Cargo.toml2
-rw-r--r--azalea-world/src/bit_storage.rs51
-rw-r--r--azalea-world/src/chunk_storage.rs (renamed from azalea-world/src/chunk.rs)129
-rw-r--r--azalea-world/src/entity/data.rs (renamed from azalea-entity/src/data.rs)0
-rw-r--r--azalea-world/src/entity/dimensions.rs23
-rw-r--r--azalea-world/src/entity/mod.rs316
-rw-r--r--azalea-world/src/entity_storage.rs (renamed from azalea-world/src/entity.rs)101
-rw-r--r--azalea-world/src/lib.rs86
-rw-r--r--azalea-world/src/palette.rs318
-rwxr-xr-xbot/Cargo.toml1
-rw-r--r--bot/src/main.rs44
58 files changed, 3155 insertions, 486 deletions
diff --git a/Cargo.lock b/Cargo.lock
index af1f9712..97bcb5b3 100755
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -21,10 +21,11 @@ dependencies = [
[[package]]
name = "ahash"
-version = "0.7.6"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+checksum = "57e6e951cfbb2db8de1828d49073a113a29fd7117b1596caa781a258c7e38d72"
dependencies = [
+ "cfg-if",
"getrandom",
"once_cell",
"version_check",
@@ -136,9 +137,10 @@ version = "0.1.0"
dependencies = [
"anyhow",
"azalea-auth",
+ "azalea-block",
"azalea-core",
"azalea-crypto",
- "azalea-entity",
+ "azalea-physics",
"azalea-protocol",
"azalea-world",
"thiserror",
@@ -172,17 +174,6 @@ dependencies = [
]
[[package]]
-name = "azalea-entity"
-version = "0.1.0"
-dependencies = [
- "azalea-buf",
- "azalea-chat",
- "azalea-core",
- "azalea-nbt",
- "uuid",
-]
-
-[[package]]
name = "azalea-language"
version = "0.1.0"
dependencies = [
@@ -205,6 +196,15 @@ dependencies = [
]
[[package]]
+name = "azalea-physics"
+version = "0.1.0"
+dependencies = [
+ "azalea-block",
+ "azalea-core",
+ "azalea-world",
+]
+
+[[package]]
name = "azalea-protocol"
version = "0.1.0"
dependencies = [
@@ -216,8 +216,8 @@ dependencies = [
"azalea-chat",
"azalea-core",
"azalea-crypto",
- "azalea-entity",
"azalea-nbt",
+ "azalea-world",
"byteorder",
"bytes",
"flate2",
@@ -244,8 +244,8 @@ version = "0.1.0"
dependencies = [
"azalea-block",
"azalea-buf",
+ "azalea-chat",
"azalea-core",
- "azalea-entity",
"azalea-nbt",
"log",
"nohash-hasher",
@@ -283,6 +283,7 @@ version = "0.1.0"
dependencies = [
"azalea-client",
"azalea-core",
+ "azalea-physics",
"azalea-protocol",
"tokio",
"uuid",
diff --git a/Cargo.toml b/Cargo.toml
index 48755155..0ba31604 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,8 +12,8 @@ members = [
"azalea-world",
"azalea-language",
"azalea-block",
- "azalea-entity",
"azalea-buf",
+ "azalea-physics",
"azalea-registry",
]
@@ -29,5 +29,13 @@ opt-level = 3
opt-level = 3
[profile.dev.package.aes]
opt-level = 3
+[profile.dev.package.crypto-common]
+opt-level = 3
+[profile.dev.package.generic-array]
+opt-level = 3
+[profile.dev.package.typenum]
+opt-level = 3
+[profile.dev.package.inout]
+opt-level = 3
[profile.dev.package.flate2]
opt-level = 3
diff --git a/README.md b/README.md
index 52637efe..fd838dc8 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,8 @@ A collection of Rust crates primarily for creating Minecraft bots.
## ⚠️ Azalea is still super unfinished, you probably shouldn't use it
-I named this Azalea because it sounds like a cool word and this is a cool library. This project was heavily inspired by PrismarineJS.
+I named this Azalea because it sounds like a cool word and this is a cool library.
+This project was heavily inspired by [PrismarineJS](https://github.com/PrismarineJS).
## Why
diff --git a/azalea-block/Cargo.toml b/azalea-block/Cargo.toml
index 71d7149f..edeba385 100644
--- a/azalea-block/Cargo.toml
+++ b/azalea-block/Cargo.toml
@@ -9,7 +9,3 @@ version = "0.1.0"
[dependencies]
block-macros = {path = "./block-macros"}
-
-[features]
-default = ["trait"]
-trait = []
diff --git a/azalea-block/block-macros/src/lib.rs b/azalea-block/block-macros/src/lib.rs
index e6585600..ac61912f 100644
--- a/azalea-block/block-macros/src/lib.rs
+++ b/azalea-block/block-macros/src/lib.rs
@@ -426,7 +426,6 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
quote! { BlockState::#block_name_pascal_case }
};
- if cfg!(feature = "trait") {
let block_struct = quote! {
#[derive(Debug)]
pub struct #block_struct_name {
@@ -459,7 +458,6 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
block_structs.extend(block_struct);
}
- }
let last_state_id = (state_id - 1) as u32;
let mut generated = quote! {
@@ -480,7 +478,6 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
}
};
- if cfg!(feature = "trait") {
generated.extend(quote! {
#block_structs
@@ -494,7 +491,6 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
}
}
});
- }
generated.into()
}
diff --git a/azalea-block/src/behavior.rs b/azalea-block/src/behavior.rs
index 949f3bd8..db357632 100644
--- a/azalea-block/src/behavior.rs
+++ b/azalea-block/src/behavior.rs
@@ -1,6 +1,7 @@
#[derive(Default)]
pub struct BlockBehavior {
pub has_collision: bool,
+ pub friction: f32,
}
impl BlockBehavior {
@@ -9,4 +10,10 @@ impl BlockBehavior {
self.has_collision = false;
self
}
+
+ #[inline]
+ pub fn friction(mut self, friction: f32) -> Self {
+ self.friction = friction;
+ self
+ }
}
diff --git a/azalea-block/src/lib.rs b/azalea-block/src/lib.rs
index 9320a2a5..3eb86a90 100644
--- a/azalea-block/src/lib.rs
+++ b/azalea-block/src/lib.rs
@@ -1,8 +1,6 @@
-#[cfg(feature = "trait")]
mod behavior;
mod blocks;
-#[cfg(feature = "trait")]
pub use behavior::BlockBehavior;
pub use blocks::*;
diff --git a/azalea-brigadier/src/lib.rs b/azalea-brigadier/src/lib.rs
index a294eb19..c2ac7e14 100755
--- a/azalea-brigadier/src/lib.rs
+++ b/azalea-brigadier/src/lib.rs
@@ -8,3 +8,4 @@ pub mod modifier;
pub mod parse_results;
pub mod string_reader;
pub mod tree;
+pub mod suggestion;
diff --git a/azalea-brigadier/src/suggestion/mod.rs b/azalea-brigadier/src/suggestion/mod.rs
index ab3a5964..4c9a9547 100644
--- a/azalea-brigadier/src/suggestion/mod.rs
+++ b/azalea-brigadier/src/suggestion/mod.rs
@@ -1,6 +1,7 @@
mod suggestions;
use crate::{context::StringRange, message::Message};
+pub use suggestions::*;
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct Suggestion {
diff --git a/azalea-brigadier/src/suggestion/suggestions.rs b/azalea-brigadier/src/suggestion/suggestions.rs
index 6c325039..1fe361f1 100644
--- a/azalea-brigadier/src/suggestion/suggestions.rs
+++ b/azalea-brigadier/src/suggestion/suggestions.rs
@@ -13,18 +13,18 @@ impl Suggestions {
if input.is_empty() {
return Suggestions::default();
} else if input.len() == 1 {
- return input[0];
+ return input[0].clone();
};
- let texts = HashSet::new();
+ let mut texts = HashSet::new();
for suggestions in input {
- texts.extend(suggestions.suggestions);
+ texts.extend(suggestions.suggestions.clone());
}
- Suggestions::create(command, texts)
+ Suggestions::create(command, &texts)
}
- pub fn create(command: &str, suggestions: &[Suggestions]) {
+ pub fn create(command: &str, suggestions: &HashSet<Suggestion>) -> Self {
if suggestions.is_empty() {
return Suggestions::default();
};
@@ -34,5 +34,16 @@ impl Suggestions {
start = suggestion.range.start().min(start);
end = suggestion.range.end().max(end);
}
+ let range = StringRange::new(start, end);
+ let mut texts = HashSet::new();
+ for suggestion in suggestions {
+ texts.insert(suggestion.expand(command, &range));
+ }
+ let mut sorted: Vec<Suggestion> = texts.into_iter().collect();
+ sorted.sort_by(|a, b| a.text.cmp(&b.text));
+ Suggestions {
+ range,
+ suggestions: sorted,
+ }
}
}
diff --git a/azalea-buf/src/definitions.rs b/azalea-buf/src/definitions.rs
index cfe1bd8a..ab75267b 100644
--- a/azalea-buf/src/definitions.rs
+++ b/azalea-buf/src/definitions.rs
@@ -53,4 +53,4 @@ impl McBufWritable for BitSet {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
self.data.write_into(buf)
}
-}
+} \ No newline at end of file
diff --git a/azalea-client/Cargo.toml b/azalea-client/Cargo.toml
index 50ba09e2..9fa32b75 100755
--- a/azalea-client/Cargo.toml
+++ b/azalea-client/Cargo.toml
@@ -7,12 +7,13 @@ version = "0.1.0"
[dependencies]
anyhow = "1.0.59"
-azalea-auth = {path = "../azalea-auth"}
-azalea-core = {path = "../azalea-core"}
-azalea-crypto = {path = "../azalea-crypto"}
-azalea-entity = {path = "../azalea-entity"}
-azalea-protocol = {path = "../azalea-protocol"}
-azalea-world = {path = "../azalea-world"}
+azalea-auth = { path = "../azalea-auth" }
+azalea-core = { path = "../azalea-core" }
+azalea-crypto = { path = "../azalea-crypto" }
+azalea-physics = { path = "../azalea-physics" }
+azalea-protocol = { path = "../azalea-protocol" }
+azalea-world = { path = "../azalea-world" }
+azalea-block = { path = "../azalea-block" }
thiserror = "^1.0.32"
-tokio = {version = "^1.19.2", features = ["sync"]}
+tokio = { version = "^1.19.2", features = ["sync"] }
uuid = "^1.1.2"
diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs
index fcb624b4..c495bc5c 100644
--- a/azalea-client/src/client.rs
+++ b/azalea-client/src/client.rs
@@ -1,7 +1,7 @@
use crate::{Account, Player};
use azalea_auth::game_profile::GameProfile;
-use azalea_core::{ChunkPos, EntityPos, PositionDelta, PositionDeltaTrait, ResourceLocation};
-use azalea_entity::Entity;
+use azalea_block::BlockState;
+use azalea_core::{ChunkPos, ResourceLocation, Vec3};
use azalea_protocol::{
connect::{Connection, ConnectionError},
packets::{
@@ -11,7 +11,7 @@ use azalea_protocol::{
serverbound_accept_teleportation_packet::ServerboundAcceptTeleportationPacket,
serverbound_custom_payload_packet::ServerboundCustomPayloadPacket,
serverbound_keep_alive_packet::ServerboundKeepAlivePacket,
- serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPacketPosRot,
+ serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket,
ClientboundGamePacket, ServerboundGamePacket,
},
handshake::client_intention_packet::ClientIntentionPacket,
@@ -25,6 +25,7 @@ use azalea_protocol::{
read::ReadPacketError,
resolver, ServerAddress,
};
+use azalea_world::entity::EntityData;
use azalea_world::Dimension;
use std::{
fmt::Debug,
@@ -66,8 +67,10 @@ pub struct Client {
game_profile: GameProfile,
pub conn: Arc<tokio::sync::Mutex<Connection<ClientboundGamePacket, ServerboundGamePacket>>>,
pub player: Arc<Mutex<Player>>,
- pub dimension: Arc<Mutex<Option<Dimension>>>,
- // game_loop
+ pub dimension: Arc<Mutex<Dimension>>,
+
+ /// Minecraft only sends a movement packet either after 20 ticks or if the player moved enough. This is that tick counter.
+ pub position_remainder: u32,
}
/// Whether we should ignore errors when decoding packets.
@@ -181,7 +184,9 @@ impl Client {
game_profile,
conn,
player: Arc::new(Mutex::new(Player::default())),
- dimension: Arc::new(Mutex::new(None)),
+ dimension: Arc::new(Mutex::new(Dimension::default())),
+
+ position_remainder: 0,
};
// just start up the game loop and we're ready!
@@ -298,16 +303,10 @@ impl Client {
let mut dimension_lock = client.dimension.lock().unwrap();
// the 16 here is our render distance
// i'll make this an actual setting later
- *dimension_lock = Some(Dimension::new(16, height, min_y));
+ *dimension_lock = Dimension::new(16, height, min_y);
- let entity =
- Entity::new(p.player_id, client.game_profile.uuid, EntityPos::default());
- dimension_lock
- .as_mut()
- .expect(
- "Dimension doesn't exist! We should've gotten a login packet by now.",
- )
- .add_entity(entity);
+ let entity = EntityData::new(client.game_profile.uuid, Vec3::default());
+ dimension_lock.add_entity(p.player_id, entity);
let mut player_lock = client.player.lock().unwrap();
@@ -368,42 +367,42 @@ impl Client {
println!("Got player position packet {:?}", p);
let (new_pos, y_rot, x_rot) = {
- let player_lock = client.player.lock().unwrap();
- let player_entity_id = player_lock.entity_id;
- drop(player_lock);
+ let player_entity_id = {
+ let player_lock = client.player.lock().unwrap();
+ player_lock.entity_id
+ };
let mut dimension_lock = client.dimension.lock().unwrap();
- let dimension = dimension_lock.as_mut().unwrap();
- let player_entity = dimension
- .mut_entity_by_id(player_entity_id)
+ let mut player_entity = dimension_lock
+ .entity_mut(player_entity_id)
.expect("Player entity doesn't exist");
- let delta_movement = &player_entity.delta;
+ let delta_movement = player_entity.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 {
- player_entity.old_pos.x += p.x;
- (delta_movement.x(), player_entity.pos().x + p.x)
+ player_entity.last_pos.x += p.x;
+ (delta_movement.x, player_entity.pos().x + p.x)
} else {
- player_entity.old_pos.x = p.x;
+ player_entity.last_pos.x = p.x;
(0.0, p.x)
};
let (delta_y, new_pos_y) = if is_y_relative {
- player_entity.old_pos.y += p.y;
- (delta_movement.y(), player_entity.pos().y + p.y)
+ player_entity.last_pos.y += p.y;
+ (delta_movement.y, player_entity.pos().y + p.y)
} else {
- player_entity.old_pos.y = p.y;
+ player_entity.last_pos.y = p.y;
(0.0, p.y)
};
let (delta_z, new_pos_z) = if is_z_relative {
- player_entity.old_pos.z += p.z;
- (delta_movement.z(), player_entity.pos().z + p.z)
+ player_entity.last_pos.z += p.z;
+ (delta_movement.z, player_entity.pos().z + p.z)
} else {
- player_entity.old_pos.z = p.z;
+ player_entity.last_pos.z = p.z;
(0.0, p.z)
};
@@ -416,21 +415,21 @@ impl Client {
x_rot += player_entity.y_rot;
}
- player_entity.delta = PositionDelta {
- xa: delta_x,
- ya: delta_y,
- za: delta_z,
+ player_entity.delta = Vec3 {
+ x: delta_x,
+ y: delta_y,
+ z: delta_z,
};
player_entity.set_rotation(y_rot, x_rot);
// TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means
// so investigate that ig
- let new_pos = EntityPos {
+ let new_pos = Vec3 {
x: new_pos_x,
y: new_pos_y,
z: new_pos_z,
};
- dimension
- .move_entity(player_entity_id, new_pos)
+ dimension_lock
+ .set_entity_pos(player_entity_id, new_pos)
.expect("The player entity should always exist");
(new_pos, y_rot, x_rot)
@@ -442,7 +441,7 @@ impl Client {
.await?;
conn_lock
.write(
- ServerboundMovePlayerPacketPosRot {
+ ServerboundMovePlayerPosRotPacket {
x: new_pos.x,
y: new_pos.y,
z: new_pos.z,
@@ -463,8 +462,6 @@ impl Client {
client
.dimension
.lock()?
- .as_mut()
- .unwrap()
.update_view_center(&ChunkPos::new(p.x, p.z));
}
ClientboundGamePacket::ClientboundLevelChunkWithLightPacket(p) => {
@@ -475,8 +472,6 @@ impl Client {
client
.dimension
.lock()?
- .as_mut()
- .expect("Dimension doesn't exist! We should've gotten a login packet by now.")
.replace_with_packet_data(&pos, &mut p.chunk_data.data.as_slice())
.unwrap();
}
@@ -485,13 +480,8 @@ impl Client {
}
ClientboundGamePacket::ClientboundAddEntityPacket(p) => {
println!("Got add entity packet {:?}", p);
- let entity = Entity::from(p);
- client
- .dimension
- .lock()?
- .as_mut()
- .expect("Dimension doesn't exist! We should've gotten a login packet by now.")
- .add_entity(entity);
+ let entity = EntityData::from(p);
+ client.dimension.lock()?.add_entity(p.id, entity);
}
ClientboundGamePacket::ClientboundSetEntityDataPacket(_p) => {
// println!("Got set entity data packet {:?}", p);
@@ -507,13 +497,8 @@ impl Client {
}
ClientboundGamePacket::ClientboundAddPlayerPacket(p) => {
println!("Got add player packet {:?}", p);
- let entity = Entity::from(p);
- client
- .dimension
- .lock()?
- .as_mut()
- .expect("Dimension doesn't exist! We should've gotten a login packet by now.")
- .add_entity(entity);
+ let entity = EntityData::from(p);
+ client.dimension.lock()?.add_entity(p.id, entity);
}
ClientboundGamePacket::ClientboundInitializeBorderPacket(p) => {
println!("Got initialize border packet {:?}", p);
@@ -535,12 +520,11 @@ impl Client {
}
ClientboundGamePacket::ClientboundTeleportEntityPacket(p) => {
let mut dimension_lock = client.dimension.lock()?;
- let dimension = dimension_lock.as_mut().unwrap();
- dimension
- .move_entity(
+ dimension_lock
+ .set_entity_pos(
p.id,
- EntityPos {
+ Vec3 {
x: p.x,
y: p.y,
z: p.z,
@@ -556,17 +540,15 @@ impl Client {
}
ClientboundGamePacket::ClientboundMoveEntityPosPacket(p) => {
let mut dimension_lock = client.dimension.lock()?;
- let dimension = dimension_lock.as_mut().unwrap();
- dimension
+ dimension_lock
.move_entity_with_delta(p.entity_id, &p.delta)
.map_err(|e| HandleError::Other(e.into()))?;
}
ClientboundGamePacket::ClientboundMoveEntityPosRotPacket(p) => {
let mut dimension_lock = client.dimension.lock()?;
- let dimension = dimension_lock.as_mut().unwrap();
- dimension
+ dimension_lock
.move_entity_with_delta(p.entity_id, &p.delta)
.map_err(|e| HandleError::Other(e.into()))?;
}
@@ -603,6 +585,16 @@ impl Client {
ClientboundGamePacket::ClientboundBlockUpdatePacket(p) => {
println!("Got block update packet {:?}", p);
// TODO: update world
+ let mut dimension = client.dimension.lock()?;
+ // dimension.get_block_state(pos)
+ if let Ok(block_state) = BlockState::try_from(p.block_state) {
+ dimension.set_block_state(&p.pos, block_state);
+ } else {
+ eprintln!(
+ "Non-existent block state for block update packet {}",
+ p.block_state
+ );
+ }
}
ClientboundGamePacket::ClientboundAnimatePacket(p) => {
println!("Got animate packet {:?}", p);
@@ -626,28 +618,107 @@ impl Client {
ClientboundGamePacket::ClientboundUpdateMobEffectPacket(p) => {
println!("Got update mob effect packet {:?}", p);
}
- _ => panic!("Unexpected packet {:?}", packet),
+ ClientboundGamePacket::ClientboundAddExperienceOrbPacket(_) => {}
+ ClientboundGamePacket::ClientboundAwardStatsPacket(_) => {}
+ ClientboundGamePacket::ClientboundBlockChangedAckPacket(_) => {}
+ ClientboundGamePacket::ClientboundBlockDestructionPacket(_) => {}
+ ClientboundGamePacket::ClientboundBlockEntityDataPacket(_) => {}
+ ClientboundGamePacket::ClientboundBlockEventPacket(_) => {}
+ ClientboundGamePacket::ClientboundBossEventPacket(_) => {}
+ ClientboundGamePacket::ClientboundChatPreviewPacket(_) => {}
+ ClientboundGamePacket::ClientboundCommandSuggestionsPacket(_) => {}
+ ClientboundGamePacket::ClientboundContainerSetDataPacket(_) => {}
+ ClientboundGamePacket::ClientboundContainerSetSlotPacket(_) => {}
+ ClientboundGamePacket::ClientboundCooldownPacket(_) => {}
+ ClientboundGamePacket::ClientboundCustomChatCompletionsPacket(_) => {}
+ ClientboundGamePacket::ClientboundCustomSoundPacket(_) => {}
+ ClientboundGamePacket::ClientboundDeleteChatPacket(_) => {}
+ ClientboundGamePacket::ClientboundExplodePacket(_) => {}
+ ClientboundGamePacket::ClientboundForgetLevelChunkPacket(_) => {}
+ ClientboundGamePacket::ClientboundHorseScreenOpenPacket(_) => {}
+ ClientboundGamePacket::ClientboundMapItemDataPacket(_) => {}
+ ClientboundGamePacket::ClientboundMerchantOffersPacket(_) => {}
+ ClientboundGamePacket::ClientboundMoveVehiclePacket(_) => {}
+ ClientboundGamePacket::ClientboundOpenBookPacket(_) => {}
+ ClientboundGamePacket::ClientboundOpenScreenPacket(_) => {}
+ ClientboundGamePacket::ClientboundOpenSignEditorPacket(_) => {}
+ ClientboundGamePacket::ClientboundPingPacket(_) => {}
+ ClientboundGamePacket::ClientboundPlaceGhostRecipePacket(_) => {}
+ ClientboundGamePacket::ClientboundPlayerChatHeaderPacket(_) => {}
+ ClientboundGamePacket::ClientboundPlayerCombatEndPacket(_) => {}
+ ClientboundGamePacket::ClientboundPlayerCombatEnterPacket(_) => {}
+ ClientboundGamePacket::ClientboundPlayerCombatKillPacket(_) => {}
+ ClientboundGamePacket::ClientboundPlayerLookAtPacket(_) => {}
+ ClientboundGamePacket::ClientboundRemoveMobEffectPacket(_) => {}
+ ClientboundGamePacket::ClientboundResourcePackPacket(_) => {}
+ ClientboundGamePacket::ClientboundRespawnPacket(_) => {}
+ ClientboundGamePacket::ClientboundSelectAdvancementsTabPacket(_) => {}
+ ClientboundGamePacket::ClientboundSetActionBarTextPacket(_) => {}
+ ClientboundGamePacket::ClientboundSetBorderCenterPacket(_) => {}
+ ClientboundGamePacket::ClientboundSetBorderLerpSizePacket(_) => {}
+ ClientboundGamePacket::ClientboundSetBorderSizePacket(_) => {}
+ ClientboundGamePacket::ClientboundSetBorderWarningDelayPacket(_) => {}
+ ClientboundGamePacket::ClientboundSetBorderWarningDistancePacket(_) => {}
+ ClientboundGamePacket::ClientboundSetCameraPacket(_) => {}
+ ClientboundGamePacket::ClientboundSetChunkCacheRadiusPacket(_) => {}
+ ClientboundGamePacket::ClientboundSetDisplayChatPreviewPacket(_) => {}
+ ClientboundGamePacket::ClientboundSetDisplayObjectivePacket(_) => {}
+ ClientboundGamePacket::ClientboundSetEntityMotionPacket(_) => {}
+ ClientboundGamePacket::ClientboundSetObjectivePacket(_) => {}
+ ClientboundGamePacket::ClientboundSetPassengersPacket(_) => {}
+ ClientboundGamePacket::ClientboundSetPlayerTeamPacket(_) => {}
+ ClientboundGamePacket::ClientboundSetScorePacket(_) => {}
+ ClientboundGamePacket::ClientboundSetSimulationDistancePacket(_) => {}
+ ClientboundGamePacket::ClientboundSetSubtitleTextPacket(_) => {}
+ ClientboundGamePacket::ClientboundSetTitleTextPacket(_) => {}
+ ClientboundGamePacket::ClientboundSetTitlesAnimationPacket(_) => {}
+ ClientboundGamePacket::ClientboundSoundEntityPacket(_) => {}
+ ClientboundGamePacket::ClientboundStopSoundPacket(_) => {}
+ ClientboundGamePacket::ClientboundTabListPacket(_) => {}
+ ClientboundGamePacket::ClientboundTagQueryPacket(_) => {}
+ ClientboundGamePacket::ClientboundTakeItemEntityPacket(_) => {}
}
Ok(())
}
/// Runs game_tick every 50 milliseconds.
- async fn game_tick_loop(client: Client, tx: UnboundedSender<Event>) {
+ async fn game_tick_loop(mut client: Client, tx: UnboundedSender<Event>) {
let mut game_tick_interval = time::interval(time::Duration::from_millis(50));
// TODO: Minecraft bursts up to 10 ticks and then skips, we should too
game_tick_interval.set_missed_tick_behavior(time::MissedTickBehavior::Burst);
loop {
game_tick_interval.tick().await;
- Self::game_tick(&client, &tx).await;
+ Self::game_tick(&mut client, &tx).await;
}
}
/// Runs every 50 milliseconds.
- async fn game_tick(client: &Client, tx: &UnboundedSender<Event>) {
- if client.dimension.lock().unwrap().is_none() {
- return;
+ async fn game_tick(client: &mut Client, tx: &UnboundedSender<Event>) {
+ // return if there's no chunk at the player's position
+ {
+ let dimension_lock = client.dimension.lock().unwrap();
+ let player_lock = client.player.lock().unwrap();
+ let player_entity = player_lock.entity(&dimension_lock);
+ let player_entity = if let Some(player_entity) = player_entity {
+ player_entity
+ } else {
+ return;
+ };
+ let player_chunk_pos: ChunkPos = player_entity.pos().into();
+ if dimension_lock[&player_chunk_pos].is_none() {
+ return;
+ }
}
+
+ // TODO: if we're a passenger, send the required packets
+
+ if let Err(e) = client.send_position().await {
+ println!("Error sending position: {:?}", e);
+ }
+
+ // TODO: minecraft does ambient sounds here
+
tx.send(Event::GameTick).unwrap();
}
}
diff --git a/azalea-client/src/movement.rs b/azalea-client/src/movement.rs
index 5f9533be..df2af9d8 100644
--- a/azalea-client/src/movement.rs
+++ b/azalea-client/src/movement.rs
@@ -1,6 +1,13 @@
use crate::Client;
-use azalea_core::EntityPos;
-use azalea_protocol::packets::game::serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPacketPosRot;
+use azalea_core::Vec3;
+use azalea_physics::collision::{MovableEntity, MoverType};
+use azalea_physics::HasPhysics;
+use azalea_protocol::packets::game::{
+ serverbound_move_player_pos_packet::ServerboundMovePlayerPosPacket,
+ serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket,
+ serverbound_move_player_rot_packet::ServerboundMovePlayerRotPacket,
+ serverbound_move_player_status_only_packet::ServerboundMovePlayerStatusOnlyPacket,
+};
use azalea_world::MoveEntityError;
use thiserror::Error;
@@ -12,45 +19,149 @@ pub enum MovePlayerError {
Io(#[from] std::io::Error),
}
+impl From<MoveEntityError> for MovePlayerError {
+ fn from(err: MoveEntityError) -> Self {
+ match err {
+ MoveEntityError::EntityDoesNotExist => MovePlayerError::PlayerNotInWorld,
+ }
+ }
+}
+
impl Client {
- /// Set the client's position to the given coordinates.
- pub async fn move_to(&mut self, new_pos: EntityPos) -> Result<(), MovePlayerError> {
- {
+ /// This gets called every tick.
+ pub async fn send_position(&mut self) -> Result<(), MovePlayerError> {
+ let packet = {
+ let player_lock = self.player.lock().unwrap();
+
let mut dimension_lock = self.dimension.lock().unwrap();
- let dimension = dimension_lock.as_mut().unwrap();
- let player_lock = self.player.lock().unwrap();
+ let mut player_entity = player_lock
+ .entity_mut(&mut dimension_lock)
+ .expect("Player must exist");
+ let player_pos = player_entity.pos();
+ let player_old_pos = player_entity.last_pos;
+
+ // TODO: send sprinting and sneaking packets here if they changed
- let player_id = if let Some(player_lock) = player_lock.entity(dimension) {
- player_lock.id
+ // TODO: the camera being able to be controlled by other entities isn't implemented yet
+ // if !self.is_controlled_camera() { return };
+
+ let x_delta = player_pos.x - player_old_pos.x;
+ let y_delta = player_pos.y - player_old_pos.y;
+ let z_delta = player_pos.z - player_old_pos.z;
+ let y_rot_delta = (player_entity.y_rot - player_entity.y_rot_last) as f64;
+ let x_rot_delta = (player_entity.x_rot - player_entity.x_rot_last) as f64;
+
+ self.position_remainder += 1;
+
+ // boolean sendingPosition = Mth.lengthSquared(xDelta, yDelta, zDelta) > Mth.square(2.0E-4D) || this.positionReminder >= 20;
+ let sending_position = ((x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2))
+ > 2.0e-4f64.powi(2))
+ || self.position_remainder >= 20;
+ let sending_rotation = y_rot_delta != 0.0 || x_rot_delta != 0.0;
+
+ // if self.is_passenger() {
+ // TODO: posrot packet for being a passenger
+ // }
+ let packet = if sending_position && sending_rotation {
+ Some(
+ ServerboundMovePlayerPosRotPacket {
+ x: player_pos.x,
+ y: player_pos.y,
+ z: player_pos.z,
+ x_rot: player_entity.x_rot,
+ y_rot: player_entity.y_rot,
+ on_ground: player_entity.on_ground,
+ }
+ .get(),
+ )
+ } else if sending_position {
+ Some(
+ ServerboundMovePlayerPosPacket {
+ x: player_pos.x,
+ y: player_pos.y,
+ z: player_pos.z,
+ on_ground: player_entity.on_ground,
+ }
+ .get(),
+ )
+ } else if sending_rotation {
+ Some(
+ ServerboundMovePlayerRotPacket {
+ x_rot: player_entity.x_rot,
+ y_rot: player_entity.y_rot,
+ on_ground: player_entity.on_ground,
+ }
+ .get(),
+ )
+ } else if player_entity.last_on_ground != player_entity.on_ground {
+ Some(
+ ServerboundMovePlayerStatusOnlyPacket {
+ on_ground: player_entity.on_ground,
+ }
+ .get(),
+ )
} else {
- return Err(MovePlayerError::PlayerNotInWorld);
+ None
};
- match dimension.move_entity(player_id, new_pos) {
- Ok(_) => Ok(()),
- Err(e) => match e {
- MoveEntityError::EntityDoesNotExist => Err(MovePlayerError::PlayerNotInWorld),
- },
- }?;
+ if sending_position {
+ player_entity.last_pos = *player_entity.pos();
+ self.position_remainder = 0;
+ }
+ if sending_rotation {
+ player_entity.y_rot_last = player_entity.y_rot;
+ player_entity.x_rot_last = player_entity.x_rot;
+ }
+
+ player_entity.last_on_ground = player_entity.on_ground;
+ // minecraft checks for autojump here, but also autojump is bad so
+
+ packet
+ };
+
+ if let Some(packet) = packet {
+ self.conn.lock().await.write(packet).await?;
}
- self.conn
- .lock()
- .await
- .write(
- ServerboundMovePlayerPacketPosRot {
- x: new_pos.x,
- y: new_pos.y,
- z: new_pos.z,
- x_rot: 0.0,
- y_rot: 0.0,
- on_ground: false,
- }
- .get(),
- )
- .await?;
+ Ok(())
+ }
+
+ // Set our current position to the provided Vec3, potentially clipping through blocks.
+ pub async fn set_pos(&mut self, new_pos: Vec3) -> Result<(), MovePlayerError> {
+ let player_lock = self.player.lock().unwrap();
+ let mut dimension_lock = self.dimension.lock().unwrap();
+
+ dimension_lock.set_entity_pos(player_lock.entity_id, new_pos)?;
+
+ Ok(())
+ }
+
+ pub async fn move_entity(&mut self, movement: &Vec3) -> Result<(), MovePlayerError> {
+ let mut dimension_lock = self.dimension.lock().unwrap();
+ let player = self.player.lock().unwrap();
+
+ let mut entity = player
+ .entity_mut(&mut dimension_lock)
+ .ok_or(MovePlayerError::PlayerNotInWorld)?;
+ println!(
+ "move entity bounding box: {} {:?}",
+ entity.id, entity.bounding_box
+ );
+
+ entity.move_colliding(&MoverType::Own, movement)?;
Ok(())
}
+
+ pub fn ai_step(&mut self) {
+ let player_lock = self.player.lock().unwrap();
+ let mut dimension_lock = self.dimension.lock().unwrap();
+
+ let mut player_entity = player_lock
+ .entity_mut(&mut dimension_lock)
+ .expect("Player must exist");
+
+ player_entity.ai_step();
+ }
}
diff --git a/azalea-client/src/player.rs b/azalea-client/src/player.rs
index 6c093517..11651b9c 100644
--- a/azalea-client/src/player.rs
+++ b/azalea-client/src/player.rs
@@ -1,4 +1,4 @@
-use azalea_entity::Entity;
+use azalea_world::entity::{EntityMut, EntityRef};
use azalea_world::Dimension;
use uuid::Uuid;
@@ -7,6 +7,7 @@ pub trait DimensionHaver {
fn dimension(&self) -> &Dimension;
}
+/// A player in the dimension or tab list.
#[derive(Default, Debug)]
pub struct Player {
/// The player's uuid.
@@ -16,10 +17,14 @@ pub struct Player {
}
impl Player {
- /// Get the entity of the player in the world.
- pub fn entity<'a>(&self, world: &'a Dimension) -> Option<&'a Entity> {
- // world.entity_by_uuid(&self.uuid)
- world.entity_by_id(self.entity_id)
+ /// Get a reference to the entity of the player in the world.
+ pub fn entity<'d>(&'d self, dimension: &'d Dimension) -> Option<EntityRef> {
+ dimension.entity(self.entity_id)
+ }
+
+ /// Get a mutable reference to the entity of the player in the world.
+ pub fn entity_mut<'d>(&'d self, dimension: &'d mut Dimension) -> Option<EntityMut> {
+ dimension.entity_mut(self.entity_id)
}
pub fn set_uuid(&mut self, uuid: Uuid) {
diff --git a/azalea-core/README.md b/azalea-core/README.md
new file mode 100644
index 00000000..7d826076
--- /dev/null
+++ b/azalea-core/README.md
@@ -0,0 +1,3 @@
+# Azalea Core
+
+Miscellaneous things in Azalea.
diff --git a/azalea-core/src/aabb.rs b/azalea-core/src/aabb.rs
new file mode 100644
index 00000000..40230fe4
--- /dev/null
+++ b/azalea-core/src/aabb.rs
@@ -0,0 +1,447 @@
+use crate::{Axis, BlockHitResult, BlockPos, Direction, PositionXYZ, Vec3};
+
+pub const EPSILON: f64 = 1.0E-7;
+
+/// A rectangular prism with a starting and ending point.
+#[derive(Copy, Clone, Debug, PartialEq, Default)]
+pub struct AABB {
+ pub min_x: f64,
+ pub min_y: f64,
+ pub min_z: f64,
+
+ pub max_x: f64,
+ pub max_y: f64,
+ pub max_z: f64,
+}
+
+impl AABB {
+ pub fn contract(&self, x: f64, y: f64, z: f64) -> AABB {
+ let mut min_x = self.min_x;
+ let mut min_y = self.min_y;
+ let mut min_z = self.min_z;
+
+ let mut max_x = self.max_x;
+ let mut max_y = self.max_y;
+ let mut max_z = self.max_z;
+
+ if x < 0.0 {
+ min_x -= x;
+ } else if x > 0.0 {
+ max_x -= x;
+ }
+
+ if y < 0.0 {
+ min_y -= y;
+ } else if y > 0.0 {
+ max_y -= y;
+ }
+
+ if z < 0.0 {
+ min_z -= z;
+ } else if z > 0.0 {
+ max_z -= z;
+ }
+
+ AABB {
+ min_x,
+ min_y,
+ min_z,
+
+ max_x,
+ max_y,
+ max_z,
+ }
+ }
+
+ pub fn expand_towards(&self, other: &Vec3) -> AABB {
+ let mut min_x = self.min_x;
+ let mut min_y = self.min_y;
+ let mut min_z = self.min_z;
+
+ let mut max_x = self.max_x;
+ let mut max_y = self.max_y;
+ let mut max_z = self.max_z;
+
+ if other.x < 0.0 {
+ min_x += other.x;
+ } else if other.x > 0.0 {
+ max_x += other.x;
+ }
+
+ if other.y < 0.0 {
+ min_y += other.y;
+ } else if other.y > 0.0 {
+ max_y += other.y;
+ }
+
+ if other.z < 0.0 {
+ min_z += other.z;
+ } else if other.z > 0.0 {
+ max_z += other.z;
+ }
+
+ AABB {
+ min_x,
+ min_y,
+ min_z,
+
+ max_x,
+ max_y,
+ max_z,
+ }
+ }
+
+ pub fn inflate(&self, x: f64, y: f64, z: f64) -> AABB {
+ let min_x = self.min_x - x;
+ let min_y = self.min_y - y;
+ let min_z = self.min_z - z;
+
+ let max_x = self.max_x + x;
+ let max_y = self.max_y + y;
+ let max_z = self.max_z + z;
+
+ AABB {
+ min_x,
+ min_y,
+ min_z,
+
+ max_x,
+ max_y,
+ max_z,
+ }
+ }
+
+ pub fn intersect(&self, other: &AABB) -> AABB {
+ let min_x = self.min_x.max(other.min_x);
+ let min_y = self.min_y.max(other.min_y);
+ let min_z = self.min_z.max(other.min_z);
+
+ let max_x = self.max_x.min(other.max_x);
+ let max_y = self.max_y.min(other.max_y);
+ let max_z = self.max_z.min(other.max_z);
+
+ AABB {
+ min_x,
+ min_y,
+ min_z,
+
+ max_x,
+ max_y,
+ max_z,
+ }
+ }
+
+ pub fn minmax(&self, other: &AABB) -> AABB {
+ let min_x = self.min_x.min(other.min_x);
+ let min_y = self.min_y.min(other.min_y);
+ let min_z = self.min_z.min(other.min_z);
+
+ let max_x = self.max_x.max(other.max_x);
+ let max_y = self.max_y.max(other.max_y);
+ let max_z = self.max_z.max(other.max_z);
+
+ AABB {
+ min_x,
+ min_y,
+ min_z,
+
+ max_x,
+ max_y,
+ max_z,
+ }
+ }
+
+ pub fn move_relative(&self, x: f64, y: f64, z: f64) -> AABB {
+ AABB {
+ min_x: self.min_x + x,
+ min_y: self.min_y + y,
+ min_z: self.min_z + z,
+
+ max_x: self.max_x + x,
+ max_y: self.max_y + y,
+ max_z: self.max_z + z,
+ }
+ }
+
+ pub fn intersects_aabb(&self, other: &AABB) -> bool {
+ self.min_x < other.max_x
+ && self.max_x > other.min_x
+ && self.min_y < other.max_y
+ && self.max_y > other.min_y
+ && self.min_z < other.max_z
+ && self.max_z > other.min_z
+ }
+ pub fn intersects_vec3(&self, other: &Vec3, other2: &Vec3) -> bool {
+ self.intersects_aabb(&AABB {
+ min_x: other.x.min(other2.x),
+ min_y: other.y.min(other2.y),
+ min_z: other.z.min(other2.z),
+
+ max_x: other.x.max(other2.x),
+ max_y: other.y.max(other2.y),
+ max_z: other.z.max(other2.z),
+ })
+ }
+
+ pub fn contains(&self, x: f64, y: f64, z: f64) -> bool {
+ x >= self.min_x
+ && x < self.max_x
+ && y >= self.min_y
+ && y < self.max_y
+ && z >= self.min_z
+ && z < self.max_z
+ }
+
+ pub fn size(&self) -> f64 {
+ let x = self.get_size(Axis::X);
+ let y = self.get_size(Axis::Y);
+ let z = self.get_size(Axis::Z);
+ (x + y + z) / 3.0
+ }
+
+ pub fn get_size(&self, axis: Axis) -> f64 {
+ axis.choose(
+ self.max_x - self.min_x,
+ self.max_y - self.min_y,
+ self.max_z - self.min_z,
+ )
+ }
+
+ pub fn deflate(&mut self, x: f64, y: f64, z: f64) -> AABB {
+ self.inflate(-x, -y, -z)
+ }
+
+ pub fn clip(&self, min: &Vec3, max: &Vec3) -> Option<Vec3> {
+ let mut t = [1.0];
+ let x = max.x - min.x;
+ let y = max.y - min.y;
+ let z = max.z - min.z;
+ let dir = self.get_direction(self, min, &mut t, None, &Vec3 { x, y, z });
+ if dir.is_none() {
+ return None;
+ }
+ let t = t[0];
+ Some(min.add(t * x, t * y, t * z))
+ }
+
+ pub fn clip_iterable(
+ &self,
+ boxes: &Vec<AABB>,
+ from: &Vec3,
+ to: &Vec3,
+ pos: &BlockPos,
+ ) -> Option<BlockHitResult> {
+ let mut t = [1.0];
+ let mut dir = None;
+ let x = to.x - from.x;
+ let y = to.y - from.y;
+ let z = to.z - from.z;
+
+ for aabb in boxes {
+ dir = self.get_direction(aabb, from, &mut t, dir, &Vec3 { x, y, z });
+ }
+ if dir.is_none() {
+ return None;
+ }
+ let t = t[0];
+ Some(BlockHitResult {
+ location: from.add(t * x, t * y, t * z),
+ direction: dir.unwrap(),
+ block_pos: *pos,
+ inside: false,
+ miss: false,
+ })
+ }
+
+ fn get_direction(
+ &self,
+ aabb: &AABB,
+ from: &Vec3,
+ t: &mut [f64],
+ dir: Option<Direction>,
+ delta: &Vec3,
+ ) -> Option<Direction> {
+ if delta.x > EPSILON {
+ return self.clip_point(
+ t,
+ dir,
+ delta,
+ aabb.min_x,
+ aabb.min_y,
+ aabb.max_y,
+ aabb.min_z,
+ aabb.max_z,
+ Direction::West,
+ from,
+ );
+ } else if delta.x < -EPSILON {
+ return self.clip_point(
+ t,
+ dir,
+ delta,
+ aabb.max_x,
+ aabb.min_y,
+ aabb.max_y,
+ aabb.min_z,
+ aabb.max_z,
+ Direction::East,
+ from,
+ );
+ }
+
+ if delta.y > EPSILON {
+ return self.clip_point(
+ t,
+ dir,
+ &Vec3 {
+ x: delta.y,
+ y: delta.z,
+ z: delta.x,
+ },
+ aabb.min_y,
+ aabb.min_z,
+ aabb.max_z,
+ aabb.min_x,
+ aabb.max_x,
+ Direction::Down,
+ &Vec3 {
+ x: from.y,
+ y: from.z,
+ z: from.x,
+ },
+ );
+ } else if delta.y < -EPSILON {
+ return self.clip_point(
+ t,
+ dir,
+ &Vec3 {
+ x: delta.y,
+ y: delta.z,
+ z: delta.x,
+ },
+ aabb.max_y,
+ aabb.min_z,
+ aabb.max_z,
+ aabb.min_x,
+ aabb.max_x,
+ Direction::Up,
+ &Vec3 {
+ x: from.y,
+ y: from.z,
+ z: from.x,
+ },
+ );
+ }
+
+ if delta.z > EPSILON {
+ return self.clip_point(
+ t,
+ dir,
+ &Vec3 {
+ x: delta.z,
+ y: delta.x,
+ z: delta.y,
+ },
+ aabb.min_z,
+ aabb.min_x,
+ aabb.max_x,
+ aabb.min_y,
+ aabb.max_y,
+ Direction::North,
+ &Vec3 {
+ x: from.z,
+ y: from.x,
+ z: from.y,
+ },
+ );
+ } else if delta.z < -EPSILON {
+ return self.clip_point(
+ t,
+ dir,
+ &Vec3 {
+ x: delta.z,
+ y: delta.x,
+ z: delta.y,
+ },
+ aabb.max_z,
+ aabb.min_x,
+ aabb.max_x,
+ aabb.min_y,
+ aabb.max_y,
+ Direction::South,
+ &Vec3 {
+ x: from.z,
+ y: from.x,
+ z: from.y,
+ },
+ );
+ }
+
+ dir
+ }
+
+ fn clip_point(
+ &self,
+ t: &mut [f64],
+ approach_dir: Option<Direction>,
+ delta: &Vec3,
+ begin: f64,
+ min_x: f64,
+ max_x: f64,
+ min_z: f64,
+ max_z: f64,
+ result_dir: Direction,
+ start: &Vec3,
+ ) -> Option<Direction> {
+ let t_x = (begin - start.x) / delta.x;
+ let t_y = (start.y + t_x) / delta.y;
+ let t_z = (start.z + t_x) / delta.z;
+ if 0.0 < t_x
+ && t_x < t[0]
+ && min_x - EPSILON < t_y
+ && t_y < max_x + EPSILON
+ && min_z - EPSILON < t_z
+ && t_z < max_z + EPSILON
+ {
+ t[0] = t_x;
+ Some(result_dir)
+ } else {
+ approach_dir
+ }
+ }
+
+ pub fn has_nan(&self) -> bool {
+ self.min_x.is_nan()
+ || self.min_y.is_nan()
+ || self.min_z.is_nan()
+ || self.max_x.is_nan()
+ || self.max_y.is_nan()
+ || self.max_z.is_nan()
+ }
+
+ pub fn get_center(&self) -> Vec3 {
+ Vec3 {
+ x: (self.min_x + self.max_x) / 2.0,
+ y: (self.min_y + self.max_y) / 2.0,
+ z: (self.min_z + self.max_z) / 2.0,
+ }
+ }
+
+ pub fn of_size(center: Vec3, dx: f64, dy: f64, dz: f64) -> AABB {
+ AABB {
+ min_x: center.x - dx / 2.0,
+ min_y: center.y - dy / 2.0,
+ min_z: center.z - dz / 2.0,
+ max_x: center.x + dx / 2.0,
+ max_y: center.y + dy / 2.0,
+ max_z: center.z + dz / 2.0,
+ }
+ }
+
+ pub fn max(&self, axis: &Axis) -> f64 {
+ axis.choose(self.max_x, self.max_y, self.max_z)
+ }
+ pub fn min(&self, axis: &Axis) -> f64 {
+ axis.choose(self.min_x, self.min_y, self.min_z)
+ }
+}
diff --git a/azalea-core/src/bitset.rs b/azalea-core/src/bitset.rs
new file mode 100644
index 00000000..2ffd5657
--- /dev/null
+++ b/azalea-core/src/bitset.rs
@@ -0,0 +1,58 @@
+use azalea_buf::{BufReadError, McBufReadable, McBufWritable};
+use std::io::{Read, Write};
+
+/// Represents Java's BitSet, a list of bits.
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
+pub struct BitSet {
+ data: Vec<u64>,
+}
+
+// the Index trait requires us to return a reference, but we can't do that
+impl BitSet {
+ pub fn new(size: usize) -> Self {
+ BitSet {
+ data: vec![0; size.div_ceil(64)],
+ }
+ }
+
+ pub fn index(&self, index: usize) -> bool {
+ (self.data[index / 64] & (1u64 << (index % 64))) != 0
+ }
+}
+
+impl McBufReadable for BitSet {
+ fn read_from(buf: &mut impl Read) -> Result<Self, BufReadError> {
+ Ok(Self {
+ data: Vec::<u64>::read_from(buf)?,
+ })
+ }
+}
+
+impl McBufWritable for BitSet {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ self.data.write_into(buf)
+ }
+}
+
+impl BitSet {
+ pub fn set(&mut self, bit_index: usize) {
+ self.data[bit_index / 64] |= 1u64 << (bit_index % 64);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_bitset() {
+ let mut bitset = BitSet::new(64);
+ assert_eq!(bitset.index(0), false);
+ assert_eq!(bitset.index(1), false);
+ assert_eq!(bitset.index(2), false);
+ bitset.set(1);
+ assert_eq!(bitset.index(0), false);
+ assert_eq!(bitset.index(1), true);
+ assert_eq!(bitset.index(2), false);
+ }
+}
diff --git a/azalea-core/src/block_hit_result.rs b/azalea-core/src/block_hit_result.rs
new file mode 100644
index 00000000..80c9b8fc
--- /dev/null
+++ b/azalea-core/src/block_hit_result.rs
@@ -0,0 +1,9 @@
+use crate::{BlockPos, Direction, Vec3};
+
+pub struct BlockHitResult {
+ pub location: Vec3,
+ pub direction: Direction,
+ pub block_pos: BlockPos,
+ pub miss: bool,
+ pub inside: bool,
+}
diff --git a/azalea-core/src/cursor3d.rs b/azalea-core/src/cursor3d.rs
new file mode 100644
index 00000000..fa265c8a
--- /dev/null
+++ b/azalea-core/src/cursor3d.rs
@@ -0,0 +1,115 @@
+use crate::BlockPos;
+
+pub struct Cursor3d {
+ index: usize,
+
+ origin_x: i32,
+ origin_y: i32,
+ origin_z: i32,
+
+ width: usize,
+ height: usize,
+ depth: usize,
+
+ end: usize,
+}
+
+impl Iterator for Cursor3d {
+ type Item = CursorIteration;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.index == self.end {
+ return None;
+ }
+ let x = self.index % self.width;
+ let r = self.index / self.width;
+ let y = r % self.height;
+ let z = r / self.height;
+ self.index += 1;
+
+ let mut iteration_type = 0;
+ if x == 0 || x == self.width - 1 {
+ iteration_type += 1;
+ }
+ if y == 0 || y == self.height - 1 {
+ iteration_type += 1;
+ }
+ if z == 0 || z == self.depth - 1 {
+ iteration_type += 1;
+ }
+
+ Some(CursorIteration {
+ pos: BlockPos {
+ x: self.origin_x + x as i32,
+ y: self.origin_y + y as i32,
+ z: self.origin_z + z as i32,
+ },
+ iteration_type: iteration_type.into(),
+ })
+ }
+}
+
+#[repr(u8)]
+#[derive(Eq, PartialEq, Debug)]
+pub enum CursorIterationType {
+ Inside = 0,
+ Face = 1,
+ Edge = 2,
+ Corner = 3,
+}
+
+pub struct CursorIteration {
+ pub pos: BlockPos,
+ pub iteration_type: CursorIterationType,
+}
+
+impl Cursor3d {
+ pub fn new(
+ origin_x: i32,
+ origin_y: i32,
+ origin_z: i32,
+ end_x: i32,
+ end_y: i32,
+ end_z: i32,
+ ) -> Self {
+ println!(
+ "making cursor3d with origin: {}, {}, {} and end: {}, {}, {}",
+ origin_x, origin_y, origin_z, end_x, end_y, end_z
+ );
+ let width = (end_x - origin_x + 1)
+ .try_into()
+ .expect("Impossible width.");
+ let height = (end_y - origin_y + 1)
+ .try_into()
+ .expect("Impossible height.");
+ let depth = (end_z - origin_z + 1)
+ .try_into()
+ .expect("Impossible depth.");
+
+ Self {
+ index: 0,
+
+ origin_x,
+ origin_y,
+ origin_z,
+
+ width,
+ height,
+ depth,
+
+ end: width * height * depth,
+ }
+ }
+}
+
+impl From<u8> for CursorIterationType {
+ fn from(value: u8) -> Self {
+ match value {
+ 0 => CursorIterationType::Inside,
+ 1 => CursorIterationType::Face,
+ 2 => CursorIterationType::Edge,
+ 3 => CursorIterationType::Corner,
+ _ => panic!("Invalid iteration type"),
+ }
+ }
+}
diff --git a/azalea-core/src/delta.rs b/azalea-core/src/delta.rs
index c0056411..d1f72c17 100644
--- a/azalea-core/src/delta.rs
+++ b/azalea-core/src/delta.rs
@@ -1,4 +1,6 @@
-use crate::EntityPos;
+use std::ops::{Add, AddAssign};
+
+use crate::Vec3;
pub use azalea_buf::McBuf;
pub trait PositionDeltaTrait {
@@ -7,13 +9,6 @@ pub trait PositionDeltaTrait {
fn z(&self) -> f64;
}
-#[derive(Clone, Debug, McBuf, Default)]
-pub struct PositionDelta {
- pub xa: f64,
- pub ya: f64,
- pub za: f64,
-}
-
/// Only works for up to 8 blocks
#[derive(Clone, Debug, McBuf, Default)]
pub struct PositionDelta8 {
@@ -22,18 +17,6 @@ pub struct PositionDelta8 {
pub za: i16,
}
-impl PositionDeltaTrait for PositionDelta {
- fn x(&self) -> f64 {
- self.xa
- }
- fn y(&self) -> f64 {
- self.ya
- }
- fn z(&self) -> f64 {
- self.za
- }
-}
-
impl PositionDelta8 {
#[deprecated]
pub fn float(&self) -> (f64, f64, f64) {
@@ -57,12 +40,60 @@ impl PositionDeltaTrait for PositionDelta8 {
}
}
-impl EntityPos {
- pub fn with_delta(&self, delta: &dyn PositionDeltaTrait) -> EntityPos {
- EntityPos {
+impl Vec3 {
+ pub fn with_delta(&self, delta: &dyn PositionDeltaTrait) -> Vec3 {
+ Vec3 {
x: self.x + delta.x(),
y: self.y + delta.y(),
z: self.z + delta.z(),
}
}
+
+ pub fn length_squared(&self) -> f64 {
+ self.x * self.x + self.y * self.y + self.z * self.z
+ }
+
+ pub fn normalize(&self) -> Vec3 {
+ let length = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
+ if length < 1e-4 {
+ return Vec3::default();
+ }
+ Vec3 {
+ x: self.x / length,
+ y: self.y / length,
+ z: self.z / length,
+ }
+ }
+
+ pub fn multiply(&self, x: f64, y: f64, z: f64) -> Vec3 {
+ Vec3 {
+ x: self.x * x,
+ y: self.y * y,
+ z: self.z * z,
+ }
+ }
+ pub fn scale(&self, amount: f64) -> Vec3 {
+ self.multiply(amount, amount, amount)
+ }
+}
+
+// impl + and +=
+impl Add for Vec3 {
+ type Output = Vec3;
+
+ fn add(self, other: Vec3) -> Vec3 {
+ Vec3 {
+ x: self.x + other.x,
+ y: self.y + other.y,
+ z: self.z + other.z,
+ }
+ }
+}
+
+impl AddAssign for Vec3 {
+ fn add_assign(&mut self, other: Vec3) {
+ self.x += other.x;
+ self.y += other.y;
+ self.z += other.z;
+ }
}
diff --git a/azalea-core/src/direction.rs b/azalea-core/src/direction.rs
index d3083922..96d20a10 100644
--- a/azalea-core/src/direction.rs
+++ b/azalea-core/src/direction.rs
@@ -1,5 +1,7 @@
use azalea_buf::McBuf;
+use crate::floor_mod;
+
#[derive(Clone, Copy, Debug, McBuf)]
pub enum Direction {
Down = 0,
@@ -9,3 +11,73 @@ pub enum Direction {
West = 4,
East = 5,
}
+
+#[derive(Clone, Copy, Debug)]
+pub enum Axis {
+ X = 0,
+ Y = 1,
+ Z = 2,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub enum AxisCycle {
+ None = 0,
+ Forward = 1,
+ Backward = 2,
+}
+
+impl Axis {
+ /// Pick x, y, or z from the arguments depending on the axis.
+ #[inline]
+ pub fn choose<T>(&self, x: T, y: T, z: T) -> T {
+ match self {
+ Axis::X => x,
+ Axis::Y => y,
+ Axis::Z => z,
+ }
+ }
+
+ pub fn from_ordinal(ordinal: u32) -> Self {
+ match ordinal {
+ 0 => Axis::X,
+ 1 => Axis::Y,
+ 2 => Axis::Z,
+ _ => panic!("Invalid ordinal {}", ordinal),
+ }
+ }
+}
+
+impl AxisCycle {
+ pub fn from_ordinal(ordinal: u32) -> Self {
+ match ordinal {
+ 0 => Self::None,
+ 1 => Self::Forward,
+ 2 => Self::Backward,
+ _ => panic!("invalid ordinal"),
+ }
+ }
+ pub fn between(axis0: Axis, axis1: Axis) -> Self {
+ Self::from_ordinal(floor_mod(axis1 as i32 - axis0 as i32, 3))
+ }
+ pub fn inverse(self) -> Self {
+ match self {
+ Self::None => Self::None,
+ Self::Forward => Self::Backward,
+ Self::Backward => Self::Forward,
+ }
+ }
+ pub fn cycle(self, axis: Axis) -> Axis {
+ match self {
+ Self::None => axis,
+ Self::Forward => Axis::from_ordinal(floor_mod(axis as i32 + 1, 3)),
+ Self::Backward => Axis::from_ordinal(floor_mod(axis as i32 - 1, 3)),
+ }
+ }
+ pub fn cycle_xyz(self, x: u32, y: u32, z: u32, axis: Axis) -> u32 {
+ match self {
+ Self::None => axis.choose(x, y, z),
+ Self::Forward => axis.choose(z, x, y),
+ Self::Backward => axis.choose(y, z, x),
+ }
+ }
+}
diff --git a/azalea-core/src/lib.rs b/azalea-core/src/lib.rs
index a1fa1fca..5aca7d52 100755
--- a/azalea-core/src/lib.rs
+++ b/azalea-core/src/lib.rs
@@ -12,16 +12,55 @@ mod game_type;
pub use game_type::*;
mod slot;
-pub use slot::{Slot, SlotData};
+pub use slot::*;
mod position;
pub use position::*;
mod direction;
-pub use direction::Direction;
+pub use direction::*;
mod delta;
pub use delta::*;
mod particle;
pub use particle::*;
+
+mod cursor3d;
+pub use cursor3d::*;
+
+mod bitset;
+pub use bitset::*;
+
+mod aabb;
+pub use aabb::*;
+
+mod block_hit_result;
+pub use block_hit_result::*;
+
+// java moment
+// TODO: add tests and optimize/simplify this
+pub fn floor_mod(x: i32, y: u32) -> u32 {
+ if x < 0 {
+ y - ((-x) as u32 % y)
+ } else {
+ x as u32 % y
+ }
+}
+
+// TODO: make this generic
+pub fn binary_search(mut min: u32, max: u32, predicate: &dyn Fn(u32) -> bool) -> u32 {
+ let mut diff = max - min;
+ while diff > 0 {
+ let diff_mid = diff / 2;
+ let mid = min + diff_mid;
+ if predicate(mid) {
+ diff = diff_mid;
+ } else {
+ min = mid + 1;
+ diff -= diff_mid + 1;
+ }
+ }
+
+ min
+}
diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs
index 7371d530..f54510b5 100644
--- a/azalea-core/src/position.rs
+++ b/azalea-core/src/position.rs
@@ -2,13 +2,54 @@ use crate::ResourceLocation;
use azalea_buf::{BufReadError, McBufReadable, McBufWritable};
use std::{
io::{Read, Write},
- ops::Rem,
+ ops::{Add, Mul, Rem},
};
-pub trait PositionXYZ<T> {
- fn add_x(&self, n: T) -> Self;
- fn add_y(&self, n: T) -> Self;
- fn add_z(&self, n: T) -> Self;
+pub trait PositionXYZ<T>
+where
+ T: Add<T, Output = T> + Mul<T, Output = T>,
+{
+ fn x(&self) -> T;
+ fn y(&self) -> T;
+ fn z(&self) -> T;
+
+ fn set_x(&self, n: T) -> Self;
+ fn set_y(&self, n: T) -> Self;
+ fn set_z(&self, n: T) -> Self;
+
+ // hopefully these get optimized
+ fn add_x(&self, n: T) -> Self
+ where
+ Self: Sized,
+ {
+ self.set_x(self.x() + n)
+ }
+ fn add_y(&self, n: T) -> Self
+ where
+ Self: Sized,
+ {
+ self.set_y(self.y() + n)
+ }
+ fn add_z(&self, n: T) -> Self
+ where
+ Self: Sized,
+ {
+ self.set_z(self.z() + n)
+ }
+
+ fn add(&self, x: T, y: T, z: T) -> Self
+ where
+ Self: Sized,
+ {
+ self.add_x(x).add_y(y).add_z(z)
+ }
+
+ fn length_sqr(&self) -> T
+ where
+ Self: Sized,
+ {
+ self.x() * self.x() + self.y() * self.y() + self.z() * self.z()
+ }
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
@@ -22,6 +63,10 @@ impl BlockPos {
pub fn new(x: i32, y: i32, z: i32) -> Self {
BlockPos { x, y, z }
}
+
+ pub fn below(&self) -> Self {
+ self.add(0, -1, 0)
+ }
}
impl Rem<i32> for BlockPos {
@@ -37,25 +82,34 @@ impl Rem<i32> for BlockPos {
}
impl PositionXYZ<i32> for BlockPos {
- fn add_x(&self, n: i32) -> Self {
+ fn x(&self) -> i32 {
+ self.x
+ }
+ fn y(&self) -> i32 {
+ self.y
+ }
+ fn z(&self) -> i32 {
+ self.z
+ }
+ fn set_x(&self, n: i32) -> Self {
BlockPos {
- x: self.x + n,
+ x: n,
y: self.y,
z: self.z,
}
}
- fn add_y(&self, n: i32) -> Self {
+ fn set_y(&self, n: i32) -> Self {
BlockPos {
x: self.x,
- y: self.y + n,
+ y: n,
z: self.z,
}
}
- fn add_z(&self, n: i32) -> Self {
+ fn set_z(&self, n: i32) -> Self {
BlockPos {
x: self.x,
y: self.y,
- z: self.z + n,
+ z: n,
}
}
}
@@ -84,6 +138,9 @@ impl ChunkSectionPos {
pub fn new(x: i32, y: i32, z: i32) -> Self {
ChunkSectionPos { x, y, z }
}
+ pub fn block_to_section_coord(block: i32) -> i32 {
+ block >> 4
+ }
}
/// The coordinates of a block inside a chunk.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
@@ -123,33 +180,43 @@ pub struct GlobalPos {
pub dimension: ResourceLocation,
}
+/// An exact point in the world.
#[derive(Debug, Clone, Copy, Default)]
-pub struct EntityPos {
+pub struct Vec3 {
pub x: f64,
pub y: f64,
pub z: f64,
}
-impl PositionXYZ<f64> for EntityPos {
- fn add_x(&self, n: f64) -> Self {
- EntityPos {
- x: self.x + n,
+impl PositionXYZ<f64> for Vec3 {
+ fn x(&self) -> f64 {
+ self.x
+ }
+ fn y(&self) -> f64 {
+ self.y
+ }
+ fn z(&self) -> f64 {
+ self.z
+ }
+ fn set_x(&self, n: f64) -> Self {
+ Vec3 {
+ x: n,
y: self.y,
z: self.z,
}
}
- fn add_y(&self, n: f64) -> Self {
- EntityPos {
+ fn set_y(&self, n: f64) -> Self {
+ Vec3 {
x: self.x,
- y: self.y + n,
+ y: n,
z: self.z,
}
}
- fn add_z(&self, n: f64) -> Self {
- EntityPos {
+ fn set_z(&self, n: f64) -> Self {
+ Vec3 {
x: self.x,
y: self.y,
- z: self.z + n,
+ z: n,
}
}
}
@@ -208,8 +275,8 @@ impl From<&ChunkBlockPos> for ChunkSectionBlockPos {
}
}
}
-impl From<&EntityPos> for BlockPos {
- fn from(pos: &EntityPos) -> Self {
+impl From<&Vec3> for BlockPos {
+ fn from(pos: &Vec3) -> Self {
BlockPos {
x: pos.x.floor() as i32,
y: pos.y.floor() as i32,
@@ -218,18 +285,27 @@ impl From<&EntityPos> for BlockPos {
}
}
-impl From<&EntityPos> for ChunkPos {
- fn from(pos: &EntityPos) -> Self {
+impl From<&Vec3> for ChunkPos {
+ fn from(pos: &Vec3) -> Self {
ChunkPos::from(&BlockPos::from(pos))
}
}
+const PACKED_X_LENGTH: u64 = 1 + 25; // minecraft does something a bit more complicated to get this 25
+const PACKED_Z_LENGTH: u64 = PACKED_X_LENGTH;
+const PACKED_Y_LENGTH: u64 = 64 - PACKED_X_LENGTH - PACKED_Z_LENGTH;
+const PACKED_X_MASK: u64 = (1 << PACKED_X_LENGTH) - 1;
+const PACKED_Y_MASK: u64 = (1 << PACKED_Y_LENGTH) - 1;
+const PACKED_Z_MASK: u64 = (1 << PACKED_Z_LENGTH) - 1;
+const Z_OFFSET: u64 = PACKED_Y_LENGTH;
+const X_OFFSET: u64 = PACKED_Y_LENGTH + PACKED_Z_LENGTH;
+
impl McBufReadable for BlockPos {
fn read_from(buf: &mut impl Read) -> Result<Self, BufReadError> {
let val = u64::read_from(buf)?;
- let x = (val >> 38) as i32;
- let y = (val & 0xFFF) as i32;
- let z = ((val >> 12) & 0x3FFFFFF) as i32;
+ let x = (val << 64 - X_OFFSET - PACKED_X_LENGTH >> 64 - PACKED_X_LENGTH) as i32;
+ let y = (val << 64 - PACKED_Y_LENGTH >> 64 - PACKED_Y_LENGTH) as i32;
+ let z = (val << 64 - Z_OFFSET - PACKED_Z_LENGTH >> 64 - PACKED_Z_LENGTH) as i32;
Ok(BlockPos { x, y, z })
}
}
@@ -256,10 +332,11 @@ impl McBufReadable for ChunkSectionPos {
impl McBufWritable for BlockPos {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- let data = (((self.x & 0x3FFFFFF) as i64) << 38)
- | (((self.z & 0x3FFFFFF) as i64) << 12)
- | ((self.y & 0xFFF) as i64);
- data.write_into(buf)
+ let mut val: u64 = 0;
+ val |= ((self.x as u64) & PACKED_X_MASK) << X_OFFSET;
+ val |= ((self.y as u64) & PACKED_Y_MASK) << 0;
+ val |= ((self.z as u64) & PACKED_Z_MASK) << Z_OFFSET;
+ val.write_into(buf)
}
}
@@ -302,7 +379,7 @@ mod tests {
#[test]
fn test_from_entity_pos_to_block_pos() {
- let entity_pos = EntityPos {
+ let entity_pos = Vec3 {
x: 31.5,
y: 80.0,
z: -16.1,
@@ -313,7 +390,7 @@ mod tests {
#[test]
fn test_from_entity_pos_to_chunk_pos() {
- let entity_pos = EntityPos {
+ let entity_pos = Vec3 {
x: 31.5,
y: 80.0,
z: -16.1,
diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs
deleted file mode 100644
index 9436d753..00000000
--- a/azalea-entity/src/lib.rs
+++ /dev/null
@@ -1,59 +0,0 @@
-mod data;
-
-use azalea_core::{EntityPos, PositionDelta};
-pub use data::*;
-use uuid::Uuid;
-
-#[derive(Default, Debug)]
-pub struct Entity {
- /// The incrementing numerical id of the entity.
- pub id: u32,
- pub uuid: Uuid,
- /// The position of the entity right now.
- pos: EntityPos,
- /// The position of the entity last tick.
- pub old_pos: EntityPos,
- pub delta: PositionDelta,
-
- pub x_rot: f32,
- pub y_rot: f32,
-}
-
-impl Entity {
- pub fn new(id: u32, uuid: Uuid, pos: EntityPos) -> Self {
- Self {
- id,
- uuid,
- pos,
- old_pos: pos,
- delta: PositionDelta::default(),
- x_rot: 0.0,
- y_rot: 0.0,
- }
- }
-
- pub fn pos(&self) -> &EntityPos {
- &self.pos
- }
-
- /// Sets the position of the entity. This doesn't update the cache in
- /// azalea-world, and should only be used within azalea-world!
- pub fn unsafe_move(&mut self, new_pos: EntityPos) {
- self.pos = new_pos;
- }
-
- pub fn set_rotation(&mut self, y_rot: f32, x_rot: f32) {
- self.y_rot = y_rot.clamp(-90.0, 90.0) % 360.0;
- self.x_rot = x_rot % 360.0;
- // TODO: minecraft also sets yRotO and xRotO to xRot and yRot ... but idk what they're used for so
- }
-}
-
-// #[cfg(test)]
-// mod tests {
-// #[test]
-// fn it_works() {
-// let result = 2 + 2;
-// assert_eq!(result, 4);
-// }
-// }
diff --git a/azalea-nbt/Cargo.toml b/azalea-nbt/Cargo.toml
index 3b334e37..4a03880c 100755
--- a/azalea-nbt/Cargo.toml
+++ b/azalea-nbt/Cargo.toml
@@ -6,7 +6,7 @@ version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-ahash = "0.7.6"
+ahash = "0.8.0"
azalea-buf = {path = "../azalea-buf"}
byteorder = "1.4.3"
flate2 = "1.0.23"
diff --git a/azalea-entity/Cargo.toml b/azalea-physics/Cargo.toml
index 022a3343..856c8ba6 100644
--- a/azalea-entity/Cargo.toml
+++ b/azalea-physics/Cargo.toml
@@ -1,13 +1,11 @@
[package]
edition = "2021"
-name = "azalea-entity"
+name = "azalea-physics"
version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-azalea-buf = {path = "../azalea-buf"}
-azalea-chat = {path = "../azalea-chat"}
+azalea-block = {path = "../azalea-block"}
azalea-core = {path = "../azalea-core"}
-azalea-nbt = {path = "../azalea-nbt"}
-uuid = "^1.1.2"
+azalea-world = {path = "../azalea-world"}
diff --git a/azalea-physics/README.md b/azalea-physics/README.md
new file mode 100644
index 00000000..21ee18ba
--- /dev/null
+++ b/azalea-physics/README.md
@@ -0,0 +1,3 @@
+# Azalea Physics
+
+Physics for Minecraft entities.
diff --git a/azalea-physics/src/collision/dimension_collisions.rs b/azalea-physics/src/collision/dimension_collisions.rs
new file mode 100644
index 00000000..59514fda
--- /dev/null
+++ b/azalea-physics/src/collision/dimension_collisions.rs
@@ -0,0 +1,137 @@
+use crate::collision::{VoxelShape, AABB};
+use azalea_block::BlockState;
+use azalea_core::{ChunkPos, ChunkSectionPos, Cursor3d, CursorIterationType, EPSILON};
+use azalea_world::entity::EntityData;
+use azalea_world::{Chunk, Dimension};
+use std::sync::{Arc, Mutex};
+
+pub trait CollisionGetter {
+ fn get_block_collisions<'a>(
+ &'a self,
+ entity: Option<&EntityData>,
+ aabb: AABB,
+ ) -> BlockCollisions<'a>;
+}
+
+impl CollisionGetter for Dimension {
+ fn get_block_collisions<'a>(
+ &'a self,
+ entity: Option<&EntityData>,
+ aabb: AABB,
+ ) -> BlockCollisions<'a> {
+ BlockCollisions::new(self, entity, aabb)
+ }
+}
+
+pub struct BlockCollisions<'a> {
+ pub dimension: &'a Dimension,
+ // context: CollisionContext,
+ pub aabb: AABB,
+
+ pub cursor: Cursor3d,
+ pub only_suffocating_blocks: bool,
+}
+
+impl<'a> BlockCollisions<'a> {
+ pub fn new(dimension: &'a Dimension, _entity: Option<&EntityData>, aabb: AABB) -> Self {
+ let origin_x = (aabb.min_x - EPSILON) as i32 - 1;
+ let origin_y = (aabb.min_y - EPSILON) as i32 - 1;
+ let origin_z = (aabb.min_z - EPSILON) as i32 - 1;
+
+ let end_x = (aabb.max_x + EPSILON) as i32 + 1;
+ let end_y = (aabb.max_y + EPSILON) as i32 + 1;
+ let end_z = (aabb.max_z + EPSILON) as i32 + 1;
+
+ let cursor = Cursor3d::new(origin_x, origin_y, origin_z, end_x, end_y, end_z);
+
+ Self {
+ dimension,
+ aabb,
+ cursor,
+ only_suffocating_blocks: false,
+ }
+ }
+
+ fn get_chunk(&self, block_x: i32, block_z: i32) -> Option<&Arc<Mutex<Chunk>>> {
+ let chunk_x = ChunkSectionPos::block_to_section_coord(block_x);
+ let chunk_z = ChunkSectionPos::block_to_section_coord(block_z);
+ let chunk_pos = ChunkPos::new(chunk_x, chunk_z);
+
+ // TODO: minecraft caches chunk here
+ // int chunkX = SectionPos.blockToSectionCoord(blockX);
+ // int chunkZ = SectionPos.blockToSectionCoord(blockZ);
+ // long chunkPosLong = ChunkPos.asLong(chunkX, chunkZ);
+ // if (this.cachedBlockGetter != null && this.cachedBlockGetterPos == var5) {
+ // return this.cachedBlockGetter;
+ // } else {
+ // BlockGetter var7 = this.collisionGetter.getChunkForCollisions(chunkX, chunkZ);
+ // this.cachedBlockGetter = var7;
+ // this.cachedBlockGetterPos = chunkPosLong;
+ // return var7;
+ // }
+
+ self.dimension[&chunk_pos].as_ref()
+ }
+}
+
+impl<'a> Iterator for BlockCollisions<'a> {
+ type Item = Box<dyn VoxelShape>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ while let Some(item) = self.cursor.next() {
+ if item.iteration_type == CursorIterationType::Corner {
+ continue;
+ }
+
+ let chunk = self.get_chunk(item.pos.x, item.pos.z);
+ let chunk = match chunk {
+ Some(chunk) => chunk,
+ None => continue,
+ };
+ let chunk_lock = chunk.lock().unwrap();
+
+ let pos = item.pos;
+ println!("getting block at {:?}", pos);
+ let block_state: BlockState = chunk_lock.get(&(&pos).into(), self.dimension.min_y());
+ // let block: Box<dyn Block> = block_state.into();
+
+ // TODO: continue if self.only_suffocating_blocks and the block is not suffocating
+
+ let block_shape = if block_state == BlockState::Air {
+ crate::collision::empty_shape()
+ } else {
+ crate::collision::block_shape()
+ };
+ // let block_shape = block.get_collision_shape();
+ // if block_shape == Shapes::block() {
+ if true {
+ // TODO: this can be optimized
+ if !self.aabb.intersects_aabb(&AABB {
+ min_x: item.pos.x as f64,
+ min_y: item.pos.y as f64,
+ min_z: item.pos.z as f64,
+ max_x: (item.pos.x + 1) as f64,
+ max_y: (item.pos.y + 1) as f64,
+ max_z: (item.pos.z + 1) as f64,
+ }) {
+ continue;
+ }
+
+ return Some(block_shape.move_relative(
+ item.pos.x as f64,
+ item.pos.y as f64,
+ item.pos.z as f64,
+ ));
+ }
+
+ // let block_shape = block_shape.move_relative(item.pos.x, item.pos.y, item.pos.z);
+ // if (!Shapes.joinIsNotEmpty(block_shape, this.entityShape, BooleanOp.AND)) {
+ // continue;
+ // }
+
+ // return block_shape;
+ }
+
+ None
+ }
+}
diff --git a/azalea-physics/src/collision/discrete_voxel_shape.rs b/azalea-physics/src/collision/discrete_voxel_shape.rs
new file mode 100644
index 00000000..6eb425ce
--- /dev/null
+++ b/azalea-physics/src/collision/discrete_voxel_shape.rs
@@ -0,0 +1,156 @@
+use azalea_core::{Axis, AxisCycle, BitSet};
+
+// TODO: every impl of DiscreteVoxelShape could be turned into a single enum as an optimization
+
+pub trait DiscreteVoxelShape {
+ fn size(&self, axis: Axis) -> u32;
+
+ fn first_full_x(&self) -> u32;
+ fn first_full_y(&self) -> u32;
+ fn first_full_z(&self) -> u32;
+
+ fn last_full_x(&self) -> u32;
+ fn last_full_y(&self) -> u32;
+ fn last_full_z(&self) -> u32;
+
+ fn is_empty(&self) -> bool {
+ if self.first_full_x() >= self.last_full_x() {
+ return true;
+ }
+ if self.first_full_y() >= self.last_full_y() {
+ return true;
+ }
+ if self.first_full_x() >= self.last_full_x() {
+ return true;
+ }
+ false
+ }
+
+ fn is_full_wide(&self, x: u32, y: u32, z: u32) -> bool {
+ (x < self.size(Axis::X) && y < self.size(Axis::Y) && z < self.size(Axis::Z))
+ && (self.is_full(x, y, z))
+ }
+ fn is_full_wide_axis_cycle(&self, axis_cycle: AxisCycle, x: u32, y: u32, z: u32) -> bool {
+ self.is_full_wide(
+ axis_cycle.cycle_xyz(x, y, z, Axis::X),
+ axis_cycle.cycle_xyz(x, y, z, Axis::Y),
+ axis_cycle.cycle_xyz(x, y, z, Axis::Z),
+ )
+ }
+
+ fn is_full(&self, x: u32, y: u32, z: u32) -> bool;
+
+ // i don't know how to do this properly
+ fn clone(&self) -> Box<dyn DiscreteVoxelShape>;
+}
+
+#[derive(Default, Clone)]
+pub struct BitSetDiscreteVoxelShape {
+ x_size: u32,
+ y_size: u32,
+ z_size: u32,
+
+ storage: BitSet,
+ x_min: u32,
+ y_min: u32,
+ z_min: u32,
+ x_max: u32,
+ y_max: u32,
+ z_max: u32,
+}
+
+impl BitSetDiscreteVoxelShape {
+ // public BitSetDiscreteVoxelShape(int var1, int var2, int var3) {
+ // super(var1, var2, var3);
+ // this.storage = new BitSet(var1 * var2 * var3);
+ // this.xMin = var1;
+ // this.yMin = var2;
+ // this.zMin = var3;
+ // }
+ pub fn new(x_min: u32, y_min: u32, z_min: u32) -> Self {
+ BitSetDiscreteVoxelShape {
+ x_size: x_min,
+ y_size: y_min,
+ z_size: z_min,
+
+ storage: BitSet::new((x_min * y_min * z_min).try_into().unwrap()),
+ x_min,
+ y_min,
+ z_min,
+ x_max: 0,
+ y_max: 0,
+ z_max: 0,
+ }
+ }
+
+ // private void fillUpdateBounds(int var1, int var2, int var3, boolean var4) {
+ // this.storage.set(this.getIndex(var1, var2, var3));
+ // if (var4) {
+ // this.xMin = Math.min(this.xMin, var1);
+ // this.yMin = Math.min(this.yMin, var2);
+ // this.zMin = Math.min(this.zMin, var3);
+ // this.xMax = Math.max(this.xMax, var1 + 1);
+ // this.yMax = Math.max(this.yMax, var2 + 1);
+ // this.zMax = Math.max(this.zMax, var3 + 1);
+ // }
+ // }
+ fn fill_update_bounds(&mut self, x: u32, y: u32, z: u32, update: bool) {
+ self.storage.set(self.get_index(x, y, z));
+ if update {
+ self.x_min = std::cmp::min(self.x_min, x);
+ self.y_min = std::cmp::min(self.y_min, y);
+ self.z_min = std::cmp::min(self.z_min, z);
+ self.x_max = std::cmp::max(self.x_max, x + 1);
+ self.y_max = std::cmp::max(self.y_max, y + 1);
+ self.z_max = std::cmp::max(self.z_max, z + 1);
+ }
+ }
+
+ // public void fill(int var1, int var2, int var3) {
+ // this.fillUpdateBounds(var1, var2, var3, true);
+ // }
+ pub fn fill(&mut self, x: u32, y: u32, z: u32) {
+ self.fill_update_bounds(x, y, z, true);
+ }
+
+ // protected int getIndex(int var1, int var2, int var3) {
+ // return (var1 * this.ySize + var2) * this.zSize + var3;
+ // }
+ fn get_index(&self, x: u32, y: u32, z: u32) -> usize {
+ ((x * self.y_size + y) * self.z_size + z) as usize
+ }
+}
+
+impl DiscreteVoxelShape for BitSetDiscreteVoxelShape {
+ fn size(&self, axis: Axis) -> u32 {
+ axis.choose(self.x_size, self.y_size, self.z_size)
+ }
+
+ fn first_full_x(&self) -> u32 {
+ self.x_min
+ }
+ fn first_full_y(&self) -> u32 {
+ self.y_min
+ }
+ fn first_full_z(&self) -> u32 {
+ self.z_min
+ }
+
+ fn last_full_x(&self) -> u32 {
+ self.x_max
+ }
+ fn last_full_y(&self) -> u32 {
+ self.y_max
+ }
+ fn last_full_z(&self) -> u32 {
+ self.z_max
+ }
+
+ fn clone(&self) -> Box<dyn DiscreteVoxelShape> {
+ Box::new(Clone::clone(self))
+ }
+
+ fn is_full(&self, x: u32, y: u32, z: u32) -> bool {
+ self.storage.index(self.get_index(x, y, z))
+ }
+}
diff --git a/azalea-physics/src/collision/mod.rs b/azalea-physics/src/collision/mod.rs
new file mode 100644
index 00000000..465698b2
--- /dev/null
+++ b/azalea-physics/src/collision/mod.rs
@@ -0,0 +1,273 @@
+mod dimension_collisions;
+mod discrete_voxel_shape;
+mod shape;
+
+use azalea_core::{Axis, PositionXYZ, Vec3, AABB, EPSILON};
+use azalea_world::entity::{EntityData, EntityMut};
+use azalea_world::{Dimension, MoveEntityError};
+use dimension_collisions::CollisionGetter;
+pub use discrete_voxel_shape::*;
+pub use shape::*;
+
+pub enum MoverType {
+ Own,
+ Player,
+ Piston,
+ ShulkerBox,
+ Shulker,
+}
+
+pub trait HasCollision {
+ fn collide(&self, movement: &Vec3, entity: &EntityData) -> Vec3;
+}
+
+pub trait MovableEntity {
+ fn move_colliding(
+ &mut self,
+ mover_type: &MoverType,
+ movement: &Vec3,
+ ) -> Result<(), MoveEntityError>;
+}
+
+impl HasCollision for Dimension {
+ // private Vec3 collide(Vec3 var1) {
+ // AABB var2 = this.getBoundingBox();
+ // List var3 = this.level.getEntityCollisions(this, var2.expandTowards(var1));
+ // Vec3 var4 = var1.lengthSqr() == 0.0D ? var1 : collideBoundingBox(this, var1, var2, this.level, var3);
+ // boolean var5 = var1.x != var4.x;
+ // boolean var6 = var1.y != var4.y;
+ // boolean var7 = var1.z != var4.z;
+ // boolean var8 = this.onGround || var6 && var1.y < 0.0D;
+ // if (this.maxUpStep > 0.0F && var8 && (var5 || var7)) {
+ // Vec3 var9 = collideBoundingBox(this, new Vec3(var1.x, (double)this.maxUpStep, var1.z), var2, this.level, var3);
+ // Vec3 var10 = collideBoundingBox(this, new Vec3(0.0D, (double)this.maxUpStep, 0.0D), var2.expandTowards(var1.x, 0.0D, var1.z), this.level, var3);
+ // if (var10.y < (double)this.maxUpStep) {
+ // Vec3 var11 = collideBoundingBox(this, new Vec3(var1.x, 0.0D, var1.z), var2.move(var10), this.level, var3).add(var10);
+ // if (var11.horizontalDistanceSqr() > var9.horizontalDistanceSqr()) {
+ // var9 = var11;
+ // }
+ // }
+
+ // if (var9.horizontalDistanceSqr() > var4.horizontalDistanceSqr()) {
+ // return var9.add(collideBoundingBox(this, new Vec3(0.0D, -var9.y + var1.y, 0.0D), var2.move(var9), this.level, var3));
+ // }
+ // }
+
+ // return var4;
+ // }
+ fn collide(&self, movement: &Vec3, entity: &EntityData) -> Vec3 {
+ let entity_bounding_box = entity.bounding_box;
+ println!("collide: entity_bounding_box: {:?}", entity_bounding_box);
+ // TODO: get_entity_collisions
+ // let entity_collisions = dimension.get_entity_collisions(self, entity_bounding_box.expand_towards(movement));
+ let entity_collisions = Vec::new();
+ if movement.length_sqr() == 0.0 {
+ *movement
+ } else {
+ collide_bounding_box(
+ Some(entity),
+ movement,
+ &entity_bounding_box,
+ self,
+ entity_collisions,
+ )
+ }
+
+ // TODO: stepping (for stairs and stuff)
+
+ // collided_movement
+ }
+}
+
+impl MovableEntity for EntityMut<'_> {
+ /// Move an entity by a given delta, checking for collisions.
+ fn move_colliding(
+ &mut self,
+ _mover_type: &MoverType,
+ movement: &Vec3,
+ ) -> Result<(), MoveEntityError> {
+ // TODO: do all these
+
+ // if self.no_physics {
+ // return;
+ // };
+
+ // if (var1 == MoverType.PISTON) {
+ // var2 = this.limitPistonMovement(var2);
+ // if (var2.equals(Vec3.ZERO)) {
+ // return;
+ // }
+ // }
+
+ // if (this.stuckSpeedMultiplier.lengthSqr() > 1.0E-7D) {
+ // var2 = var2.multiply(this.stuckSpeedMultiplier);
+ // this.stuckSpeedMultiplier = Vec3.ZERO;
+ // this.setDeltaMovement(Vec3.ZERO);
+ // }
+
+ // movement = this.maybeBackOffFromEdge(movement, moverType);
+
+ println!("move_entity {:?}", movement);
+
+ let collide_result = { self.dimension.collide(movement, self) };
+
+ let move_distance = collide_result.length_sqr();
+
+ println!("move_entity move_distance: {}", move_distance);
+
+ if move_distance > EPSILON {
+ // TODO: fall damage
+
+ let new_pos = {
+ let entity_pos = self.pos();
+ Vec3 {
+ x: entity_pos.x + collide_result.x,
+ y: entity_pos.y + collide_result.y,
+ z: entity_pos.z + collide_result.z,
+ }
+ };
+
+ self.dimension.set_entity_pos(self.id, new_pos)?;
+
+ println!("move_entity set_entity_pos {:?}", new_pos)
+ }
+
+ let x_collision = movement.x != collide_result.x;
+ let z_collision = movement.z != collide_result.z;
+ let horizontal_collision = x_collision || z_collision;
+ let vertical_collision = movement.y != collide_result.y;
+ let on_ground = vertical_collision && movement.y < 0.;
+ // self.on_ground = on_ground;
+
+ println!(
+ "move_entity {} {} {}",
+ x_collision, z_collision, vertical_collision
+ );
+
+ // TODO: minecraft checks for a "minor" horizontal collision here
+
+ let block_pos_below = { self.on_pos_legacy() };
+ let _block_state_below = self
+ .dimension
+ .get_block_state(&block_pos_below)
+ .expect("Couldn't get block state below");
+
+ println!("move_entity 4");
+ // self.check_fall_damage(collide_result.y, on_ground, block_state_below, block_pos_below);
+
+ // if self.isRemoved() { return; }
+
+ if horizontal_collision {
+ let delta_movement = &self.delta;
+ self.delta = Vec3 {
+ x: if x_collision { 0. } else { delta_movement.x },
+ y: delta_movement.y,
+ z: if z_collision { 0. } else { delta_movement.z },
+ }
+ }
+
+ if vertical_collision {
+ // blockBelow.updateEntityAfterFallOn(this.level, this);
+ }
+
+ if on_ground {
+ // blockBelow.stepOn(this.level, blockPosBelow, blockStateBelow, this);
+ }
+
+ // sounds
+
+ // this.tryCheckInsideBlocks();
+
+ // float var25 = this.getBlockSpeedFactor();
+ // this.setDeltaMovement(this.getDeltaMovement().multiply((double)var25, 1.0D, (double)var25));
+ // if (this.level.getBlockStatesIfLoaded(this.getBoundingBox().deflate(1.0E-6D)).noneMatch((var0) -> {
+ // return var0.is(BlockTags.FIRE) || var0.is(Blocks.LAVA);
+ // })) {
+ // if (this.remainingFireTicks <= 0) {
+ // this.setRemainingFireTicks(-this.getFireImmuneTicks());
+ // }
+
+ // if (this.wasOnFire && (this.isInPowderSnow || this.isInWaterRainOrBubble())) {
+ // this.playEntityOnFireExtinguishedSound();
+ // }
+ // }
+
+ // if (this.isOnFire() && (this.isInPowderSnow || this.isInWaterRainOrBubble())) {
+ // this.setRemainingFireTicks(-this.getFireImmuneTicks());
+ // }
+
+ println!("move_entity 5");
+
+ Ok(())
+ }
+}
+
+fn collide_bounding_box(
+ entity: Option<&EntityData>,
+ movement: &Vec3,
+ entity_bounding_box: &AABB,
+ dimension: &Dimension,
+ entity_collisions: Vec<Box<dyn VoxelShape>>,
+) -> Vec3 {
+ let mut collision_boxes: Vec<Box<dyn VoxelShape>> =
+ Vec::with_capacity(entity_collisions.len() + 1);
+
+ if !entity_collisions.is_empty() {
+ collision_boxes.extend(entity_collisions);
+ }
+
+ // TODO: world border
+
+ let block_collisions =
+ dimension.get_block_collisions(entity, entity_bounding_box.expand_towards(movement));
+ collision_boxes.extend(block_collisions);
+ collide_with_shapes(movement, *entity_bounding_box, &collision_boxes)
+}
+
+fn collide_with_shapes(
+ movement: &Vec3,
+ mut entity_box: AABB,
+ collision_boxes: &Vec<Box<dyn VoxelShape>>,
+) -> Vec3 {
+ if collision_boxes.is_empty() {
+ return *movement;
+ }
+
+ let mut x_movement = movement.x;
+ let mut y_movement = movement.y;
+ let mut z_movement = movement.z;
+ if y_movement != 0. {
+ y_movement = Shapes::collide(&Axis::Y, &entity_box, collision_boxes, y_movement);
+ if y_movement != 0. {
+ entity_box = entity_box.move_relative(0., y_movement, 0.);
+ }
+ }
+
+ // whether the player is moving more in the z axis than x
+ // this is done to fix a movement bug, minecraft does this too
+ let more_z_movement = x_movement.abs() < z_movement.abs();
+
+ if more_z_movement && z_movement != 0. {
+ z_movement = Shapes::collide(&Axis::Z, &entity_box, collision_boxes, z_movement);
+ if z_movement != 0. {
+ entity_box = entity_box.move_relative(0., 0., z_movement);
+ }
+ }
+
+ if x_movement != 0. {
+ x_movement = Shapes::collide(&Axis::X, &entity_box, collision_boxes, x_movement);
+ if x_movement != 0. {
+ entity_box = entity_box.move_relative(x_movement, 0., 0.);
+ }
+ }
+
+ if !more_z_movement && z_movement != 0. {
+ z_movement = Shapes::collide(&Axis::Z, &entity_box, collision_boxes, z_movement);
+ }
+
+ Vec3 {
+ x: x_movement,
+ y: y_movement,
+ z: z_movement,
+ }
+}
diff --git a/azalea-physics/src/collision/shape.rs b/azalea-physics/src/collision/shape.rs
new file mode 100644
index 00000000..45822d07
--- /dev/null
+++ b/azalea-physics/src/collision/shape.rs
@@ -0,0 +1,254 @@
+use crate::collision::{BitSetDiscreteVoxelShape, DiscreteVoxelShape, AABB};
+use azalea_core::{binary_search, Axis, AxisCycle, EPSILON};
+use std::cmp;
+
+pub struct Shapes {}
+
+pub fn block_shape() -> Box<dyn VoxelShape> {
+ let mut shape = BitSetDiscreteVoxelShape::new(1, 1, 1);
+ shape.fill(0, 0, 0);
+ Box::new(CubeVoxelShape::new(Box::new(shape)))
+}
+pub fn empty_shape() -> Box<dyn VoxelShape> {
+ Box::new(ArrayVoxelShape::new(
+ Box::new(BitSetDiscreteVoxelShape::new(0, 0, 0)),
+ vec![0.],
+ vec![0.],
+ vec![0.],
+ ))
+}
+
+impl Shapes {
+ pub fn collide(
+ axis: &Axis,
+ entity_box: &AABB,
+ collision_boxes: &Vec<Box<dyn VoxelShape>>,
+ mut movement: f64,
+ ) -> f64 {
+ for shape in collision_boxes {
+ if movement.abs() < EPSILON {
+ return 0.;
+ }
+ movement = shape.collide(axis, entity_box, movement);
+ }
+ movement
+ }
+}
+
+pub trait VoxelShape {
+ fn shape(&self) -> Box<dyn DiscreteVoxelShape>;
+
+ fn get_coords(&self, axis: Axis) -> Vec<f64>;
+
+ // TODO: optimization: should this be changed to return ArrayVoxelShape?
+ // i might change the implementation of empty_shape in the future so not 100% sure
+ fn move_relative(&self, x: f64, y: f64, z: f64) -> Box<dyn VoxelShape> {
+ if self.shape().is_empty() {
+ return empty_shape();
+ }
+
+ println!(
+ "making new voxel shape {:?} {:?} {:?}",
+ self.get_coords(Axis::X),
+ self.get_coords(Axis::Y),
+ self.get_coords(Axis::Z)
+ );
+
+ Box::new(ArrayVoxelShape::new(
+ self.shape(),
+ self.get_coords(Axis::X).iter().map(|c| c + x).collect(),
+ self.get_coords(Axis::Y).iter().map(|c| c + y).collect(),
+ self.get_coords(Axis::Z).iter().map(|c| c + z).collect(),
+ ))
+ }
+
+ fn get(&self, axis: Axis, index: usize) -> f64 {
+ self.get_coords(axis)[index]
+ }
+
+ fn find_index(&self, axis: Axis, coord: f64) -> u32 {
+ binary_search(0, self.shape().size(axis) + 1, &|t| {
+ coord < self.get(axis, t as usize)
+ }) - 1
+ }
+
+ fn collide(&self, axis: &Axis, entity_box: &AABB, movement: f64) -> f64 {
+ self.collide_x(AxisCycle::between(*axis, Axis::X), entity_box, movement)
+ }
+ fn collide_x(&self, axis_cycle: AxisCycle, entity_box: &AABB, mut movement: f64) -> f64 {
+ if self.shape().is_empty() {
+ return movement;
+ }
+ if movement.abs() < EPSILON {
+ return 0.;
+ }
+
+ let inverse_axis_cycle = axis_cycle.inverse();
+
+ // probably not good names but idk what this does
+ let x_axis = inverse_axis_cycle.cycle(Axis::X);
+ let y_axis = inverse_axis_cycle.cycle(Axis::Y);
+ let z_axis = inverse_axis_cycle.cycle(Axis::Z);
+
+ // i gave up on names at this point (these are the obfuscated names from fernflower)
+ let var9 = entity_box.max(&x_axis);
+ let var11 = entity_box.min(&x_axis);
+
+ let var13 = self.find_index(x_axis, var11 + EPSILON);
+ let var14 = self.find_index(x_axis, var9 - EPSILON);
+
+ let var15 = cmp::max(
+ 0,
+ self.find_index(y_axis, entity_box.min(&y_axis) + EPSILON),
+ );
+ let var16 = cmp::min(
+ self.shape().size(y_axis),
+ self.find_index(y_axis, entity_box.max(&y_axis) - EPSILON) + 1,
+ );
+
+ let var17 = cmp::max(
+ 0,
+ self.find_index(z_axis, entity_box.min(&z_axis) + EPSILON),
+ );
+ let var18 = cmp::min(
+ self.shape().size(z_axis),
+ self.find_index(z_axis, entity_box.max(&z_axis) - EPSILON) + 1,
+ );
+
+ let var19 = self.shape().size(x_axis);
+
+ if movement > 0. {
+ for var20 in var14 + 1..var19 {
+ for var21 in var15..var16 {
+ for var22 in var17..var18 {
+ if self.shape().is_full_wide_axis_cycle(
+ inverse_axis_cycle,
+ var20,
+ var21,
+ var22,
+ ) {
+ let var23 = self.get(x_axis, var20 as usize) - var9;
+ if var23 >= -EPSILON {
+ movement = f64::min(movement, var23);
+ }
+ return movement;
+ }
+ }
+ }
+ }
+ } else if movement < 0. {
+ for var20 in (var13 - 1)..=0 {
+ for var21 in var15..var16 {
+ for var22 in var17..var18 {
+ if self.shape().is_full_wide_axis_cycle(
+ inverse_axis_cycle,
+ var20,
+ var21,
+ var22,
+ ) {
+ let var23 = self.get(x_axis, (var20 + 1) as usize) - var11;
+ if var23 <= EPSILON {
+ movement = f64::max(movement, var23);
+ }
+ return movement;
+ }
+ }
+ }
+ }
+ }
+
+ movement
+ }
+}
+
+pub struct ArrayVoxelShape {
+ shape: Box<dyn DiscreteVoxelShape>,
+ // TODO: check where faces is used in minecraft
+ #[allow(dead_code)]
+ faces: Option<Vec<Box<dyn VoxelShape>>>,
+
+ pub xs: Vec<f64>,
+ pub ys: Vec<f64>,
+ pub zs: Vec<f64>,
+}
+
+pub struct CubeVoxelShape {
+ shape: Box<dyn DiscreteVoxelShape>,
+ // TODO: check where faces is used in minecraft
+ #[allow(dead_code)]
+ faces: Option<Vec<Box<dyn VoxelShape>>>,
+}
+
+impl ArrayVoxelShape {
+ pub fn new(
+ shape: Box<dyn DiscreteVoxelShape>,
+ xs: Vec<f64>,
+ ys: Vec<f64>,
+ zs: Vec<f64>,
+ ) -> Self {
+ let x_size = shape.size(Axis::X) + 1;
+ let y_size = shape.size(Axis::Y) + 1;
+ let z_size = shape.size(Axis::Z) + 1;
+
+ // Lengths of point arrays must be consistent with the size of the VoxelShape.
+ assert_eq!(x_size, xs.len() as u32);
+ assert_eq!(y_size, ys.len() as u32);
+ assert_eq!(z_size, zs.len() as u32);
+
+ Self {
+ faces: None,
+ shape,
+ xs,
+ ys,
+ zs,
+ }
+ }
+}
+
+impl CubeVoxelShape {
+ pub fn new(shape: Box<dyn DiscreteVoxelShape>) -> Self {
+ Self { shape, faces: None }
+ }
+}
+
+impl VoxelShape for ArrayVoxelShape {
+ fn shape(&self) -> Box<dyn DiscreteVoxelShape> {
+ self.shape.clone()
+ }
+
+ fn get_coords(&self, axis: Axis) -> Vec<f64> {
+ axis.choose(self.xs.clone(), self.ys.clone(), self.zs.clone())
+ }
+}
+
+impl VoxelShape for CubeVoxelShape {
+ fn shape(&self) -> Box<dyn DiscreteVoxelShape> {
+ self.shape.clone()
+ }
+
+ fn get_coords(&self, axis: Axis) -> Vec<f64> {
+ let size = self.shape.size(axis);
+ let mut parts = Vec::with_capacity(size as usize);
+ for i in 0..=size {
+ parts.push(i as f64 / size as f64);
+ }
+ parts
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_block_shape() {
+ let shape = block_shape();
+ assert_eq!(shape.shape().size(Axis::X), 1);
+ assert_eq!(shape.shape().size(Axis::Y), 1);
+ assert_eq!(shape.shape().size(Axis::Z), 1);
+
+ assert_eq!(shape.get_coords(Axis::X).len(), 2);
+ assert_eq!(shape.get_coords(Axis::Y).len(), 2);
+ assert_eq!(shape.get_coords(Axis::Z).len(), 2);
+ }
+}
diff --git a/azalea-physics/src/lib.rs b/azalea-physics/src/lib.rs
new file mode 100644
index 00000000..8842727b
--- /dev/null
+++ b/azalea-physics/src/lib.rs
@@ -0,0 +1,127 @@
+pub mod collision;
+
+use azalea_block::Block;
+use azalea_core::{BlockPos, Vec3};
+use azalea_world::entity::{EntityData, EntityMut};
+use collision::{MovableEntity, MoverType};
+
+pub trait HasPhysics {
+ fn travel(&mut self, acceleration: &Vec3);
+ fn ai_step(&mut self);
+}
+
+impl HasPhysics for EntityMut<'_> {
+ /// Move the entity with the given acceleration while handling friction,
+ /// gravity, collisions, and some other stuff.
+ fn travel(&mut self, acceleration: &Vec3) {
+ // if !self.is_effective_ai() && !self.is_controlled_by_local_instance() {
+ // // this.calculateEntityAnimation(this, this instanceof FlyingAnimal);
+ // return;
+ // }
+
+ let gravity: f64 = 0.08;
+
+ // TODO: slow falling effect
+ // let is_falling = self.delta.y <= 0.;
+
+ // TODO: fluids
+
+ // TODO: elytra
+
+ let block_pos_below = get_block_pos_below_that_affects_movement(self);
+ let block_friction =
+ if let Some(block_state_below) = self.dimension.get_block_state(&block_pos_below) {
+ let block_below: Box<dyn Block> = block_state_below.into();
+ block_below.behavior().friction
+ } else {
+ unreachable!("Block below should be a real block.")
+ };
+
+ let inertia = if self.on_ground {
+ block_friction * 0.91
+ } else {
+ 0.91
+ };
+ let mut movement =
+ handle_relative_friction_and_calculate_movement(self, acceleration, block_friction);
+
+ movement.y -= gravity;
+
+ // if (this.shouldDiscardFriction()) {
+ // this.setDeltaMovement(movement.x, yMovement, movement.z);
+ // } else {
+ // this.setDeltaMovement(movement.x * (double)inertia, yMovement * 0.9800000190734863D, movement.z * (double)inertia);
+ // }
+
+ // if should_discard_friction(self) {
+ if false {
+ self.delta = movement;
+ } else {
+ self.delta = Vec3 {
+ x: movement.x * inertia as f64,
+ y: movement.y * 0.98f64,
+ z: movement.z * inertia as f64,
+ };
+ }
+ }
+
+ /// applies air resistance, calls self.travel(), and some other random
+ /// stuff.
+ fn ai_step(&mut self) {
+ // vanilla does movement interpolation here, doesn't really matter much for a bot though
+
+ self.xxa *= 0.98;
+ self.zza *= 0.98;
+
+ self.travel(&Vec3 {
+ x: self.xxa as f64,
+ y: self.yya as f64,
+ z: self.zza as f64,
+ });
+ // freezing
+ // pushEntities
+ // drowning damage
+ }
+}
+
+fn get_block_pos_below_that_affects_movement(entity: &EntityData) -> BlockPos {
+ BlockPos::new(
+ entity.pos().x as i32,
+ // TODO: this uses bounding_box.min_y instead of position.y
+ (entity.pos().y - 0.5f64) as i32,
+ entity.pos().z as i32,
+ )
+}
+
+fn handle_relative_friction_and_calculate_movement(
+ entity: &mut EntityMut,
+ acceleration: &Vec3,
+ block_friction: f32,
+) -> Vec3 {
+ entity.move_relative(get_speed(&*entity, block_friction), acceleration);
+ // entity.delta = entity.handle_on_climbable(entity.delta);
+ entity
+ .move_colliding(&MoverType::Own, &entity.delta.clone())
+ .expect("Entity should exist.");
+ // let delta_movement = entity.delta;
+ // if ((entity.horizontalCollision || entity.jumping) && (entity.onClimbable() || entity.getFeetBlockState().is(Blocks.POWDER_SNOW) && PowderSnowBlock.canEntityWalkOnPowderSnow(entity))) {
+ // var3 = new Vec3(var3.x, 0.2D, var3.z);
+ // }
+ // TODO: powdered snow
+
+ entity.delta
+}
+
+// private float getFrictionInfluencedSpeed(float friction) {
+// return this.onGround ? this.getSpeed() * (0.21600002F / (friction * friction * friction)) : this.flyingSpeed;
+// }
+fn get_speed(entity: &EntityData, friction: f32) -> f32 {
+ // TODO: have speed & flying_speed fields in entity
+ if entity.on_ground {
+ let speed: f32 = 0.7;
+ speed * (0.216f32 / (friction * friction * friction))
+ } else {
+ // entity.flying_speed
+ 0.02
+ }
+}
diff --git a/azalea-protocol/Cargo.toml b/azalea-protocol/Cargo.toml
index bd475676..ab9b322f 100755
--- a/azalea-protocol/Cargo.toml
+++ b/azalea-protocol/Cargo.toml
@@ -14,8 +14,8 @@ azalea-buf = {path = "../azalea-buf"}
azalea-chat = {path = "../azalea-chat"}
azalea-core = {path = "../azalea-core", optional = true}
azalea-crypto = {path = "../azalea-crypto"}
-azalea-entity = {path = "../azalea-entity"}
azalea-nbt = {path = "../azalea-nbt"}
+azalea-world = {path = "../azalea-world"}
byteorder = "^1.4.3"
bytes = "^1.1.0"
flate2 = "1.0.23"
diff --git a/azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs b/azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs
index b79646c0..4b26efe7 100644
--- a/azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs
@@ -1,6 +1,6 @@
use azalea_buf::McBuf;
-use azalea_core::EntityPos;
-use azalea_entity::Entity;
+use azalea_core::Vec3;
+use azalea_world::entity::EntityData;
use packet_macros::ClientboundGamePacket;
use uuid::Uuid;
@@ -26,12 +26,11 @@ pub struct ClientboundAddEntityPacket {
pub z_vel: i16,
}
-impl From<&ClientboundAddEntityPacket> for Entity {
+impl From<&ClientboundAddEntityPacket> for EntityData {
fn from(p: &ClientboundAddEntityPacket) -> Self {
Self::new(
- p.id,
p.uuid,
- EntityPos {
+ Vec3 {
x: p.x,
y: p.y,
z: p.z,
diff --git a/azalea-protocol/src/packets/game/clientbound_add_player_packet.rs b/azalea-protocol/src/packets/game/clientbound_add_player_packet.rs
index 2e450084..cd07d033 100644
--- a/azalea-protocol/src/packets/game/clientbound_add_player_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_add_player_packet.rs
@@ -1,6 +1,6 @@
use azalea_buf::McBuf;
-use azalea_core::EntityPos;
-use azalea_entity::Entity;
+use azalea_core::Vec3;
+use azalea_world::entity::EntityData;
use packet_macros::ClientboundGamePacket;
use uuid::Uuid;
@@ -17,12 +17,11 @@ pub struct ClientboundAddPlayerPacket {
pub y_rot: i8,
}
-impl From<&ClientboundAddPlayerPacket> for Entity {
+impl From<&ClientboundAddPlayerPacket> for EntityData {
fn from(p: &ClientboundAddPlayerPacket) -> Self {
Self::new(
- p.id,
p.uuid,
- EntityPos {
+ Vec3 {
x: p.x,
y: p.y,
z: p.z,
diff --git a/azalea-protocol/src/packets/game/clientbound_light_update_packet.rs b/azalea-protocol/src/packets/game/clientbound_light_update_packet.rs
index 038e6202..e7fa936f 100644
--- a/azalea-protocol/src/packets/game/clientbound_light_update_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_light_update_packet.rs
@@ -1,4 +1,5 @@
-use azalea_buf::{BitSet, McBuf};
+use azalea_buf::McBuf;
+use azalea_core::BitSet;
use packet_macros::ClientboundGamePacket;
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
diff --git a/azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs b/azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs
index 7414e10c..492f352f 100644
--- a/azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs
@@ -1,6 +1,8 @@
-use azalea_buf::{BitSet, BufReadError, McBuf, McBufReadable, McBufVarWritable};
-use azalea_buf::{McBufVarReadable, McBufWritable};
+use azalea_buf::{
+ BufReadError, McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable,
+};
use azalea_chat::component::Component;
+use azalea_core::BitSet;
use azalea_crypto::{MessageSignature, SignedMessageHeader};
use packet_macros::ClientboundGamePacket;
use std::io::{Read, Write};
diff --git a/azalea-protocol/src/packets/game/clientbound_player_info_packet.rs b/azalea-protocol/src/packets/game/clientbound_player_info_packet.rs
index d53774b7..4c61d5c7 100644
--- a/azalea-protocol/src/packets/game/clientbound_player_info_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_player_info_packet.rs
@@ -1,3 +1,4 @@
+use crate::packets::login::serverbound_hello_packet::ProfilePublicKeyData;
use azalea_buf::{BufReadError, McBuf};
use azalea_buf::{McBufReadable, McBufWritable, Readable, Writable};
use azalea_chat::component::Component;
@@ -28,14 +29,15 @@ pub struct PlayerProperty {
#[derive(Clone, Debug, McBuf)]
pub struct AddPlayer {
- uuid: Uuid,
- name: String,
- properties: Vec<PlayerProperty>,
+ pub uuid: Uuid,
+ pub name: String,
+ pub properties: Vec<PlayerProperty>,
#[var]
- gamemode: u32,
+ pub gamemode: u32,
#[var]
- ping: i32,
- display_name: Option<Component>,
+ pub ping: i32,
+ pub display_name: Option<Component>,
+ pub profile_public_key: Option<ProfilePublicKeyData>,
}
#[derive(Clone, Debug, McBuf)]
diff --git a/azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs
index 7468fc91..35319acc 100644
--- a/azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs
@@ -1,5 +1,5 @@
use azalea_buf::McBuf;
-use azalea_entity::EntityMetadata;
+use azalea_world::entity::EntityMetadata;
use packet_macros::ClientboundGamePacket;
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
diff --git a/azalea-protocol/src/packets/game/mod.rs b/azalea-protocol/src/packets/game/mod.rs
index 194c7680..ca647241 100755..100644
--- a/azalea-protocol/src/packets/game/mod.rs
+++ b/azalea-protocol/src/packets/game/mod.rs
@@ -183,10 +183,10 @@ declare_state_packets!(
0x11: serverbound_jigsaw_generate_packet::ServerboundJigsawGeneratePacket,
0x12: serverbound_keep_alive_packet::ServerboundKeepAlivePacket,
0x13: serverbound_lock_difficulty_packet::ServerboundLockDifficultyPacket,
- 0x14: serverbound_move_player_pos_packet::ServerboundMovePlayerPacketPos,
- 0x15: serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPacketPosRot,
- 0x16: serverbound_move_player_rot_packet::ServerboundMovePlayerPacketRot,
- 0x17: serverbound_move_player_status_only_packet::ServerboundMovePlayerPacketStatusOnly,
+ 0x14: serverbound_move_player_pos_packet::ServerboundMovePlayerPosPacket,
+ 0x15: serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket,
+ 0x16: serverbound_move_player_rot_packet::ServerboundMovePlayerRotPacket,
+ 0x17: serverbound_move_player_status_only_packet::ServerboundMovePlayerStatusOnlyPacket,
0x18: serverbound_move_vehicle_packet::ServerboundMoveVehiclePacket,
0x19: serverbound_paddle_boat_packet::ServerboundPaddleBoatPacket,
0x1a: serverbound_pick_item_packet::ServerboundPickItemPacket,
diff --git a/azalea-protocol/src/packets/game/serverbound_interact_packet.rs b/azalea-protocol/src/packets/game/serverbound_interact_packet.rs
index 47843d47..7f54bd44 100644
--- a/azalea-protocol/src/packets/game/serverbound_interact_packet.rs
+++ b/azalea-protocol/src/packets/game/serverbound_interact_packet.rs
@@ -1,7 +1,6 @@
use crate::packets::BufReadError;
-use azalea_buf::McBufVarReadable;
-use azalea_buf::{McBuf, McBufReadable, McBufVarWritable, McBufWritable};
-use azalea_core::EntityPos;
+use azalea_buf::{McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable};
+use azalea_core::Vec3;
use packet_macros::ServerboundGamePacket;
use std::io::{Read, Write};
@@ -21,7 +20,7 @@ pub enum ActionType {
},
Attack,
InteractAt {
- location: EntityPos,
+ location: Vec3,
hand: InteractionHand,
},
}
@@ -63,7 +62,7 @@ impl McBufReadable for ActionType {
let z = f32::read_from(buf)?;
let hand = InteractionHand::read_from(buf)?;
Ok(ActionType::InteractAt {
- location: EntityPos {
+ location: Vec3 {
x: x as f64,
y: y as f64,
z: z as f64,
diff --git a/azalea-protocol/src/packets/game/serverbound_move_player_pos_packet.rs b/azalea-protocol/src/packets/game/serverbound_move_player_pos_packet.rs
index 9e70eec6..aac85d6b 100644
--- a/azalea-protocol/src/packets/game/serverbound_move_player_pos_packet.rs
+++ b/azalea-protocol/src/packets/game/serverbound_move_player_pos_packet.rs
@@ -2,7 +2,7 @@ use azalea_buf::McBuf;
use packet_macros::ServerboundGamePacket;
#[derive(Clone, Debug, McBuf, ServerboundGamePacket)]
-pub struct ServerboundMovePlayerPacketPos {
+pub struct ServerboundMovePlayerPosPacket {
pub x: f64,
pub y: f64,
pub z: f64,
diff --git a/azalea-protocol/src/packets/game/serverbound_move_player_pos_rot_packet.rs b/azalea-protocol/src/packets/game/serverbound_move_player_pos_rot_packet.rs
index 6933a724..a1ee359f 100644
--- a/azalea-protocol/src/packets/game/serverbound_move_player_pos_rot_packet.rs
+++ b/azalea-protocol/src/packets/game/serverbound_move_player_pos_rot_packet.rs
@@ -2,7 +2,7 @@ use azalea_buf::McBuf;
use packet_macros::ServerboundGamePacket;
#[derive(Clone, Debug, McBuf, ServerboundGamePacket)]
-pub struct ServerboundMovePlayerPacketPosRot {
+pub struct ServerboundMovePlayerPosRotPacket {
pub x: f64,
pub y: f64,
pub z: f64,
diff --git a/azalea-protocol/src/packets/game/serverbound_move_player_rot_packet.rs b/azalea-protocol/src/packets/game/serverbound_move_player_rot_packet.rs
index 493c5eab..86a4669f 100644
--- a/azalea-protocol/src/packets/game/serverbound_move_player_rot_packet.rs
+++ b/azalea-protocol/src/packets/game/serverbound_move_player_rot_packet.rs
@@ -2,7 +2,7 @@ use azalea_buf::McBuf;
use packet_macros::ServerboundGamePacket;
#[derive(Clone, Debug, McBuf, ServerboundGamePacket)]
-pub struct ServerboundMovePlayerPacketRot {
+pub struct ServerboundMovePlayerRotPacket {
pub y_rot: f32,
pub x_rot: f32,
pub on_ground: bool,
diff --git a/azalea-protocol/src/packets/game/serverbound_move_player_status_only_packet.rs b/azalea-protocol/src/packets/game/serverbound_move_player_status_only_packet.rs
index 8b08154b..64ba93ae 100644
--- a/azalea-protocol/src/packets/game/serverbound_move_player_status_only_packet.rs
+++ b/azalea-protocol/src/packets/game/serverbound_move_player_status_only_packet.rs
@@ -2,6 +2,6 @@ use azalea_buf::McBuf;
use packet_macros::ServerboundGamePacket;
#[derive(Clone, Debug, McBuf, ServerboundGamePacket)]
-pub struct ServerboundMovePlayerPacketStatusOnly {
+pub struct ServerboundMovePlayerStatusOnlyPacket {
pub on_ground: bool,
}
diff --git a/azalea-protocol/src/packets/game/serverbound_use_item_on_packet.rs b/azalea-protocol/src/packets/game/serverbound_use_item_on_packet.rs
index f2fb1b2a..39762c2e 100644
--- a/azalea-protocol/src/packets/game/serverbound_use_item_on_packet.rs
+++ b/azalea-protocol/src/packets/game/serverbound_use_item_on_packet.rs
@@ -1,6 +1,6 @@
use crate::packets::game::serverbound_interact_packet::InteractionHand;
use azalea_buf::{BufReadError, McBuf, McBufReadable, McBufWritable};
-use azalea_core::{BlockPos, Direction, EntityPos};
+use azalea_core::{BlockPos, Direction, Vec3};
use packet_macros::ServerboundGamePacket;
use std::io::{Read, Write};
@@ -16,7 +16,7 @@ pub struct ServerboundUseItemOnPacket {
pub struct BlockHitResult {
pub block_pos: BlockPos,
pub direction: Direction,
- pub location: EntityPos,
+ pub location: Vec3,
pub inside: bool,
}
@@ -43,7 +43,7 @@ impl McBufReadable for BlockHitResult {
Ok(Self {
block_pos,
direction,
- location: EntityPos {
+ location: Vec3 {
x: block_pos.x as f64 + cursor_x as f64,
y: block_pos.y as f64 + cursor_y as f64,
z: block_pos.z as f64 + cursor_z as f64,
diff --git a/azalea-world/Cargo.toml b/azalea-world/Cargo.toml
index 250a7bd9..524616e6 100644
--- a/azalea-world/Cargo.toml
+++ b/azalea-world/Cargo.toml
@@ -8,8 +8,8 @@ version = "0.1.0"
[dependencies]
azalea-block = {path = "../azalea-block", default-features = false}
azalea-buf = {path = "../azalea-buf"}
+azalea-chat = {path = "../azalea-chat"}
azalea-core = {path = "../azalea-core"}
-azalea-entity = {path = "../azalea-entity"}
azalea-nbt = {path = "../azalea-nbt"}
log = "0.4.17"
nohash-hasher = "0.2.0"
diff --git a/azalea-world/src/bit_storage.rs b/azalea-world/src/bit_storage.rs
index fcb3f8f9..2626c312 100644
--- a/azalea-world/src/bit_storage.rs
+++ b/azalea-world/src/bit_storage.rs
@@ -103,16 +103,21 @@ impl BitStorage {
/// Create a new BitStorage with the given number of bits per entry.
/// `size` is the number of entries in the BitStorage.
pub fn new(bits: usize, size: usize, data: Option<Vec<u64>>) -> Result<Self, BitStorageError> {
- // vanilla has this assert but it's not always true for some reason??
- // assert!(bits >= 1 && bits <= 32);
-
if let Some(data) = &data {
+ // 0 bit storage
if data.is_empty() {
- // TODO: make 0 bit storage actually work
- return Ok(BitStorage::default());
+ return Ok(BitStorage {
+ data: Vec::with_capacity(0),
+ bits,
+ size,
+ ..Default::default()
+ });
}
}
+ // vanilla has this assert but it's not always true for some reason??
+ // assert!(bits >= 1 && bits <= 32);
+
let values_per_long = 64 / bits;
let magic_index = values_per_long - 1;
let (divide_mul, divide_add, divide_shift) = MAGIC[magic_index as usize];
@@ -163,23 +168,43 @@ impl BitStorage {
assert!(
index < self.size,
- "Index {} out of bounds (max is {})",
+ "Index {} out of bounds (must be less than {})",
index,
- self.size - 1
+ self.size
);
+
+ // 0 bit storage
+ if self.data.is_empty() {
+ return 0;
+ }
+
let cell_index = self.cell_index(index as u64);
let cell = &self.data[cell_index as usize];
let bit_index = (index - cell_index * self.values_per_long as usize) * self.bits;
cell >> bit_index & self.mask
}
+ pub fn get_and_set(&mut self, index: usize, value: u64) -> u64 {
+ // 0 bit storage
+ if self.data.is_empty() {
+ return 0;
+ }
+
+ assert!(index < self.size);
+ assert!(value <= self.mask);
+ let cell_index = self.cell_index(index as u64);
+ let cell = &mut self.data[cell_index as usize];
+ let bit_index = (index - cell_index * self.values_per_long as usize) * self.bits;
+ let old_value = *cell >> (bit_index as u64) & self.mask;
+ *cell = *cell & !(self.mask << bit_index) | (value & self.mask) << bit_index;
+ old_value
+ }
+
pub fn set(&mut self, index: usize, value: u64) {
- // Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)var1);
- // Validate.inclusiveBetween(0L, this.mask, (long)var2);
- // int var3 = this.cellIndex(var1);
- // long var4 = this.data[var3];
- // int var6 = (var1 - var3 * this.valuesPerLong) * this.bits;
- // this.data[var3] = var4 & ~(this.mask << var6) | ((long)var2 & this.mask) << var6;
+ // 0 bit storage
+ if self.data.is_empty() {
+ return;
+ }
assert!(index < self.size);
assert!(value <= self.mask);
diff --git a/azalea-world/src/chunk.rs b/azalea-world/src/chunk_storage.rs
index 9d393c2d..4ceea347 100644
--- a/azalea-world/src/chunk.rs
+++ b/azalea-world/src/chunk_storage.rs
@@ -4,6 +4,7 @@ use crate::Dimension;
use azalea_block::BlockState;
use azalea_buf::BufReadError;
use azalea_buf::{McBufReadable, McBufWritable};
+use azalea_core::floor_mod;
use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos};
use std::fmt::Debug;
use std::{
@@ -24,13 +25,33 @@ pub struct ChunkStorage {
chunks: Vec<Option<Arc<Mutex<Chunk>>>>,
}
-// java moment
-// it might be possible to replace this with just a modulo, but i copied java's floorMod just in case
-fn floor_mod(x: i32, y: u32) -> u32 {
- if x < 0 {
- y - ((-x) as u32 % y)
- } else {
- x as u32 % y
+#[derive(Debug)]
+pub struct Chunk {
+ pub sections: Vec<Section>,
+}
+
+#[derive(Clone, Debug)]
+pub struct Section {
+ pub block_count: u16,
+ pub states: PalettedContainer,
+ pub biomes: PalettedContainer,
+}
+
+impl Default for Section {
+ fn default() -> Self {
+ Section {
+ block_count: 0,
+ states: PalettedContainer::new(&PalettedContainerType::BlockStates).unwrap(),
+ biomes: PalettedContainer::new(&PalettedContainerType::Biomes).unwrap(),
+ }
+ }
+}
+
+impl Default for Chunk {
+ fn default() -> Self {
+ Chunk {
+ sections: vec![Section::default(); (384 / 16) as usize],
+ }
}
}
@@ -59,13 +80,24 @@ impl ChunkStorage {
pub fn get_block_state(&self, pos: &BlockPos, min_y: i32) -> Option<BlockState> {
let chunk_pos = ChunkPos::from(pos);
- println!("chunk_pos {:?} block_pos {:?}", chunk_pos, pos);
let chunk = &self[&chunk_pos];
chunk
.as_ref()
.map(|chunk| chunk.lock().unwrap().get(&ChunkBlockPos::from(pos), min_y))
}
+ pub fn set_block_state(&self, pos: &BlockPos, state: BlockState, min_y: i32) -> BlockState {
+ let chunk_pos = ChunkPos::from(pos);
+ let chunk = &self[&chunk_pos];
+ if let Some(chunk) = chunk.as_ref() {
+ let mut chunk = chunk.lock().unwrap();
+ chunk.get_and_set(&ChunkBlockPos::from(pos), state, min_y)
+ } else {
+ // nothing is in this chunk, just return air
+ BlockState::Air
+ }
+ }
+
pub fn replace_with_packet_data(
&mut self,
pos: &ChunkPos,
@@ -104,11 +136,6 @@ impl IndexMut<&ChunkPos> for ChunkStorage {
}
}
-#[derive(Debug)]
-pub struct Chunk {
- pub sections: Vec<Section>,
-}
-
impl Chunk {
pub fn read_with_dimension(
buf: &mut impl Read,
@@ -131,9 +158,7 @@ impl Chunk {
}
pub fn section_index(&self, y: i32, min_y: i32) -> u32 {
- // TODO: check the build height and stuff, this code will be broken if the min build height is 0
- // (LevelHeightAccessor.getMinSection in vanilla code)
- assert!(y >= 0);
+ assert!(y >= min_y, "y ({}) must be at least {}", y, min_y);
let min_section_index = min_y.div_floor(16);
(y.div_floor(16) - min_section_index) as u32
}
@@ -145,6 +170,27 @@ impl Chunk {
let chunk_section_pos = ChunkSectionBlockPos::from(pos);
section.get(chunk_section_pos)
}
+
+ pub fn get_and_set(
+ &mut self,
+ pos: &ChunkBlockPos,
+ state: BlockState,
+ min_y: i32,
+ ) -> BlockState {
+ let section_index = self.section_index(pos.y, min_y);
+ // TODO: make sure the section exists
+ let section = &mut self.sections[section_index as usize];
+ let chunk_section_pos = ChunkSectionBlockPos::from(pos);
+ section.get_and_set(chunk_section_pos, state)
+ }
+
+ pub fn set(&mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32) {
+ let section_index = self.section_index(pos.y, min_y);
+ // TODO: make sure the section exists
+ let section = &mut self.sections[section_index as usize];
+ let chunk_section_pos = ChunkSectionBlockPos::from(pos);
+ section.set(chunk_section_pos, state)
+ }
}
impl McBufWritable for Chunk {
@@ -170,13 +216,6 @@ impl Debug for ChunkStorage {
}
}
-#[derive(Clone, Debug)]
-pub struct Section {
- pub block_count: u16,
- pub states: PalettedContainer,
- pub biomes: PalettedContainer,
-}
-
impl McBufReadable for Section {
fn read_from(buf: &mut impl Read) -> Result<Self, BufReadError> {
let block_count = u16::read_from(buf)?;
@@ -220,9 +259,47 @@ impl McBufWritable for Section {
impl Section {
fn get(&self, pos: ChunkSectionBlockPos) -> BlockState {
// TODO: use the unsafe method and do the check earlier
+ let state = self
+ .states
+ .get(pos.x as usize, pos.y as usize, pos.z as usize);
+ // if there's an unknown block assume it's air
+ BlockState::try_from(state).unwrap_or(BlockState::Air)
+ }
+
+ fn get_and_set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) -> BlockState {
+ let previous_state =
+ self.states
+ .get_and_set(pos.x as usize, pos.y as usize, pos.z as usize, state as u32);
+ // if there's an unknown block assume it's air
+ BlockState::try_from(previous_state).unwrap_or(BlockState::Air)
+ }
+
+ fn set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) {
self.states
- .get(pos.x as usize, pos.y as usize, pos.z as usize)
- .try_into()
- .expect("Invalid block state.")
+ .set(pos.x as usize, pos.y as usize, pos.z as usize, state as u32);
+ }
+}
+
+impl Default for ChunkStorage {
+ fn default() -> Self {
+ Self::new(8, 384, -64)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_section_index() {
+ let chunk = Chunk::default();
+ assert_eq!(chunk.section_index(0, 0), 0);
+ assert_eq!(chunk.section_index(128, 0), 8);
+ assert_eq!(chunk.section_index(127, 0), 7);
+ assert_eq!(chunk.section_index(0, -64), 4);
+ assert_eq!(chunk.section_index(-64, -64), 0);
+ assert_eq!(chunk.section_index(-49, -64), 0);
+ assert_eq!(chunk.section_index(-48, -64), 1);
+ assert_eq!(chunk.section_index(128, -64), 12);
}
}
diff --git a/azalea-entity/src/data.rs b/azalea-world/src/entity/data.rs
index ff708653..ff708653 100644
--- a/azalea-entity/src/data.rs
+++ b/azalea-world/src/entity/data.rs
diff --git a/azalea-world/src/entity/dimensions.rs b/azalea-world/src/entity/dimensions.rs
new file mode 100644
index 00000000..1d013d10
--- /dev/null
+++ b/azalea-world/src/entity/dimensions.rs
@@ -0,0 +1,23 @@
+use azalea_core::{Vec3, AABB};
+
+#[derive(Debug, Default)]
+pub struct EntityDimensions {
+ pub width: f32,
+ pub height: f32,
+}
+
+impl EntityDimensions {
+ pub fn make_bounding_box(&self, pos: &Vec3) -> AABB {
+ let radius = (self.width / 2.0) as f64;
+ let height = self.height as f64;
+ AABB {
+ min_x: pos.x - radius,
+ min_y: pos.y,
+ min_z: pos.z - radius,
+
+ max_x: pos.x + radius,
+ max_y: pos.y + height,
+ max_z: pos.z + radius,
+ }
+ }
+}
diff --git a/azalea-world/src/entity/mod.rs b/azalea-world/src/entity/mod.rs
new file mode 100644
index 00000000..37321e0a
--- /dev/null
+++ b/azalea-world/src/entity/mod.rs
@@ -0,0 +1,316 @@
+mod data;
+mod dimensions;
+
+use crate::Dimension;
+use azalea_core::{BlockPos, Vec3, AABB};
+pub use data::*;
+pub use dimensions::*;
+use std::ops::{Deref, DerefMut};
+use std::ptr::NonNull;
+use uuid::Uuid;
+
+#[derive(Debug)]
+pub struct EntityRef<'d> {
+ /// The dimension this entity is in.
+ pub dimension: &'d Dimension,
+ /// The incrementing numerical id of the entity.
+ pub id: u32,
+ pub data: &'d EntityData,
+}
+
+impl<'d> EntityRef<'d> {
+ pub fn new(dimension: &'d Dimension, id: u32, data: &'d EntityData) -> Self {
+ // TODO: have this be based on the entity type
+ Self {
+ dimension,
+ id,
+ data,
+ }
+ }
+}
+
+impl<'d> EntityRef<'d> {
+ #[inline]
+ pub fn pos(&self) -> &Vec3 {
+ &self.pos
+ }
+
+ pub fn make_bounding_box(&self) -> AABB {
+ self.dimensions.make_bounding_box(&self.pos())
+ }
+
+ /// Get the position of the block below the entity, but a little lower.
+ pub fn on_pos_legacy(&self) -> BlockPos {
+ self.on_pos(0.2)
+ }
+
+ // int x = Mth.floor(this.position.x);
+ // int y = Mth.floor(this.position.y - (double)var1);
+ // int z = Mth.floor(this.position.z);
+ // BlockPos var5 = new BlockPos(x, y, z);
+ // if (this.level.getBlockState(var5).isAir()) {
+ // BlockPos var6 = var5.below();
+ // BlockState var7 = this.level.getBlockState(var6);
+ // if (var7.is(BlockTags.FENCES) || var7.is(BlockTags.WALLS) || var7.getBlock() instanceof FenceGateBlock) {
+ // return var6;
+ // }
+ // }
+ // return var5;
+ pub fn on_pos(&self, offset: f32) -> BlockPos {
+ let x = self.pos().x.floor() as i32;
+ let y = (self.pos().y - offset as f64).floor() as i32;
+ let z = self.pos().z.floor() as i32;
+ let pos = BlockPos { x, y, z };
+
+ // TODO: check if block below is a fence, wall, or fence gate
+ // let block_pos = pos.below();
+ // let block_state = dimension.get_block_state(&block_pos);
+ // if block_state == Some(BlockState::Air) {
+ // let block_pos_below = block_pos.below();
+ // let block_state_below = dimension.get_block_state(&block_pos_below);
+ // if let Some(block_state_below) = block_state_below {
+ // if block_state_below.is_fence()
+ // || block_state_below.is_wall()
+ // || block_state_below.is_fence_gate()
+ // {
+ // return block_pos_below;
+ // }
+ // }
+ // }
+
+ pos
+ }
+}
+
+#[derive(Debug)]
+pub struct EntityMut<'d> {
+ /// The dimension this entity is in.
+ pub dimension: &'d mut Dimension,
+ /// The incrementing numerical id of the entity.
+ pub id: u32,
+ pub data: NonNull<EntityData>,
+}
+
+impl<'d> EntityMut<'d> {
+ pub fn new(dimension: &'d mut Dimension, id: u32, data: NonNull<EntityData>) -> Self {
+ Self {
+ dimension,
+ id,
+ data,
+ }
+ }
+
+ /// Sets the position of the entity. This doesn't update the cache in
+ /// azalea-world, and should only be used within azalea-world!
+ pub unsafe fn move_unchecked(&mut self, new_pos: Vec3) {
+ self.pos = new_pos;
+ let bounding_box = self.make_bounding_box();
+ self.bounding_box = bounding_box;
+ }
+
+ pub fn set_rotation(&mut self, y_rot: f32, x_rot: f32) {
+ self.y_rot = y_rot.clamp(-90.0, 90.0) % 360.0;
+ self.x_rot = x_rot % 360.0;
+ // TODO: minecraft also sets yRotO and xRotO to xRot and yRot ... but idk what they're used for so
+ }
+
+ pub fn move_relative(&mut self, speed: f32, acceleration: &Vec3) {
+ let input_vector = self.input_vector(speed, acceleration);
+ self.delta += input_vector;
+ }
+
+ pub fn input_vector(&self, speed: f32, acceleration: &Vec3) -> Vec3 {
+ let distance = acceleration.length_squared();
+ if distance < 1.0E-7 {
+ return Vec3::default();
+ }
+ let acceleration = if distance > 1.0 {
+ acceleration.normalize()
+ } else {
+ *acceleration
+ }
+ .scale(speed as f64);
+ let y_rot = f32::sin(self.y_rot * 0.017453292f32);
+ let x_rot = f32::cos(self.y_rot * 0.017453292f32);
+ Vec3 {
+ x: acceleration.x * (x_rot as f64) - acceleration.z * (y_rot as f64),
+ y: acceleration.y,
+ z: acceleration.z * (x_rot as f64) + acceleration.x * (y_rot as f64),
+ }
+ }
+}
+
+impl<'d> EntityMut<'d> {
+ #[inline]
+ pub fn pos(&self) -> &Vec3 {
+ &self.pos
+ }
+
+ pub fn make_bounding_box(&self) -> AABB {
+ self.dimensions.make_bounding_box(&self.pos())
+ }
+
+ /// Get the position of the block below the entity, but a little lower.
+ pub fn on_pos_legacy(&self) -> BlockPos {
+ self.on_pos(0.2)
+ }
+
+ // int x = Mth.floor(this.position.x);
+ // int y = Mth.floor(this.position.y - (double)var1);
+ // int z = Mth.floor(this.position.z);
+ // BlockPos var5 = new BlockPos(x, y, z);
+ // if (this.level.getBlockState(var5).isAir()) {
+ // BlockPos var6 = var5.below();
+ // BlockState var7 = this.level.getBlockState(var6);
+ // if (var7.is(BlockTags.FENCES) || var7.is(BlockTags.WALLS) || var7.getBlock() instanceof FenceGateBlock) {
+ // return var6;
+ // }
+ // }
+ // return var5;
+ pub fn on_pos(&self, offset: f32) -> BlockPos {
+ let x = self.pos().x.floor() as i32;
+ let y = (self.pos().y - offset as f64).floor() as i32;
+ let z = self.pos().z.floor() as i32;
+ let pos = BlockPos { x, y, z };
+
+ // TODO: check if block below is a fence, wall, or fence gate
+ // let block_pos = pos.below();
+ // let block_state = dimension.get_block_state(&block_pos);
+ // if block_state == Some(BlockState::Air) {
+ // let block_pos_below = block_pos.below();
+ // let block_state_below = dimension.get_block_state(&block_pos_below);
+ // if let Some(block_state_below) = block_state_below {
+ // if block_state_below.is_fence()
+ // || block_state_below.is_wall()
+ // || block_state_below.is_fence_gate()
+ // {
+ // return block_pos_below;
+ // }
+ // }
+ // }
+
+ pos
+ }
+}
+
+impl<'d> From<EntityMut<'d>> for EntityRef<'d> {
+ fn from(entity: EntityMut<'d>) -> EntityRef<'d> {
+ let data = unsafe { entity.data.as_ref() };
+ EntityRef {
+ dimension: entity.dimension,
+ id: entity.id,
+ data,
+ }
+ }
+}
+
+impl Deref for EntityMut<'_> {
+ type Target = EntityData;
+
+ fn deref(&self) -> &Self::Target {
+ unsafe { self.data.as_ref() }
+ }
+}
+
+impl DerefMut for EntityMut<'_> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ unsafe { self.data.as_mut() }
+ }
+}
+
+impl Deref for EntityRef<'_> {
+ type Target = EntityData;
+
+ fn deref(&self) -> &Self::Target {
+ self.data
+ }
+}
+
+#[derive(Debug)]
+pub struct EntityData {
+ pub uuid: Uuid,
+ /// The position of the entity right now.
+ /// This can be changde with unsafe_move, but the correct way is with dimension.move_entity
+ pos: Vec3,
+ /// The position of the entity last tick.
+ pub last_pos: Vec3,
+ pub delta: Vec3,
+
+ /// X acceleration.
+ pub xxa: f32,
+ /// Y acceleration.
+ pub yya: f32,
+ /// Z acceleration.
+ pub zza: f32,
+
+ pub x_rot: f32,
+ pub y_rot: f32,
+
+ pub x_rot_last: f32,
+ pub y_rot_last: f32,
+
+ pub on_ground: bool,
+ pub last_on_ground: bool,
+
+ /// The width and height of the entity.
+ pub dimensions: EntityDimensions,
+ /// The bounding box of the entity. This is more than just width and height, unlike dimensions.
+ pub bounding_box: AABB,
+}
+
+impl EntityData {
+ pub fn new(uuid: Uuid, pos: Vec3) -> Self {
+ let dimensions = EntityDimensions {
+ width: 0.8,
+ height: 1.8,
+ };
+
+ Self {
+ uuid,
+ pos,
+ last_pos: pos,
+ delta: Vec3::default(),
+
+ xxa: 0.,
+ yya: 0.,
+ zza: 0.,
+
+ x_rot: 0.,
+ y_rot: 0.,
+
+ y_rot_last: 0.,
+ x_rot_last: 0.,
+
+ on_ground: false,
+ last_on_ground: false,
+
+ // TODO: have this be based on the entity type
+ bounding_box: dimensions.make_bounding_box(&pos),
+ dimensions,
+ }
+ }
+
+ #[inline]
+ pub fn pos(&self) -> &Vec3 {
+ &self.pos
+ }
+
+ pub(crate) unsafe fn as_ptr(&mut self) -> NonNull<EntityData> {
+ NonNull::new_unchecked(self as *mut EntityData)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn from_mut_entity_to_ref_entity() {
+ let mut dim = Dimension::default();
+ let uuid = Uuid::from_u128(100);
+ dim.add_entity(0, EntityData::new(uuid, Vec3::default()));
+ let entity: EntityMut = dim.entity_mut(0).unwrap();
+ let entity_ref: EntityRef = entity.into();
+ assert_eq!(entity_ref.uuid, uuid);
+ }
+}
diff --git a/azalea-world/src/entity.rs b/azalea-world/src/entity_storage.rs
index 69529294..c7fd9c0b 100644
--- a/azalea-world/src/entity.rs
+++ b/azalea-world/src/entity_storage.rs
@@ -1,5 +1,5 @@
+use crate::entity::EntityData;
use azalea_core::ChunkPos;
-use azalea_entity::Entity;
use log::warn;
use nohash_hasher::{IntMap, IntSet};
use std::collections::HashMap;
@@ -7,41 +7,41 @@ use uuid::Uuid;
#[derive(Debug)]
pub struct EntityStorage {
- by_id: IntMap<u32, Entity>,
- by_chunk: HashMap<ChunkPos, IntSet<u32>>,
- by_uuid: HashMap<Uuid, u32>,
+ data_by_id: IntMap<u32, EntityData>,
+ id_by_chunk: HashMap<ChunkPos, IntSet<u32>>,
+ id_by_uuid: HashMap<Uuid, u32>,
}
impl EntityStorage {
pub fn new() -> Self {
Self {
- by_id: IntMap::default(),
- by_chunk: HashMap::default(),
- by_uuid: HashMap::default(),
+ data_by_id: IntMap::default(),
+ id_by_chunk: HashMap::default(),
+ id_by_uuid: HashMap::default(),
}
}
/// Add an entity to the storage.
#[inline]
- pub fn insert(&mut self, entity: Entity) {
- self.by_chunk
+ pub fn insert(&mut self, id: u32, entity: EntityData) {
+ self.id_by_chunk
.entry(ChunkPos::from(entity.pos()))
.or_default()
- .insert(entity.id);
- self.by_uuid.insert(entity.uuid, entity.id);
- self.by_id.insert(entity.id, entity);
+ .insert(id);
+ self.id_by_uuid.insert(entity.uuid, id);
+ self.data_by_id.insert(id, entity);
}
/// Remove an entity from the storage by its id.
#[inline]
pub fn remove_by_id(&mut self, id: u32) {
- if let Some(entity) = self.by_id.remove(&id) {
+ if let Some(entity) = self.data_by_id.remove(&id) {
let entity_chunk = ChunkPos::from(entity.pos());
let entity_uuid = entity.uuid;
- if self.by_chunk.remove(&entity_chunk).is_none() {
+ if self.id_by_chunk.remove(&entity_chunk).is_none() {
warn!("Tried to remove entity with id {id} from chunk {entity_chunk:?} but it was not found.");
}
- if self.by_uuid.remove(&entity_uuid).is_none() {
+ if self.id_by_uuid.remove(&entity_uuid).is_none() {
warn!("Tried to remove entity with id {id} from uuid {entity_uuid:?} but it was not found.");
}
} else {
@@ -49,36 +49,46 @@ impl EntityStorage {
}
}
+ /// Check if there is an entity that exists with the given id.
+ #[inline]
+ pub fn contains_id(&self, id: &u32) -> bool {
+ self.data_by_id.contains_key(id)
+ }
+
/// Get a reference to an entity by its id.
#[inline]
- pub fn get_by_id(&self, id: u32) -> Option<&Entity> {
- self.by_id.get(&id)
+ pub fn get_by_id(&self, id: u32) -> Option<&EntityData> {
+ self.data_by_id.get(&id)
}
/// Get a mutable reference to an entity by its id.
#[inline]
- pub fn get_mut_by_id(&mut self, id: u32) -> Option<&mut Entity> {
- self.by_id.get_mut(&id)
+ pub fn get_mut_by_id<'d>(&'d mut self, id: u32) -> Option<&'d mut EntityData> {
+ self.data_by_id.get_mut(&id)
}
/// Get a reference to an entity by its uuid.
#[inline]
- pub fn get_by_uuid(&self, uuid: &Uuid) -> Option<&Entity> {
- self.by_uuid.get(uuid).and_then(|id| self.by_id.get(id))
+ pub fn get_by_uuid(&self, uuid: &Uuid) -> Option<&EntityData> {
+ self.id_by_uuid
+ .get(uuid)
+ .and_then(|id| self.data_by_id.get(id))
}
/// Get a mutable reference to an entity by its uuid.
#[inline]
- pub fn get_mut_by_uuid(&mut self, uuid: &Uuid) -> Option<&mut Entity> {
- self.by_uuid.get(uuid).and_then(|id| self.by_id.get_mut(id))
+ pub fn get_mut_by_uuid(&mut self, uuid: &Uuid) -> Option<&mut EntityData> {
+ self.id_by_uuid
+ .get(uuid)
+ .and_then(|id| self.data_by_id.get_mut(id))
}
/// Clear all entities in a chunk.
pub fn clear_chunk(&mut self, chunk: &ChunkPos) {
- if let Some(entities) = self.by_chunk.remove(chunk) {
+ if let Some(entities) = self.id_by_chunk.remove(chunk) {
for entity_id in entities {
- if let Some(entity) = self.by_id.remove(&entity_id) {
- self.by_uuid.remove(&entity.uuid);
+ if let Some(entity) = self.data_by_id.remove(&entity_id) {
+ self.id_by_uuid.remove(&entity.uuid);
} else {
warn!("While clearing chunk {chunk:?}, found an entity that isn't in by_id {entity_id}.");
}
@@ -94,10 +104,10 @@ impl EntityStorage {
old_chunk: &ChunkPos,
new_chunk: &ChunkPos,
) {
- if let Some(entities) = self.by_chunk.get_mut(old_chunk) {
+ if let Some(entities) = self.id_by_chunk.get_mut(old_chunk) {
entities.remove(&entity_id);
}
- self.by_chunk
+ self.id_by_chunk
.entry(*new_chunk)
.or_default()
.insert(entity_id);
@@ -105,24 +115,24 @@ impl EntityStorage {
/// Get an iterator over all entities.
#[inline]
- pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, Entity> {
- self.by_id.values()
+ pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, EntityData> {
+ self.data_by_id.values()
}
- pub fn find_one_entity<F>(&self, mut f: F) -> Option<&Entity>
+ pub fn find_one_entity<F>(&self, mut f: F) -> Option<&EntityData>
where
- F: FnMut(&Entity) -> bool,
+ F: FnMut(&EntityData) -> bool,
{
self.entities().find(|&entity| f(entity))
}
- pub fn find_one_entity_in_chunk<F>(&self, chunk: &ChunkPos, mut f: F) -> Option<&Entity>
+ pub fn find_one_entity_in_chunk<F>(&self, chunk: &ChunkPos, mut f: F) -> Option<&EntityData>
where
- F: FnMut(&Entity) -> bool,
+ F: FnMut(&EntityData) -> bool,
{
- if let Some(entities) = self.by_chunk.get(chunk) {
+ if let Some(entities) = self.id_by_chunk.get(chunk) {
for entity_id in entities {
- if let Some(entity) = self.by_id.get(entity_id) {
+ if let Some(entity) = self.data_by_id.get(entity_id) {
if f(entity) {
return Some(entity);
}
@@ -138,3 +148,22 @@ impl Default for EntityStorage {
Self::new()
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use azalea_core::Vec3;
+
+ #[test]
+ fn test_store_entity() {
+ let mut storage = EntityStorage::new();
+ assert!(storage.get_by_id(0).is_none());
+
+ let uuid = Uuid::from_u128(100);
+ storage.insert(0, EntityData::new(uuid, Vec3::default()));
+ assert_eq!(storage.get_by_id(0).unwrap().uuid, uuid);
+
+ storage.remove_by_id(0);
+ assert!(storage.get_by_id(0).is_none());
+ }
+}
diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs
index 646ba1d9..77bb0f0f 100644
--- a/azalea-world/src/lib.rs
+++ b/azalea-world/src/lib.rs
@@ -1,17 +1,18 @@
#![feature(int_roundings)]
mod bit_storage;
-mod chunk;
-mod entity;
+mod chunk_storage;
+pub mod entity;
+mod entity_storage;
mod palette;
use azalea_block::BlockState;
use azalea_buf::BufReadError;
-use azalea_core::{BlockPos, ChunkPos, EntityPos, PositionDelta8};
-use azalea_entity::Entity;
+use azalea_core::{BlockPos, ChunkPos, PositionDelta8, Vec3};
pub use bit_storage::BitStorage;
-pub use chunk::{Chunk, ChunkStorage};
-pub use entity::EntityStorage;
+pub use chunk_storage::{Chunk, ChunkStorage};
+use entity::{EntityData, EntityMut, EntityRef};
+pub use entity_storage::EntityStorage;
use std::{
io::Read,
ops::{Index, IndexMut},
@@ -20,17 +21,10 @@ use std::{
use thiserror::Error;
use uuid::Uuid;
-#[cfg(test)]
-mod tests {
- #[test]
- fn it_works() {
- let result = 2 + 2;
- assert_eq!(result, 4);
- }
-}
-
/// A dimension is a collection of chunks and entities.
-#[derive(Debug)]
+/// Minecraft calls these "Levels", Fabric calls them "Worlds", Minestom calls them "Instances".
+/// Yeah.
+#[derive(Debug, Default)]
pub struct Dimension {
chunk_storage: ChunkStorage,
entity_storage: EntityStorage,
@@ -66,20 +60,20 @@ impl Dimension {
self.chunk_storage.get_block_state(pos, self.min_y())
}
- pub fn move_entity(
- &mut self,
- entity_id: u32,
- new_pos: EntityPos,
- ) -> Result<(), MoveEntityError> {
- let entity = self
- .entity_storage
- .get_mut_by_id(entity_id)
+ pub fn set_block_state(&mut self, pos: &BlockPos, state: BlockState) -> BlockState {
+ self.chunk_storage.set_block_state(pos, state, self.min_y())
+ }
+
+ pub fn set_entity_pos(&mut self, entity_id: u32, new_pos: Vec3) -> Result<(), MoveEntityError> {
+ println!("set_entity_pos({}, {:?})", entity_id, new_pos);
+ let mut entity = self
+ .entity_mut(entity_id)
.ok_or(MoveEntityError::EntityDoesNotExist)?;
let old_chunk = ChunkPos::from(entity.pos());
let new_chunk = ChunkPos::from(&new_pos);
// this is fine because we update the chunk below
- entity.unsafe_move(new_pos);
+ unsafe { entity.move_unchecked(new_pos) };
if old_chunk != new_chunk {
self.entity_storage
.update_entity_chunk(entity_id, &old_chunk, &new_chunk);
@@ -92,9 +86,8 @@ impl Dimension {
entity_id: u32,
delta: &PositionDelta8,
) -> Result<(), MoveEntityError> {
- let entity = self
- .entity_storage
- .get_mut_by_id(entity_id)
+ let mut entity = self
+ .entity_mut(entity_id)
.ok_or(MoveEntityError::EntityDoesNotExist)?;
let new_pos = entity.pos().with_delta(delta);
@@ -102,7 +95,7 @@ impl Dimension {
let new_chunk = ChunkPos::from(&new_pos);
// this is fine because we update the chunk below
- entity.unsafe_move(new_pos);
+ unsafe { entity.move_unchecked(new_pos) };
if old_chunk != new_chunk {
self.entity_storage
.update_entity_chunk(entity_id, &old_chunk, &new_chunk);
@@ -110,8 +103,8 @@ impl Dimension {
Ok(())
}
- pub fn add_entity(&mut self, entity: Entity) {
- self.entity_storage.insert(entity);
+ pub fn add_entity(&mut self, id: u32, entity: EntityData) {
+ self.entity_storage.insert(id, entity);
}
pub fn height(&self) -> u32 {
@@ -122,27 +115,46 @@ impl Dimension {
self.chunk_storage.min_y
}
- pub fn entity_by_id(&self, id: u32) -> Option<&Entity> {
+ pub fn entity_data_by_id(&self, id: u32) -> Option<&EntityData> {
self.entity_storage.get_by_id(id)
}
- pub fn mut_entity_by_id(&mut self, id: u32) -> Option<&mut Entity> {
+ pub fn entity_data_mut_by_id(&mut self, id: u32) -> Option<&mut EntityData> {
self.entity_storage.get_mut_by_id(id)
}
- pub fn entity_by_uuid(&self, uuid: &Uuid) -> Option<&Entity> {
+ pub fn entity<'d>(&'d self, id: u32) -> Option<EntityRef<'d>> {
+ let entity_data = self.entity_storage.get_by_id(id);
+ if let Some(entity_data) = entity_data {
+ Some(EntityRef::new(self, id, entity_data))
+ } else {
+ None
+ }
+ }
+
+ pub fn entity_mut<'d>(&'d mut self, id: u32) -> Option<EntityMut<'d>> {
+ let entity_data = self.entity_storage.get_mut_by_id(id);
+ if let Some(entity_data) = entity_data {
+ let entity_ptr = unsafe { entity_data.as_ptr() };
+ Some(EntityMut::new(self, id, entity_ptr))
+ } else {
+ None
+ }
+ }
+
+ pub fn entity_by_uuid(&self, uuid: &Uuid) -> Option<&EntityData> {
self.entity_storage.get_by_uuid(uuid)
}
/// Get an iterator over all entities.
#[inline]
- pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, Entity> {
+ pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, EntityData> {
self.entity_storage.entities()
}
- pub fn find_one_entity<F>(&self, mut f: F) -> Option<&Entity>
+ pub fn find_one_entity<F>(&self, mut f: F) -> Option<&EntityData>
where
- F: FnMut(&Entity) -> bool,
+ F: FnMut(&EntityData) -> bool,
{
self.entity_storage.find_one_entity(|entity| f(entity))
}
diff --git a/azalea-world/src/palette.rs b/azalea-world/src/palette.rs
index 2473a7e5..4e0f9a96 100644
--- a/azalea-world/src/palette.rs
+++ b/azalea-world/src/palette.rs
@@ -21,23 +21,27 @@ pub struct PalettedContainer {
}
impl PalettedContainer {
+ pub fn new(container_type: &'static PalettedContainerType) -> Result<Self, String> {
+ let palette = Palette::SingleValue(0);
+ let size = container_type.size();
+ let storage = BitStorage::new(0, size, Some(vec![])).unwrap();
+
+ Ok(PalettedContainer {
+ bits_per_entry: 0,
+ palette,
+ storage,
+ container_type: *container_type,
+ })
+ }
+
pub fn read_with_type(
buf: &mut impl Read,
- type_: &'static PalettedContainerType,
+ container_type: &'static PalettedContainerType,
) -> Result<Self, BufReadError> {
let bits_per_entry = buf.read_byte()?;
- let palette = match type_ {
- PalettedContainerType::BlockStates => {
- Palette::block_states_read_with_bits_per_entry(buf, bits_per_entry)?
- }
- PalettedContainerType::Biomes => {
- Palette::biomes_read_with_bits_per_entry(buf, bits_per_entry)?
- }
- };
- let size = match type_ {
- PalettedContainerType::BlockStates => 4096,
- PalettedContainerType::Biomes => 64,
- };
+ let palette_type = PaletteType::from_bits_and_type(bits_per_entry, container_type);
+ let palette = palette_type.read(buf)?;
+ let size = container_type.size();
let data = Vec::<u64>::read_from(buf)?;
debug_assert!(
@@ -50,24 +54,130 @@ impl PalettedContainer {
bits_per_entry,
palette,
storage,
- container_type: *type_,
+ container_type: *container_type,
})
}
+ /// Calculates the index of the given coordinates.
pub fn get_index(&self, x: usize, y: usize, z: usize) -> usize {
- let size_bits = match self.container_type {
- PalettedContainerType::BlockStates => 4,
- PalettedContainerType::Biomes => 2,
- };
+ let size_bits = self.container_type.size_bits();
(((y << size_bits) | z) << size_bits) | x
}
- pub fn get(&self, x: usize, y: usize, z: usize) -> u32 {
- let paletted_value = self.storage.get(self.get_index(x, y, z));
- println!("palette: {:?}", self.palette);
+ /// Returns the value at the given index.
+ pub fn get_at_index(&self, index: usize) -> u32 {
+ let paletted_value = self.storage.get(index);
self.palette.value_for(paletted_value as usize)
}
+
+ /// Returns the value at the given coordinates.
+ pub fn get(&self, x: usize, y: usize, z: usize) -> u32 {
+ // let paletted_value = self.storage.get(self.get_index(x, y, z));
+ // self.palette.value_for(paletted_value as usize)
+ self.get_at_index(self.get_index(x, y, z))
+ }
+
+ /// Sets the id at the given coordinates and return the previous id
+ pub fn get_and_set(&mut self, x: usize, y: usize, z: usize, value: u32) -> u32 {
+ let paletted_value = self.id_for(value);
+ self.storage
+ .get_and_set(self.get_index(x, y, z), paletted_value as u64) as u32
+ }
+
+ /// Sets the id at the given index and return the previous id. You probably want `.set` instead.
+ pub fn set_at_index(&mut self, index: usize, value: u32) {
+ let paletted_value = self.id_for(value);
+ self.storage.set(index, paletted_value as u64)
+ }
+
+ /// Sets the id at the given coordinates and return the previous id
+ pub fn set(&mut self, x: usize, y: usize, z: usize, value: u32) {
+ self.set_at_index(self.get_index(x, y, z), value);
+ }
+
+ fn create_or_reuse_data(&self, bits_per_entry: u8) -> PalettedContainer {
+ let new_palette_type =
+ PaletteType::from_bits_and_type(bits_per_entry, &self.container_type);
+ // note for whoever is trying to optimize this: vanilla has this
+ // but it causes a stack overflow since it's not changing the bits per entry
+ // i don't know how to fix this properly so glhf
+ // let old_palette_type: PaletteType = (&self.palette).into();
+ // if new_palette_type == old_palette_type {
+ // return self.clone();
+ // }
+ let storage =
+ BitStorage::new(bits_per_entry as usize, self.container_type.size(), None).unwrap();
+
+ // sanity check
+ debug_assert_eq!(storage.size(), self.container_type.size());
+
+ // let palette = new_palette_type.into_empty_palette(1usize << (bits_per_entry as usize));
+ let palette = new_palette_type.into_empty_palette();
+ PalettedContainer {
+ bits_per_entry,
+ palette,
+ storage,
+ container_type: self.container_type,
+ }
+ }
+
+ fn on_resize(&mut self, bits_per_entry: u8, value: u32) -> usize {
+ if bits_per_entry > 5 {
+ panic!("bits_per_entry must be <= 5");
+ }
+ let mut new_data = self.create_or_reuse_data(bits_per_entry);
+ new_data.copy_from(&self.palette, &self.storage);
+ *self = new_data;
+ let id = self.id_for(value);
+ id
+ }
+
+ fn copy_from(&mut self, palette: &Palette, storage: &BitStorage) {
+ for i in 0..storage.size() {
+ let value = palette.value_for(storage.get(i) as usize);
+ let id = self.id_for(value) as u64;
+ self.storage.set(i, id);
+ }
+ }
+
+ pub fn id_for(&mut self, value: u32) -> usize {
+ match &mut self.palette {
+ Palette::SingleValue(v) => {
+ if *v != value {
+ self.on_resize(1, value)
+ } else {
+ 0
+ }
+ }
+ Palette::Linear(palette) => {
+ if let Some(index) = palette.iter().position(|v| *v == value) {
+ return index as usize;
+ }
+ let capacity = 2usize.pow(self.bits_per_entry.into());
+ if capacity > palette.len() {
+ palette.push(value);
+ palette.len() - 1
+ } else {
+ self.on_resize(self.bits_per_entry + 1, value)
+ }
+ }
+ Palette::Hashmap(palette) => {
+ // TODO? vanilla keeps this in memory as a hashmap, but also i don't care
+ if let Some(index) = palette.iter().position(|v| *v == value) {
+ return index as usize;
+ }
+ let capacity = 2usize.pow(self.bits_per_entry.into());
+ if capacity > palette.len() {
+ palette.push(value);
+ palette.len() - 1
+ } else {
+ self.on_resize(self.bits_per_entry + 1, value)
+ }
+ }
+ Palette::Global => value as usize,
+ }
+ }
}
impl McBufWritable for PalettedContainer {
@@ -79,45 +189,37 @@ impl McBufWritable for PalettedContainer {
}
}
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum PaletteType {
+ SingleValue,
+ Linear,
+ Hashmap,
+ Global,
+}
+
#[derive(Clone, Debug)]
pub enum Palette {
/// ID of the corresponding entry in its global palette
SingleValue(u32),
+ // in vanilla this keeps a `size` field that might be less than the length, but i'm not sure it's actually needed?
Linear(Vec<u32>),
Hashmap(Vec<u32>),
Global,
}
impl Palette {
- pub fn block_states_read_with_bits_per_entry(
- buf: &mut impl Read,
- bits_per_entry: u8,
- ) -> Result<Palette, BufReadError> {
- Ok(match bits_per_entry {
- 0 => Palette::SingleValue(u32::var_read_from(buf)?),
- 1..=4 => Palette::Linear(Vec::<u32>::var_read_from(buf)?),
- 5..=8 => Palette::Hashmap(Vec::<u32>::var_read_from(buf)?),
- _ => Palette::Global,
- })
- }
-
- pub fn biomes_read_with_bits_per_entry(
- buf: &mut impl Read,
- bits_per_entry: u8,
- ) -> Result<Palette, BufReadError> {
- Ok(match bits_per_entry {
- 0 => Palette::SingleValue(u32::var_read_from(buf)?),
- 1..=3 => Palette::Linear(Vec::<u32>::var_read_from(buf)?),
- _ => Palette::Global,
- })
- }
-
- pub fn value_for(&self, value: usize) -> u32 {
+ pub fn value_for(&self, id: usize) -> u32 {
match self {
Palette::SingleValue(v) => *v,
- Palette::Linear(v) => v[value],
- Palette::Hashmap(v) => v[value],
- Palette::Global => value as u32,
+ Palette::Linear(v) => v[id],
+ Palette::Hashmap(v) => {
+ if id >= v.len() {
+ 0
+ } else {
+ v[id]
+ }
+ }
+ Palette::Global => id as u32,
}
}
}
@@ -139,3 +241,123 @@ impl McBufWritable for Palette {
Ok(())
}
}
+
+impl PaletteType {
+ pub fn from_bits_and_type(bits_per_entry: u8, container_type: &PalettedContainerType) -> Self {
+ match container_type {
+ PalettedContainerType::BlockStates => match bits_per_entry {
+ 0 => PaletteType::SingleValue,
+ 1..=4 => PaletteType::Linear,
+ 5..=8 => PaletteType::Hashmap,
+ _ => PaletteType::Global,
+ },
+ PalettedContainerType::Biomes => match bits_per_entry {
+ 0 => PaletteType::SingleValue,
+ 1..=3 => PaletteType::Linear,
+ _ => PaletteType::Global,
+ },
+ }
+ }
+
+ pub fn read(&self, buf: &mut impl Read) -> Result<Palette, BufReadError> {
+ Ok(match self {
+ PaletteType::SingleValue => Palette::SingleValue(u32::var_read_from(buf)?),
+ PaletteType::Linear => Palette::Linear(Vec::<u32>::var_read_from(buf)?),
+ PaletteType::Hashmap => Palette::Hashmap(Vec::<u32>::var_read_from(buf)?),
+ PaletteType::Global => Palette::Global,
+ })
+ }
+
+ pub fn into_empty_palette(&self) -> Palette {
+ match self {
+ PaletteType::SingleValue => Palette::SingleValue(0),
+ PaletteType::Linear => Palette::Linear(Vec::new()),
+ PaletteType::Hashmap => Palette::Hashmap(Vec::new()),
+ PaletteType::Global => Palette::Global,
+ }
+ }
+}
+
+impl From<&Palette> for PaletteType {
+ fn from(palette: &Palette) -> Self {
+ match palette {
+ Palette::SingleValue(_) => PaletteType::SingleValue,
+ Palette::Linear(_) => PaletteType::Linear,
+ Palette::Hashmap(_) => PaletteType::Hashmap,
+ Palette::Global => PaletteType::Global,
+ }
+ }
+}
+
+impl PalettedContainerType {
+ fn size_bits(&self) -> usize {
+ match self {
+ PalettedContainerType::BlockStates => 4,
+ PalettedContainerType::Biomes => 2,
+ }
+ }
+
+ fn size(&self) -> usize {
+ 1 << self.size_bits() * 3
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_resize_0_bits_to_1() {
+ let mut palette_container =
+ PalettedContainer::new(&PalettedContainerType::BlockStates).unwrap();
+
+ assert_eq!(palette_container.bits_per_entry, 0);
+ assert_eq!(palette_container.get_at_index(0), 0);
+ assert_eq!(
+ PaletteType::from(&palette_container.palette),
+ PaletteType::SingleValue
+ );
+ palette_container.set_at_index(0, 1);
+ assert_eq!(palette_container.get_at_index(0), 1);
+ assert_eq!(
+ PaletteType::from(&palette_container.palette),
+ PaletteType::Linear
+ );
+ }
+
+ #[test]
+ fn test_resize_0_bits_to_5() {
+ let mut palette_container =
+ PalettedContainer::new(&PalettedContainerType::BlockStates).unwrap();
+
+ palette_container.set_at_index(0, 0); // 0 bits
+ assert_eq!(palette_container.bits_per_entry, 0);
+
+ palette_container.set_at_index(1, 1); // 1 bit
+ assert_eq!(palette_container.bits_per_entry, 1);
+
+ palette_container.set_at_index(2, 2); // 2 bits
+ assert_eq!(palette_container.bits_per_entry, 2);
+ palette_container.set_at_index(3, 3);
+
+ palette_container.set_at_index(4, 4); // 3 bits
+ assert_eq!(palette_container.bits_per_entry, 3);
+ palette_container.set_at_index(5, 5);
+ palette_container.set_at_index(6, 6);
+ palette_container.set_at_index(7, 7);
+
+ palette_container.set_at_index(8, 8); // 4 bits
+ assert_eq!(palette_container.bits_per_entry, 4);
+ palette_container.set_at_index(9, 9);
+ palette_container.set_at_index(10, 10);
+ palette_container.set_at_index(11, 11);
+ palette_container.set_at_index(12, 12);
+ palette_container.set_at_index(13, 13);
+ palette_container.set_at_index(14, 14);
+ palette_container.set_at_index(15, 15);
+ assert_eq!(palette_container.bits_per_entry, 4);
+
+ palette_container.set_at_index(16, 16); // 5 bits
+ assert_eq!(palette_container.bits_per_entry, 5);
+ }
+}
diff --git a/bot/Cargo.toml b/bot/Cargo.toml
index 974d6f61..fa0b0c67 100755
--- a/bot/Cargo.toml
+++ b/bot/Cargo.toml
@@ -8,6 +8,7 @@ version = "0.1.0"
[dependencies]
azalea-client = {path = "../azalea-client"}
azalea-core = {path = "../azalea-core"}
+azalea-physics = {path = "../azalea-physics"}
azalea-protocol = {path = "../azalea-protocol"}
tokio = "1.19.2"
uuid = "1.1.2"
diff --git a/bot/src/main.rs b/bot/src/main.rs
index 0f3ea31a..c8f6bea7 100644
--- a/bot/src/main.rs
+++ b/bot/src/main.rs
@@ -1,12 +1,14 @@
+#![allow(unused_variables, unused_imports)]
use azalea_client::{Account, Event};
-use azalea_core::PositionXYZ;
+use azalea_core::{PositionXYZ, Vec3};
+use azalea_physics::collision::{HasCollision, MoverType};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Hello, world!");
// let address = "95.111.249.143:10000";
- let address = "localhost:25565";
+ let address = "localhost";
// let response = azalea_client::ping::ping_server(&address.try_into().unwrap())
// .await
// .unwrap();
@@ -38,19 +40,31 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// // println!("block state: {:?}", c);
// // }
// }
- Event::Chat(_m) => {
- let new_pos = {
- let dimension_lock = client.dimension.lock().unwrap();
- let dimension = dimension_lock.as_ref().unwrap();
- let player = client.player.lock().unwrap();
- let entity = player
- .entity(dimension)
- .expect("Player entity is not in world");
- entity.pos().add_y(0.5)
- };
-
- println!("{:?}", new_pos);
- client.move_to(new_pos).await.unwrap();
+ Event::Chat(m) => {
+ // let new_pos = {
+ // let dimension_lock = client.dimension.lock().unwrap();
+ // let player = client.player.lock().unwrap();
+ // let entity = player
+ // .entity(&dimension_lock)
+ // .expect("Player entity is not in world");
+ // entity.pos().add_y(-0.5)
+ // };
+
+ // println!("{:?}", new_pos);
+ // client.set_pos(new_pos).await.unwrap();
+ // client.move_entity()
+
+ // println!("{}", m.to_ansi(None));
+ if let Err(e) = client
+ .move_entity(&Vec3 {
+ x: 0.,
+ y: -0.5,
+ z: 0.,
+ })
+ .await
+ {
+ eprintln!("{:?}", e);
+ }
}
_ => {}
}