mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
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 🎉 * 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
This commit is contained in:
parent
2ea804401f
commit
f42d630544
58 changed files with 3156 additions and 487 deletions
33
Cargo.lock
generated
33
Cargo.lock
generated
|
@ -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",
|
||||
|
@ -171,17 +173,6 @@ dependencies = [
|
|||
"uuid",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -204,6 +195,15 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "azalea-physics"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"azalea-block",
|
||||
"azalea-core",
|
||||
"azalea-world",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "azalea-protocol"
|
||||
version = "0.1.0"
|
||||
|
@ -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",
|
||||
|
|
10
Cargo.toml
10
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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -9,7 +9,3 @@ version = "0.1.0"
|
|||
|
||||
[dependencies]
|
||||
block-macros = {path = "./block-macros"}
|
||||
|
||||
[features]
|
||||
default = ["trait"]
|
||||
trait = []
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
#[cfg(feature = "trait")]
|
||||
mod behavior;
|
||||
mod blocks;
|
||||
|
||||
#[cfg(feature = "trait")]
|
||||
pub use behavior::BlockBehavior;
|
||||
pub use blocks::*;
|
||||
|
||||
|
|
|
@ -8,3 +8,4 @@ pub mod modifier;
|
|||
pub mod parse_results;
|
||||
pub mod string_reader;
|
||||
pub mod tree;
|
||||
pub mod suggestion;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
mod suggestions;
|
||||
|
||||
use crate::{context::StringRange, message::Message};
|
||||
pub use suggestions::*;
|
||||
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct Suggestion {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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_entity_id = {
|
||||
let player_lock = client.player.lock().unwrap();
|
||||
let player_entity_id = player_lock.entity_id;
|
||||
drop(player_lock);
|
||||
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() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 Client {
|
||||
/// Set the client's position to the given coordinates.
|
||||
pub async fn move_to(&mut self, new_pos: EntityPos) -> Result<(), MovePlayerError> {
|
||||
{
|
||||
let mut dimension_lock = self.dimension.lock().unwrap();
|
||||
let dimension = dimension_lock.as_mut().unwrap();
|
||||
impl From<MoveEntityError> for MovePlayerError {
|
||||
fn from(err: MoveEntityError) -> Self {
|
||||
match err {
|
||||
MoveEntityError::EntityDoesNotExist => MovePlayerError::PlayerNotInWorld,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// This gets called every tick.
|
||||
pub async fn send_position(&mut self) -> Result<(), MovePlayerError> {
|
||||
let packet = {
|
||||
let player_lock = self.player.lock().unwrap();
|
||||
|
||||
let player_id = if let Some(player_lock) = player_lock.entity(dimension) {
|
||||
player_lock.id
|
||||
} else {
|
||||
return Err(MovePlayerError::PlayerNotInWorld);
|
||||
};
|
||||
let mut dimension_lock = self.dimension.lock().unwrap();
|
||||
|
||||
match dimension.move_entity(player_id, new_pos) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => match e {
|
||||
MoveEntityError::EntityDoesNotExist => Err(MovePlayerError::PlayerNotInWorld),
|
||||
},
|
||||
}?;
|
||||
}
|
||||
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;
|
||||
|
||||
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,
|
||||
// TODO: send sprinting and sneaking packets here if they changed
|
||||
|
||||
// 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(),
|
||||
)
|
||||
.await?;
|
||||
} 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 {
|
||||
None
|
||||
};
|
||||
|
||||
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?;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
3
azalea-core/README.md
Normal file
3
azalea-core/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Azalea Core
|
||||
|
||||
Miscellaneous things in Azalea.
|
447
azalea-core/src/aabb.rs
Normal file
447
azalea-core/src/aabb.rs
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
58
azalea-core/src/bitset.rs
Normal file
58
azalea-core/src/bitset.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
9
azalea-core/src/block_hit_result.rs
Normal file
9
azalea-core/src/block_hit_result.rs
Normal file
|
@ -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,
|
||||
}
|
115
azalea-core/src/cursor3d.rs
Normal file
115
azalea-core/src/cursor3d.rs
Normal file
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
// }
|
||||
// }
|
|
@ -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"
|
||||
|
|
|
@ -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"}
|
3
azalea-physics/README.md
Normal file
3
azalea-physics/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Azalea Physics
|
||||
|
||||
Physics for Minecraft entities.
|
137
azalea-physics/src/collision/dimension_collisions.rs
Normal file
137
azalea-physics/src/collision/dimension_collisions.rs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
156
azalea-physics/src/collision/discrete_voxel_shape.rs
Normal file
156
azalea-physics/src/collision/discrete_voxel_shape.rs
Normal file
|
@ -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))
|
||||
}
|
||||
}
|
273
azalea-physics/src/collision/mod.rs
Normal file
273
azalea-physics/src/collision/mod.rs
Normal file
|
@ -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,
|
||||
}
|
||||
}
|
254
azalea-physics/src/collision/shape.rs
Normal file
254
azalea-physics/src/collision/shape.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
127
azalea-physics/src/lib.rs
Normal file
127
azalea-physics/src/lib.rs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)]
|
||||
|
|
8
azalea-protocol/src/packets/game/mod.rs
Executable file → Normal file
8
azalea-protocol/src/packets/game/mod.rs
Executable file → Normal file
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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> {
|
||||
if let Some(data) = &data {
|
||||
// 0 bit storage
|
||||
if data.is_empty() {
|
||||
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);
|
||||
|
||||
if let Some(data) = &data {
|
||||
if data.is_empty() {
|
||||
// TODO: make 0 bit storage actually work
|
||||
return Ok(BitStorage::default());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -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(pos.x as usize, pos.y as usize, pos.z as usize)
|
||||
.try_into()
|
||||
.expect("Invalid block state.")
|
||||
.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
|
||||
.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);
|
||||
}
|
||||
}
|
23
azalea-world/src/entity/dimensions.rs
Normal file
23
azalea-world/src/entity/dimensions.rs
Normal file
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
316
azalea-world/src/entity/mod.rs
Normal file
316
azalea-world/src/entity/mod.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
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.move_to(new_pos).await.unwrap();
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue