1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00
* 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:
mat 2022-08-29 20:41:01 -05:00 committed by GitHub
parent 2ea804401f
commit f42d630544
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
58 changed files with 3156 additions and 487 deletions

33
Cargo.lock generated
View file

@ -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",

View file

@ -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

View file

@ -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

View file

@ -9,7 +9,3 @@ version = "0.1.0"
[dependencies]
block-macros = {path = "./block-macros"}
[features]
default = ["trait"]
trait = []

View file

@ -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()
}

View file

@ -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
}
}

View file

@ -1,8 +1,6 @@
#[cfg(feature = "trait")]
mod behavior;
mod blocks;
#[cfg(feature = "trait")]
pub use behavior::BlockBehavior;
pub use blocks::*;

View file

@ -8,3 +8,4 @@ pub mod modifier;
pub mod parse_results;
pub mod string_reader;
pub mod tree;
pub mod suggestion;

View file

@ -1,6 +1,7 @@
mod suggestions;
use crate::{context::StringRange, message::Message};
pub use suggestions::*;
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct Suggestion {

View file

@ -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,
}
}
}

View file

@ -53,4 +53,4 @@ impl McBufWritable for BitSet {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
self.data.write_into(buf)
}
}
}

View file

@ -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"

View file

@ -1,7 +1,7 @@
use crate::{Account, Player};
use azalea_auth::game_profile::GameProfile;
use azalea_core::{ChunkPos, EntityPos, PositionDelta, PositionDeltaTrait, ResourceLocation};
use azalea_entity::Entity;
use azalea_block::BlockState;
use azalea_core::{ChunkPos, ResourceLocation, Vec3};
use azalea_protocol::{
connect::{Connection, ConnectionError},
packets::{
@ -11,7 +11,7 @@ use azalea_protocol::{
serverbound_accept_teleportation_packet::ServerboundAcceptTeleportationPacket,
serverbound_custom_payload_packet::ServerboundCustomPayloadPacket,
serverbound_keep_alive_packet::ServerboundKeepAlivePacket,
serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPacketPosRot,
serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket,
ClientboundGamePacket, ServerboundGamePacket,
},
handshake::client_intention_packet::ClientIntentionPacket,
@ -25,6 +25,7 @@ use azalea_protocol::{
read::ReadPacketError,
resolver, ServerAddress,
};
use azalea_world::entity::EntityData;
use azalea_world::Dimension;
use std::{
fmt::Debug,
@ -66,8 +67,10 @@ pub struct Client {
game_profile: GameProfile,
pub conn: Arc<tokio::sync::Mutex<Connection<ClientboundGamePacket, ServerboundGamePacket>>>,
pub player: Arc<Mutex<Player>>,
pub dimension: Arc<Mutex<Option<Dimension>>>,
// game_loop
pub dimension: Arc<Mutex<Dimension>>,
/// Minecraft only sends a movement packet either after 20 ticks or if the player moved enough. This is that tick counter.
pub position_remainder: u32,
}
/// Whether we should ignore errors when decoding packets.
@ -181,7 +184,9 @@ impl Client {
game_profile,
conn,
player: Arc::new(Mutex::new(Player::default())),
dimension: Arc::new(Mutex::new(None)),
dimension: Arc::new(Mutex::new(Dimension::default())),
position_remainder: 0,
};
// just start up the game loop and we're ready!
@ -298,16 +303,10 @@ impl Client {
let mut dimension_lock = client.dimension.lock().unwrap();
// the 16 here is our render distance
// i'll make this an actual setting later
*dimension_lock = Some(Dimension::new(16, height, min_y));
*dimension_lock = Dimension::new(16, height, min_y);
let entity =
Entity::new(p.player_id, client.game_profile.uuid, EntityPos::default());
dimension_lock
.as_mut()
.expect(
"Dimension doesn't exist! We should've gotten a login packet by now.",
)
.add_entity(entity);
let entity = EntityData::new(client.game_profile.uuid, Vec3::default());
dimension_lock.add_entity(p.player_id, entity);
let mut player_lock = client.player.lock().unwrap();
@ -368,42 +367,42 @@ impl Client {
println!("Got player position packet {:?}", p);
let (new_pos, y_rot, x_rot) = {
let player_lock = client.player.lock().unwrap();
let player_entity_id = player_lock.entity_id;
drop(player_lock);
let player_entity_id = {
let player_lock = client.player.lock().unwrap();
player_lock.entity_id
};
let mut dimension_lock = client.dimension.lock().unwrap();
let dimension = dimension_lock.as_mut().unwrap();
let player_entity = dimension
.mut_entity_by_id(player_entity_id)
let mut player_entity = dimension_lock
.entity_mut(player_entity_id)
.expect("Player entity doesn't exist");
let delta_movement = &player_entity.delta;
let delta_movement = player_entity.delta;
let is_x_relative = p.relative_arguments.x;
let is_y_relative = p.relative_arguments.y;
let is_z_relative = p.relative_arguments.z;
let (delta_x, new_pos_x) = if is_x_relative {
player_entity.old_pos.x += p.x;
(delta_movement.x(), player_entity.pos().x + p.x)
player_entity.last_pos.x += p.x;
(delta_movement.x, player_entity.pos().x + p.x)
} else {
player_entity.old_pos.x = p.x;
player_entity.last_pos.x = p.x;
(0.0, p.x)
};
let (delta_y, new_pos_y) = if is_y_relative {
player_entity.old_pos.y += p.y;
(delta_movement.y(), player_entity.pos().y + p.y)
player_entity.last_pos.y += p.y;
(delta_movement.y, player_entity.pos().y + p.y)
} else {
player_entity.old_pos.y = p.y;
player_entity.last_pos.y = p.y;
(0.0, p.y)
};
let (delta_z, new_pos_z) = if is_z_relative {
player_entity.old_pos.z += p.z;
(delta_movement.z(), player_entity.pos().z + p.z)
player_entity.last_pos.z += p.z;
(delta_movement.z, player_entity.pos().z + p.z)
} else {
player_entity.old_pos.z = p.z;
player_entity.last_pos.z = p.z;
(0.0, p.z)
};
@ -416,21 +415,21 @@ impl Client {
x_rot += player_entity.y_rot;
}
player_entity.delta = PositionDelta {
xa: delta_x,
ya: delta_y,
za: delta_z,
player_entity.delta = Vec3 {
x: delta_x,
y: delta_y,
z: delta_z,
};
player_entity.set_rotation(y_rot, x_rot);
// TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means
// so investigate that ig
let new_pos = EntityPos {
let new_pos = Vec3 {
x: new_pos_x,
y: new_pos_y,
z: new_pos_z,
};
dimension
.move_entity(player_entity_id, new_pos)
dimension_lock
.set_entity_pos(player_entity_id, new_pos)
.expect("The player entity should always exist");
(new_pos, y_rot, x_rot)
@ -442,7 +441,7 @@ impl Client {
.await?;
conn_lock
.write(
ServerboundMovePlayerPacketPosRot {
ServerboundMovePlayerPosRotPacket {
x: new_pos.x,
y: new_pos.y,
z: new_pos.z,
@ -463,8 +462,6 @@ impl Client {
client
.dimension
.lock()?
.as_mut()
.unwrap()
.update_view_center(&ChunkPos::new(p.x, p.z));
}
ClientboundGamePacket::ClientboundLevelChunkWithLightPacket(p) => {
@ -475,8 +472,6 @@ impl Client {
client
.dimension
.lock()?
.as_mut()
.expect("Dimension doesn't exist! We should've gotten a login packet by now.")
.replace_with_packet_data(&pos, &mut p.chunk_data.data.as_slice())
.unwrap();
}
@ -485,13 +480,8 @@ impl Client {
}
ClientboundGamePacket::ClientboundAddEntityPacket(p) => {
println!("Got add entity packet {:?}", p);
let entity = Entity::from(p);
client
.dimension
.lock()?
.as_mut()
.expect("Dimension doesn't exist! We should've gotten a login packet by now.")
.add_entity(entity);
let entity = EntityData::from(p);
client.dimension.lock()?.add_entity(p.id, entity);
}
ClientboundGamePacket::ClientboundSetEntityDataPacket(_p) => {
// println!("Got set entity data packet {:?}", p);
@ -507,13 +497,8 @@ impl Client {
}
ClientboundGamePacket::ClientboundAddPlayerPacket(p) => {
println!("Got add player packet {:?}", p);
let entity = Entity::from(p);
client
.dimension
.lock()?
.as_mut()
.expect("Dimension doesn't exist! We should've gotten a login packet by now.")
.add_entity(entity);
let entity = EntityData::from(p);
client.dimension.lock()?.add_entity(p.id, entity);
}
ClientboundGamePacket::ClientboundInitializeBorderPacket(p) => {
println!("Got initialize border packet {:?}", p);
@ -535,12 +520,11 @@ impl Client {
}
ClientboundGamePacket::ClientboundTeleportEntityPacket(p) => {
let mut dimension_lock = client.dimension.lock()?;
let dimension = dimension_lock.as_mut().unwrap();
dimension
.move_entity(
dimension_lock
.set_entity_pos(
p.id,
EntityPos {
Vec3 {
x: p.x,
y: p.y,
z: p.z,
@ -556,17 +540,15 @@ impl Client {
}
ClientboundGamePacket::ClientboundMoveEntityPosPacket(p) => {
let mut dimension_lock = client.dimension.lock()?;
let dimension = dimension_lock.as_mut().unwrap();
dimension
dimension_lock
.move_entity_with_delta(p.entity_id, &p.delta)
.map_err(|e| HandleError::Other(e.into()))?;
}
ClientboundGamePacket::ClientboundMoveEntityPosRotPacket(p) => {
let mut dimension_lock = client.dimension.lock()?;
let dimension = dimension_lock.as_mut().unwrap();
dimension
dimension_lock
.move_entity_with_delta(p.entity_id, &p.delta)
.map_err(|e| HandleError::Other(e.into()))?;
}
@ -603,6 +585,16 @@ impl Client {
ClientboundGamePacket::ClientboundBlockUpdatePacket(p) => {
println!("Got block update packet {:?}", p);
// TODO: update world
let mut dimension = client.dimension.lock()?;
// dimension.get_block_state(pos)
if let Ok(block_state) = BlockState::try_from(p.block_state) {
dimension.set_block_state(&p.pos, block_state);
} else {
eprintln!(
"Non-existent block state for block update packet {}",
p.block_state
);
}
}
ClientboundGamePacket::ClientboundAnimatePacket(p) => {
println!("Got animate packet {:?}", p);
@ -626,28 +618,107 @@ impl Client {
ClientboundGamePacket::ClientboundUpdateMobEffectPacket(p) => {
println!("Got update mob effect packet {:?}", p);
}
_ => panic!("Unexpected packet {:?}", packet),
ClientboundGamePacket::ClientboundAddExperienceOrbPacket(_) => {}
ClientboundGamePacket::ClientboundAwardStatsPacket(_) => {}
ClientboundGamePacket::ClientboundBlockChangedAckPacket(_) => {}
ClientboundGamePacket::ClientboundBlockDestructionPacket(_) => {}
ClientboundGamePacket::ClientboundBlockEntityDataPacket(_) => {}
ClientboundGamePacket::ClientboundBlockEventPacket(_) => {}
ClientboundGamePacket::ClientboundBossEventPacket(_) => {}
ClientboundGamePacket::ClientboundChatPreviewPacket(_) => {}
ClientboundGamePacket::ClientboundCommandSuggestionsPacket(_) => {}
ClientboundGamePacket::ClientboundContainerSetDataPacket(_) => {}
ClientboundGamePacket::ClientboundContainerSetSlotPacket(_) => {}
ClientboundGamePacket::ClientboundCooldownPacket(_) => {}
ClientboundGamePacket::ClientboundCustomChatCompletionsPacket(_) => {}
ClientboundGamePacket::ClientboundCustomSoundPacket(_) => {}
ClientboundGamePacket::ClientboundDeleteChatPacket(_) => {}
ClientboundGamePacket::ClientboundExplodePacket(_) => {}
ClientboundGamePacket::ClientboundForgetLevelChunkPacket(_) => {}
ClientboundGamePacket::ClientboundHorseScreenOpenPacket(_) => {}
ClientboundGamePacket::ClientboundMapItemDataPacket(_) => {}
ClientboundGamePacket::ClientboundMerchantOffersPacket(_) => {}
ClientboundGamePacket::ClientboundMoveVehiclePacket(_) => {}
ClientboundGamePacket::ClientboundOpenBookPacket(_) => {}
ClientboundGamePacket::ClientboundOpenScreenPacket(_) => {}
ClientboundGamePacket::ClientboundOpenSignEditorPacket(_) => {}
ClientboundGamePacket::ClientboundPingPacket(_) => {}
ClientboundGamePacket::ClientboundPlaceGhostRecipePacket(_) => {}
ClientboundGamePacket::ClientboundPlayerChatHeaderPacket(_) => {}
ClientboundGamePacket::ClientboundPlayerCombatEndPacket(_) => {}
ClientboundGamePacket::ClientboundPlayerCombatEnterPacket(_) => {}
ClientboundGamePacket::ClientboundPlayerCombatKillPacket(_) => {}
ClientboundGamePacket::ClientboundPlayerLookAtPacket(_) => {}
ClientboundGamePacket::ClientboundRemoveMobEffectPacket(_) => {}
ClientboundGamePacket::ClientboundResourcePackPacket(_) => {}
ClientboundGamePacket::ClientboundRespawnPacket(_) => {}
ClientboundGamePacket::ClientboundSelectAdvancementsTabPacket(_) => {}
ClientboundGamePacket::ClientboundSetActionBarTextPacket(_) => {}
ClientboundGamePacket::ClientboundSetBorderCenterPacket(_) => {}
ClientboundGamePacket::ClientboundSetBorderLerpSizePacket(_) => {}
ClientboundGamePacket::ClientboundSetBorderSizePacket(_) => {}
ClientboundGamePacket::ClientboundSetBorderWarningDelayPacket(_) => {}
ClientboundGamePacket::ClientboundSetBorderWarningDistancePacket(_) => {}
ClientboundGamePacket::ClientboundSetCameraPacket(_) => {}
ClientboundGamePacket::ClientboundSetChunkCacheRadiusPacket(_) => {}
ClientboundGamePacket::ClientboundSetDisplayChatPreviewPacket(_) => {}
ClientboundGamePacket::ClientboundSetDisplayObjectivePacket(_) => {}
ClientboundGamePacket::ClientboundSetEntityMotionPacket(_) => {}
ClientboundGamePacket::ClientboundSetObjectivePacket(_) => {}
ClientboundGamePacket::ClientboundSetPassengersPacket(_) => {}
ClientboundGamePacket::ClientboundSetPlayerTeamPacket(_) => {}
ClientboundGamePacket::ClientboundSetScorePacket(_) => {}
ClientboundGamePacket::ClientboundSetSimulationDistancePacket(_) => {}
ClientboundGamePacket::ClientboundSetSubtitleTextPacket(_) => {}
ClientboundGamePacket::ClientboundSetTitleTextPacket(_) => {}
ClientboundGamePacket::ClientboundSetTitlesAnimationPacket(_) => {}
ClientboundGamePacket::ClientboundSoundEntityPacket(_) => {}
ClientboundGamePacket::ClientboundStopSoundPacket(_) => {}
ClientboundGamePacket::ClientboundTabListPacket(_) => {}
ClientboundGamePacket::ClientboundTagQueryPacket(_) => {}
ClientboundGamePacket::ClientboundTakeItemEntityPacket(_) => {}
}
Ok(())
}
/// Runs game_tick every 50 milliseconds.
async fn game_tick_loop(client: Client, tx: UnboundedSender<Event>) {
async fn game_tick_loop(mut client: Client, tx: UnboundedSender<Event>) {
let mut game_tick_interval = time::interval(time::Duration::from_millis(50));
// TODO: Minecraft bursts up to 10 ticks and then skips, we should too
game_tick_interval.set_missed_tick_behavior(time::MissedTickBehavior::Burst);
loop {
game_tick_interval.tick().await;
Self::game_tick(&client, &tx).await;
Self::game_tick(&mut client, &tx).await;
}
}
/// Runs every 50 milliseconds.
async fn game_tick(client: &Client, tx: &UnboundedSender<Event>) {
if client.dimension.lock().unwrap().is_none() {
return;
async fn game_tick(client: &mut Client, tx: &UnboundedSender<Event>) {
// return if there's no chunk at the player's position
{
let dimension_lock = client.dimension.lock().unwrap();
let player_lock = client.player.lock().unwrap();
let player_entity = player_lock.entity(&dimension_lock);
let player_entity = if let Some(player_entity) = player_entity {
player_entity
} else {
return;
};
let player_chunk_pos: ChunkPos = player_entity.pos().into();
if dimension_lock[&player_chunk_pos].is_none() {
return;
}
}
// TODO: if we're a passenger, send the required packets
if let Err(e) = client.send_position().await {
println!("Error sending position: {:?}", e);
}
// TODO: minecraft does ambient sounds here
tx.send(Event::GameTick).unwrap();
}
}

View file

@ -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
let mut dimension_lock = self.dimension.lock().unwrap();
let mut player_entity = player_lock
.entity_mut(&mut dimension_lock)
.expect("Player must exist");
let player_pos = player_entity.pos();
let player_old_pos = player_entity.last_pos;
// TODO: send sprinting and sneaking packets here if they changed
// TODO: the camera being able to be controlled by other entities isn't implemented yet
// if !self.is_controlled_camera() { return };
let x_delta = player_pos.x - player_old_pos.x;
let y_delta = player_pos.y - player_old_pos.y;
let z_delta = player_pos.z - player_old_pos.z;
let y_rot_delta = (player_entity.y_rot - player_entity.y_rot_last) as f64;
let x_rot_delta = (player_entity.x_rot - player_entity.x_rot_last) as f64;
self.position_remainder += 1;
// boolean sendingPosition = Mth.lengthSquared(xDelta, yDelta, zDelta) > Mth.square(2.0E-4D) || this.positionReminder >= 20;
let sending_position = ((x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2))
> 2.0e-4f64.powi(2))
|| self.position_remainder >= 20;
let sending_rotation = y_rot_delta != 0.0 || x_rot_delta != 0.0;
// if self.is_passenger() {
// TODO: posrot packet for being a passenger
// }
let packet = if sending_position && sending_rotation {
Some(
ServerboundMovePlayerPosRotPacket {
x: player_pos.x,
y: player_pos.y,
z: player_pos.z,
x_rot: player_entity.x_rot,
y_rot: player_entity.y_rot,
on_ground: player_entity.on_ground,
}
.get(),
)
} else if sending_position {
Some(
ServerboundMovePlayerPosPacket {
x: player_pos.x,
y: player_pos.y,
z: player_pos.z,
on_ground: player_entity.on_ground,
}
.get(),
)
} else if sending_rotation {
Some(
ServerboundMovePlayerRotPacket {
x_rot: player_entity.x_rot,
y_rot: player_entity.y_rot,
on_ground: player_entity.on_ground,
}
.get(),
)
} else if player_entity.last_on_ground != player_entity.on_ground {
Some(
ServerboundMovePlayerStatusOnlyPacket {
on_ground: player_entity.on_ground,
}
.get(),
)
} else {
return Err(MovePlayerError::PlayerNotInWorld);
None
};
match dimension.move_entity(player_id, new_pos) {
Ok(_) => Ok(()),
Err(e) => match e {
MoveEntityError::EntityDoesNotExist => Err(MovePlayerError::PlayerNotInWorld),
},
}?;
}
if sending_position {
player_entity.last_pos = *player_entity.pos();
self.position_remainder = 0;
}
if sending_rotation {
player_entity.y_rot_last = player_entity.y_rot;
player_entity.x_rot_last = player_entity.x_rot;
}
self.conn
.lock()
.await
.write(
ServerboundMovePlayerPacketPosRot {
x: new_pos.x,
y: new_pos.y,
z: new_pos.z,
x_rot: 0.0,
y_rot: 0.0,
on_ground: false,
}
.get(),
)
.await?;
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();
}
}

View file

@ -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
View file

@ -0,0 +1,3 @@
# Azalea Core
Miscellaneous things in Azalea.

447
azalea-core/src/aabb.rs Normal file
View 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
View 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);
}
}

View 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
View 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"),
}
}
}

View file

@ -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;
}
}

View file

@ -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),
}
}
}

View file

@ -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
}

View file

@ -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,

View file

@ -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);
// }
// }

View file

@ -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"

View file

@ -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
View file

@ -0,0 +1,3 @@
# Azalea Physics
Physics for Minecraft entities.

View 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
}
}

View 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))
}
}

View 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,
}
}

View 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
View 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
}
}

View file

@ -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"

View file

@ -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,

View file

@ -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,

View file

@ -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)]

View file

@ -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};

View file

@ -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)]

View file

@ -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
View 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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,
}

View file

@ -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,

View file

@ -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"

View file

@ -103,16 +103,21 @@ impl BitStorage {
/// Create a new BitStorage with the given number of bits per entry.
/// `size` is the number of entries in the BitStorage.
pub fn new(bits: usize, size: usize, data: Option<Vec<u64>>) -> Result<Self, BitStorageError> {
// vanilla has this assert but it's not always true for some reason??
// assert!(bits >= 1 && bits <= 32);
if let Some(data) = &data {
// 0 bit storage
if data.is_empty() {
// TODO: make 0 bit storage actually work
return Ok(BitStorage::default());
return Ok(BitStorage {
data: Vec::with_capacity(0),
bits,
size,
..Default::default()
});
}
}
// vanilla has this assert but it's not always true for some reason??
// assert!(bits >= 1 && bits <= 32);
let values_per_long = 64 / bits;
let magic_index = values_per_long - 1;
let (divide_mul, divide_add, divide_shift) = MAGIC[magic_index as usize];
@ -163,23 +168,43 @@ impl BitStorage {
assert!(
index < self.size,
"Index {} out of bounds (max is {})",
"Index {} out of bounds (must be less than {})",
index,
self.size - 1
self.size
);
// 0 bit storage
if self.data.is_empty() {
return 0;
}
let cell_index = self.cell_index(index as u64);
let cell = &self.data[cell_index as usize];
let bit_index = (index - cell_index * self.values_per_long as usize) * self.bits;
cell >> bit_index & self.mask
}
pub fn get_and_set(&mut self, index: usize, value: u64) -> u64 {
// 0 bit storage
if self.data.is_empty() {
return 0;
}
assert!(index < self.size);
assert!(value <= self.mask);
let cell_index = self.cell_index(index as u64);
let cell = &mut self.data[cell_index as usize];
let bit_index = (index - cell_index * self.values_per_long as usize) * self.bits;
let old_value = *cell >> (bit_index as u64) & self.mask;
*cell = *cell & !(self.mask << bit_index) | (value & self.mask) << bit_index;
old_value
}
pub fn set(&mut self, index: usize, value: u64) {
// Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)var1);
// Validate.inclusiveBetween(0L, this.mask, (long)var2);
// int var3 = this.cellIndex(var1);
// long var4 = this.data[var3];
// int var6 = (var1 - var3 * this.valuesPerLong) * this.bits;
// this.data[var3] = var4 & ~(this.mask << var6) | ((long)var2 & this.mask) << var6;
// 0 bit storage
if self.data.is_empty() {
return;
}
assert!(index < self.size);
assert!(value <= self.mask);

View file

@ -4,6 +4,7 @@ use crate::Dimension;
use azalea_block::BlockState;
use azalea_buf::BufReadError;
use azalea_buf::{McBufReadable, McBufWritable};
use azalea_core::floor_mod;
use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos};
use std::fmt::Debug;
use std::{
@ -24,13 +25,33 @@ pub struct ChunkStorage {
chunks: Vec<Option<Arc<Mutex<Chunk>>>>,
}
// java moment
// it might be possible to replace this with just a modulo, but i copied java's floorMod just in case
fn floor_mod(x: i32, y: u32) -> u32 {
if x < 0 {
y - ((-x) as u32 % y)
} else {
x as u32 % y
#[derive(Debug)]
pub struct Chunk {
pub sections: Vec<Section>,
}
#[derive(Clone, Debug)]
pub struct Section {
pub block_count: u16,
pub states: PalettedContainer,
pub biomes: PalettedContainer,
}
impl Default for Section {
fn default() -> Self {
Section {
block_count: 0,
states: PalettedContainer::new(&PalettedContainerType::BlockStates).unwrap(),
biomes: PalettedContainer::new(&PalettedContainerType::Biomes).unwrap(),
}
}
}
impl Default for Chunk {
fn default() -> Self {
Chunk {
sections: vec![Section::default(); (384 / 16) as usize],
}
}
}
@ -59,13 +80,24 @@ impl ChunkStorage {
pub fn get_block_state(&self, pos: &BlockPos, min_y: i32) -> Option<BlockState> {
let chunk_pos = ChunkPos::from(pos);
println!("chunk_pos {:?} block_pos {:?}", chunk_pos, pos);
let chunk = &self[&chunk_pos];
chunk
.as_ref()
.map(|chunk| chunk.lock().unwrap().get(&ChunkBlockPos::from(pos), min_y))
}
pub fn set_block_state(&self, pos: &BlockPos, state: BlockState, min_y: i32) -> BlockState {
let chunk_pos = ChunkPos::from(pos);
let chunk = &self[&chunk_pos];
if let Some(chunk) = chunk.as_ref() {
let mut chunk = chunk.lock().unwrap();
chunk.get_and_set(&ChunkBlockPos::from(pos), state, min_y)
} else {
// nothing is in this chunk, just return air
BlockState::Air
}
}
pub fn replace_with_packet_data(
&mut self,
pos: &ChunkPos,
@ -104,11 +136,6 @@ impl IndexMut<&ChunkPos> for ChunkStorage {
}
}
#[derive(Debug)]
pub struct Chunk {
pub sections: Vec<Section>,
}
impl Chunk {
pub fn read_with_dimension(
buf: &mut impl Read,
@ -131,9 +158,7 @@ impl Chunk {
}
pub fn section_index(&self, y: i32, min_y: i32) -> u32 {
// TODO: check the build height and stuff, this code will be broken if the min build height is 0
// (LevelHeightAccessor.getMinSection in vanilla code)
assert!(y >= 0);
assert!(y >= min_y, "y ({}) must be at least {}", y, min_y);
let min_section_index = min_y.div_floor(16);
(y.div_floor(16) - min_section_index) as u32
}
@ -145,6 +170,27 @@ impl Chunk {
let chunk_section_pos = ChunkSectionBlockPos::from(pos);
section.get(chunk_section_pos)
}
pub fn get_and_set(
&mut self,
pos: &ChunkBlockPos,
state: BlockState,
min_y: i32,
) -> BlockState {
let section_index = self.section_index(pos.y, min_y);
// TODO: make sure the section exists
let section = &mut self.sections[section_index as usize];
let chunk_section_pos = ChunkSectionBlockPos::from(pos);
section.get_and_set(chunk_section_pos, state)
}
pub fn set(&mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32) {
let section_index = self.section_index(pos.y, min_y);
// TODO: make sure the section exists
let section = &mut self.sections[section_index as usize];
let chunk_section_pos = ChunkSectionBlockPos::from(pos);
section.set(chunk_section_pos, state)
}
}
impl McBufWritable for Chunk {
@ -170,13 +216,6 @@ impl Debug for ChunkStorage {
}
}
#[derive(Clone, Debug)]
pub struct Section {
pub block_count: u16,
pub states: PalettedContainer,
pub biomes: PalettedContainer,
}
impl McBufReadable for Section {
fn read_from(buf: &mut impl Read) -> Result<Self, BufReadError> {
let block_count = u16::read_from(buf)?;
@ -220,9 +259,47 @@ impl McBufWritable for Section {
impl Section {
fn get(&self, pos: ChunkSectionBlockPos) -> BlockState {
// TODO: use the unsafe method and do the check earlier
let state = self
.states
.get(pos.x as usize, pos.y as usize, pos.z as usize);
// if there's an unknown block assume it's air
BlockState::try_from(state).unwrap_or(BlockState::Air)
}
fn get_and_set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) -> BlockState {
let previous_state =
self.states
.get_and_set(pos.x as usize, pos.y as usize, pos.z as usize, state as u32);
// if there's an unknown block assume it's air
BlockState::try_from(previous_state).unwrap_or(BlockState::Air)
}
fn set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) {
self.states
.get(pos.x as usize, pos.y as usize, pos.z as usize)
.try_into()
.expect("Invalid block state.")
.set(pos.x as usize, pos.y as usize, pos.z as usize, state as u32);
}
}
impl Default for ChunkStorage {
fn default() -> Self {
Self::new(8, 384, -64)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_section_index() {
let chunk = Chunk::default();
assert_eq!(chunk.section_index(0, 0), 0);
assert_eq!(chunk.section_index(128, 0), 8);
assert_eq!(chunk.section_index(127, 0), 7);
assert_eq!(chunk.section_index(0, -64), 4);
assert_eq!(chunk.section_index(-64, -64), 0);
assert_eq!(chunk.section_index(-49, -64), 0);
assert_eq!(chunk.section_index(-48, -64), 1);
assert_eq!(chunk.section_index(128, -64), 12);
}
}

View 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,
}
}
}

View 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);
}
}

View file

@ -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());
}
}

View file

@ -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))
}

View file

@ -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);
}
}

View file

@ -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"

View file

@ -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);
}
}
_ => {}
}