diff options
Diffstat (limited to 'azalea-physics/src')
| -rwxr-xr-x | azalea-physics/src/collision/discrete_voxel_shape.rs | 2 | ||||
| -rw-r--r-- | azalea-physics/src/collision/mod.rs | 304 | ||||
| -rwxr-xr-x | azalea-physics/src/collision/shape.rs | 2 | ||||
| -rw-r--r-- | azalea-physics/src/collision/world_collisions.rs | 39 | ||||
| -rw-r--r-- | azalea-physics/src/lib.rs | 571 |
5 files changed, 527 insertions, 391 deletions
diff --git a/azalea-physics/src/collision/discrete_voxel_shape.rs b/azalea-physics/src/collision/discrete_voxel_shape.rs index 51d45316..4a329398 100755 --- a/azalea-physics/src/collision/discrete_voxel_shape.rs +++ b/azalea-physics/src/collision/discrete_voxel_shape.rs @@ -64,7 +64,7 @@ impl DiscreteVoxelShape { } pub fn for_all_boxes(&self, consumer: impl IntLineConsumer, swap: bool) { - BitSetDiscreteVoxelShape::for_all_boxes(self, consumer, swap) + BitSetDiscreteVoxelShape::for_all_boxes(self, consumer, swap); } } diff --git a/azalea-physics/src/collision/mod.rs b/azalea-physics/src/collision/mod.rs index 458303c5..7d934020 100644 --- a/azalea-physics/src/collision/mod.rs +++ b/azalea-physics/src/collision/mod.rs @@ -5,13 +5,15 @@ mod shape; mod world_collisions; use azalea_core::{Axis, Vec3, AABB, EPSILON}; -use azalea_world::entity::{Entity, EntityData}; -use azalea_world::{MoveEntityError, WeakWorld}; +use azalea_world::{ + entity::{self}, + MoveEntityError, World, +}; pub use blocks::BlockWithShape; pub use discrete_voxel_shape::*; pub use shape::*; -use std::ops::Deref; -use world_collisions::CollisionGetter; + +use self::world_collisions::get_block_collisions; pub enum MoverType { Own, @@ -21,192 +23,170 @@ pub enum MoverType { Shulker, } -pub trait HasCollision { - fn collide(&self, movement: &Vec3, entity: &EntityData) -> Vec3; -} +// 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(movement: &Vec3, world: &World, physics: &entity::Physics) -> Vec3 { + let entity_bounding_box = physics.bounding_box; + // TODO: get_entity_collisions + // let entity_collisions = world.get_entity_collisions(self, + // entity_bounding_box.expand_towards(movement)); + let entity_collisions = Vec::new(); + if movement.length_sqr() == 0.0 { + *movement + } else { + collide_bounding_box(movement, &entity_bounding_box, world, entity_collisions) + } -pub trait MovableEntity { - fn move_colliding( - &mut self, - mover_type: &MoverType, - movement: &Vec3, - ) -> Result<(), MoveEntityError>; + // TODO: stepping (for stairs and stuff) + + // collided_movement } -impl<D: Deref<Target = WeakWorld>> HasCollision for D { - // 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)); } +/// Move an entity by a given delta, checking for collisions. +pub fn move_colliding( + _mover_type: &MoverType, + movement: &Vec3, + world: &World, + position: &mut entity::Position, + physics: &mut entity::Physics, +) -> 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; // } + // } - // return var4; + // if (this.stuckSpeedMultiplier.lengthSqr() > 1.0E-7D) { + // var2 = var2.multiply(this.stuckSpeedMultiplier); + // this.stuckSpeedMultiplier = Vec3.ZERO; + // this.setDeltaMovement(Vec3.ZERO); // } - fn collide(&self, movement: &Vec3, entity: &EntityData) -> Vec3 { - let entity_bounding_box = entity.bounding_box; - // TODO: get_entity_collisions - // let entity_collisions = world.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) + // movement = this.maybeBackOffFromEdge(movement, moverType); - // collided_movement - } -} + let collide_result = collide(movement, world, physics); -impl<D: Deref<Target = WeakWorld>> MovableEntity for Entity<'_, D> { - /// 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); - - let collide_result = { self.world.collide(movement, self) }; - - let move_distance = collide_result.length_sqr(); - - 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.world.set_entity_pos(self.id, new_pos)?; - } + let move_distance = collide_result.length_sqr(); + + if move_distance > EPSILON { + // TODO: fall damage + + let new_pos = { + Vec3 { + x: position.x + collide_result.x, + y: position.y + collide_result.y, + z: position.z + collide_result.z, + } + }; - 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; + **position = new_pos; + } - // TODO: minecraft checks for a "minor" horizontal collision here + 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.; + physics.on_ground = on_ground; - let _block_pos_below = self.on_pos_legacy(); - // let _block_state_below = self - // .world - // .get_block_state(&block_pos_below) - // .expect("Couldn't get block state below"); + // TODO: minecraft checks for a "minor" horizontal collision here - // self.check_fall_damage(collide_result.y, on_ground, block_state_below, - // block_pos_below); + let _block_pos_below = entity::on_pos_legacy(&world.chunks, position); + // let _block_state_below = self + // .world + // .get_block_state(&block_pos_below) + // .expect("Couldn't get block state below"); - // if self.isRemoved() { return; } + // self.check_fall_damage(collide_result.y, on_ground, block_state_below, + // block_pos_below); - 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 self.isRemoved() { return; } - if vertical_collision { - // blockBelow.updateEntityAfterFallOn(this.level, this); - // the default implementation of updateEntityAfterFallOn sets the y movement to - // 0 - self.delta.y = 0.; + if horizontal_collision { + let delta_movement = &physics.delta; + physics.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 on_ground { - // blockBelow.stepOn(this.level, blockPosBelow, blockStateBelow, - // this); - } + if vertical_collision { + // blockBelow.updateEntityAfterFallOn(this.level, this); + // the default implementation of updateEntityAfterFallOn sets the y movement to + // 0 + physics.delta.y = 0.; + } + + if on_ground { + // blockBelow.stepOn(this.level, blockPosBelow, blockStateBelow, + // this); + } - // sounds + // sounds - // this.tryCheckInsideBlocks(); + // 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()); - // } + // 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.wasOnFire && (this.isInPowderSnow || + // this.isInWaterRainOrBubble())) { this. + // playEntityOnFireExtinguishedSound(); } + // } - // if (this.isOnFire() && (this.isInPowderSnow || this.isInWaterRainOrBubble())) - // { this.setRemainingFireTicks(-this.getFireImmuneTicks()); - // } + // if (this.isOnFire() && (this.isInPowderSnow || this.isInWaterRainOrBubble())) + // { this.setRemainingFireTicks(-this.getFireImmuneTicks()); + // } - Ok(()) - } + Ok(()) } fn collide_bounding_box( - entity: Option<&EntityData>, movement: &Vec3, entity_bounding_box: &AABB, - world: &WeakWorld, + world: &World, entity_collisions: Vec<VoxelShape>, ) -> Vec3 { let mut collision_boxes: Vec<VoxelShape> = Vec::with_capacity(entity_collisions.len() + 1); @@ -218,7 +198,7 @@ fn collide_bounding_box( // TODO: world border let block_collisions = - world.get_block_collisions(entity, entity_bounding_box.expand_towards(movement)); + get_block_collisions(world, entity_bounding_box.expand_towards(movement)); let block_collisions = block_collisions.collect::<Vec<_>>(); collision_boxes.extend(block_collisions); collide_with_shapes(movement, *entity_bounding_box, &collision_boxes) diff --git a/azalea-physics/src/collision/shape.rs b/azalea-physics/src/collision/shape.rs index bb2ed2e5..cc184591 100755 --- a/azalea-physics/src/collision/shape.rs +++ b/azalea-physics/src/collision/shape.rs @@ -539,7 +539,7 @@ impl VoxelShape { x_coords[var7 as usize], y_coords[var8 as usize], z_coords[var9 as usize], - ) + ); }, true, ); diff --git a/azalea-physics/src/collision/world_collisions.rs b/azalea-physics/src/collision/world_collisions.rs index 9e238bf9..c7b2a91b 100644 --- a/azalea-physics/src/collision/world_collisions.rs +++ b/azalea-physics/src/collision/world_collisions.rs @@ -1,34 +1,17 @@ +use super::Shapes; use crate::collision::{BlockWithShape, VoxelShape, AABB}; use azalea_block::BlockState; use azalea_core::{ChunkPos, ChunkSectionPos, Cursor3d, CursorIterationType, EPSILON}; -use azalea_world::entity::EntityData; -use azalea_world::{Chunk, WeakWorld}; +use azalea_world::{Chunk, World}; use parking_lot::RwLock; use std::sync::Arc; -use super::Shapes; - -pub trait CollisionGetter { - fn get_block_collisions<'a>( - &'a self, - entity: Option<&EntityData>, - aabb: AABB, - ) -> BlockCollisions<'a>; -} - -impl CollisionGetter for WeakWorld { - fn get_block_collisions<'a>( - &'a self, - entity: Option<&EntityData>, - aabb: AABB, - ) -> BlockCollisions<'a> { - BlockCollisions::new(self, entity, aabb) - } +pub fn get_block_collisions(world: &World, aabb: AABB) -> BlockCollisions<'_> { + BlockCollisions::new(world, aabb) } pub struct BlockCollisions<'a> { - pub world: &'a WeakWorld, - // context: CollisionContext, + pub world: &'a World, pub aabb: AABB, pub entity_shape: VoxelShape, pub cursor: Cursor3d, @@ -36,8 +19,7 @@ pub struct BlockCollisions<'a> { } impl<'a> BlockCollisions<'a> { - // TODO: the entity is stored in the context - pub fn new(world: &'a WeakWorld, _entity: Option<&EntityData>, aabb: AABB) -> Self { + pub fn new(world: &'a World, aabb: AABB) -> Self { let origin_x = (aabb.min_x - EPSILON) as i32 - 1; let origin_y = (aabb.min_y - EPSILON) as i32 - 1; let origin_z = (aabb.min_z - EPSILON) as i32 - 1; @@ -75,7 +57,7 @@ impl<'a> BlockCollisions<'a> { // return var7; // } - self.world.get_chunk(&chunk_pos) + self.world.chunks.get(&chunk_pos) } } @@ -89,15 +71,14 @@ impl<'a> Iterator for BlockCollisions<'a> { } let chunk = self.get_chunk(item.pos.x, item.pos.z); - let chunk = match chunk { - Some(chunk) => chunk, - None => continue, + let Some(chunk) = chunk else { + continue }; let pos = item.pos; let block_state: BlockState = chunk .read() - .get(&(&pos).into(), self.world.min_y()) + .get(&(&pos).into(), self.world.chunks.min_y) .unwrap_or(BlockState::Air); // TODO: continue if self.only_suffocating_blocks and the block is not diff --git a/azalea-physics/src/lib.rs b/azalea-physics/src/lib.rs index 2409dff7..96bebd1a 100644 --- a/azalea-physics/src/lib.rs +++ b/azalea-physics/src/lib.rs @@ -5,24 +5,56 @@ pub mod collision; use azalea_block::{Block, BlockState}; use azalea_core::{BlockPos, Vec3}; +use azalea_ecs::{ + app::{App, Plugin}, + entity::Entity, + event::{EventReader, EventWriter}, + query::With, + schedule::{IntoSystemDescriptor, SystemSet}, + system::{Query, Res}, + AppTickExt, +}; use azalea_world::{ - entity::{Entity, EntityData}, - WeakWorld, + entity::{ + metadata::Sprinting, move_relative, Attributes, Jumping, Physics, Position, WorldName, + }, + Local, World, WorldContainer, }; -use collision::{MovableEntity, MoverType}; -use std::ops::Deref; - -pub trait HasPhysics { - fn travel(&mut self, acceleration: &Vec3); - fn ai_step(&mut self); - - fn jump_from_ground(&mut self); +use collision::{move_colliding, MoverType}; + +pub struct PhysicsPlugin; +impl Plugin for PhysicsPlugin { + fn build(&self, app: &mut App) { + app.add_event::<ForceJumpEvent>() + .add_system( + force_jump_listener + .label("force_jump_listener") + .after("ai_step"), + ) + .add_tick_system_set( + SystemSet::new() + .with_system(ai_step.label("ai_step")) + .with_system( + travel + .label("travel") + .after("ai_step") + .after("force_jump_listener"), + ), + ); + } } -impl<D: Deref<Target = WeakWorld>> HasPhysics for Entity<'_, D> { - /// Move the entity with the given acceleration while handling friction, - /// gravity, collisions, and some other stuff. - fn travel(&mut self, acceleration: &Vec3) { +/// Move the entity with the given acceleration while handling friction, +/// gravity, collisions, and some other stuff. +fn travel( + mut query: Query<(&mut Physics, &mut Position, &Attributes, &WorldName), With<Local>>, + world_container: Res<WorldContainer>, +) { + for (mut physics, mut position, attributes, world_name) in &mut query { + let world_lock = world_container + .get(world_name) + .expect("All entities should be in a valid world"); + let world = world_lock.read(); // if !self.is_effective_ai() && !self.is_controlled_by_local_instance() { // // this.calculateEntityAnimation(this, this instanceof FlyingAnimal); // return; @@ -37,24 +69,29 @@ impl<D: Deref<Target = WeakWorld>> HasPhysics for Entity<'_, D> { // TODO: elytra - let block_pos_below = get_block_pos_below_that_affects_movement(self); + let block_pos_below = get_block_pos_below_that_affects_movement(&position); - let block_state_below = self - .world + let block_state_below = world + .chunks .get_block_state(&block_pos_below) .unwrap_or(BlockState::Air); let block_below: Box<dyn Block> = block_state_below.into(); let block_friction = block_below.behavior().friction; - let inertia = if self.on_ground { + let inertia = if physics.on_ground { block_friction * 0.91 } else { 0.91 }; // this applies the current delta - let mut movement = - handle_relative_friction_and_calculate_movement(self, acceleration, block_friction); + let mut movement = handle_relative_friction_and_calculate_movement( + block_friction, + &world, + &mut physics, + &mut position, + attributes, + ); movement.y -= gravity; @@ -66,96 +103,132 @@ impl<D: Deref<Target = WeakWorld>> HasPhysics for Entity<'_, D> { // if should_discard_friction(self) { if false { - self.delta = movement; + physics.delta = movement; } else { - self.delta = Vec3 { + physics.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) { +/// applies air resistance, calls self.travel(), and some other random +/// stuff. +pub fn ai_step( + mut query: Query< + (Entity, &mut Physics, Option<&Jumping>), + With<Local>, + // TODO: ai_step should only run for players in loaded chunks + // With<LocalPlayerInLoadedChunk> maybe there should be an InLoadedChunk/InUnloadedChunk + // component? + >, + mut force_jump_events: EventWriter<ForceJumpEvent>, +) { + for (entity, mut physics, jumping) in &mut query { // vanilla does movement interpolation here, doesn't really matter much for a // bot though - if self.delta.x.abs() < 0.003 { - self.delta.x = 0.; + if physics.delta.x.abs() < 0.003 { + physics.delta.x = 0.; } - if self.delta.y.abs() < 0.003 { - self.delta.y = 0.; + if physics.delta.y.abs() < 0.003 { + physics.delta.y = 0.; } - if self.delta.z.abs() < 0.003 { - self.delta.z = 0.; + if physics.delta.z.abs() < 0.003 { + physics.delta.z = 0.; } - if self.jumping { - // TODO: jumping in liquids and jump delay + if let Some(jumping) = jumping { + if **jumping { + // TODO: jumping in liquids and jump delay - if self.on_ground { - self.jump_from_ground(); + if physics.on_ground { + force_jump_events.send(ForceJumpEvent(entity)); + } } } - 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 + physics.xxa *= 0.98; + physics.zza *= 0.98; + + // TODO: freezing, pushEntities, drowning damage (in their own systems, + // after `travel`) } +} - fn jump_from_ground(&mut self) { - let jump_power: f64 = jump_power(self) as f64 + jump_boost_power(self); - let old_delta_movement = self.delta; - self.delta = Vec3 { - x: old_delta_movement.x, - y: jump_power, - z: old_delta_movement.z, - }; - if self.metadata.sprinting { - let y_rot = self.y_rot * 0.017453292; - self.delta += Vec3 { - x: (-f32::sin(y_rot) * 0.2) as f64, - y: 0., - z: (f32::cos(y_rot) * 0.2) as f64, +/// Jump even if we aren't on the ground. +pub struct ForceJumpEvent(pub Entity); + +fn force_jump_listener( + mut query: Query<(&mut Physics, &Position, &Sprinting, &WorldName)>, + world_container: Res<WorldContainer>, + mut events: EventReader<ForceJumpEvent>, +) { + for event in events.iter() { + if let Ok((mut physics, position, sprinting, world_name)) = query.get_mut(event.0) { + let world_lock = world_container + .get(world_name) + .expect("All entities should be in a valid world"); + let world = world_lock.read(); + + let jump_power: f64 = jump_power(&world, position) as f64 + jump_boost_power(); + let old_delta_movement = physics.delta; + physics.delta = Vec3 { + x: old_delta_movement.x, + y: jump_power, + z: old_delta_movement.z, }; - } + if **sprinting { + // sprint jumping gives some extra velocity + let y_rot = physics.y_rot * 0.017453292; + physics.delta += Vec3 { + x: (-f32::sin(y_rot) * 0.2) as f64, + y: 0., + z: (f32::cos(y_rot) * 0.2) as f64, + }; + } - self.has_impulse = true; + physics.has_impulse = true; + } } } -fn get_block_pos_below_that_affects_movement(entity: &EntityData) -> BlockPos { +fn get_block_pos_below_that_affects_movement(position: &Position) -> BlockPos { BlockPos::new( - entity.pos().x.floor() as i32, + position.x.floor() as i32, // TODO: this uses bounding_box.min_y instead of position.y - (entity.pos().y - 0.5f64).floor() as i32, - entity.pos().z.floor() as i32, + (position.y - 0.5f64).floor() as i32, + position.z.floor() as i32, ) } -fn handle_relative_friction_and_calculate_movement<D: Deref<Target = WeakWorld>>( - entity: &mut Entity<D>, - acceleration: &Vec3, +fn handle_relative_friction_and_calculate_movement( block_friction: f32, + world: &World, + physics: &mut Physics, + position: &mut Position, + attributes: &Attributes, ) -> Vec3 { - entity.move_relative( - get_friction_influenced_speed(&*entity, block_friction), - acceleration, + move_relative( + physics, + get_friction_influenced_speed(physics, attributes, block_friction), + &Vec3 { + x: physics.xxa as f64, + y: physics.yya as f64, + z: physics.zza as f64, + }, ); // entity.delta = entity.handle_on_climbable(entity.delta); - entity - .move_colliding(&MoverType::Own, &entity.delta.clone()) - .expect("Entity should exist."); + move_colliding( + &MoverType::Own, + &physics.delta.clone(), + world, + position, + physics, + ) + .expect("Entity should exist."); // let delta_movement = entity.delta; // ladders // if ((entity.horizontalCollision || entity.jumping) && (entity.onClimbable() @@ -164,16 +237,16 @@ fn handle_relative_friction_and_calculate_movement<D: Deref<Target = WeakWorld>> // Vec3(var3.x, 0.2D, var3.z); } // TODO: powdered snow - entity.delta + physics.delta } // private float getFrictionInfluencedSpeed(float friction) { // return this.onGround ? this.getSpeed() * (0.21600002F / (friction * // friction * friction)) : this.flyingSpeed; } -fn get_friction_influenced_speed(entity: &EntityData, friction: f32) -> f32 { +fn get_friction_influenced_speed(physics: &Physics, attributes: &Attributes, friction: f32) -> f32 { // TODO: have speed & flying_speed fields in entity - if entity.on_ground { - let speed: f32 = entity.attributes.speed.calculate() as f32; + if physics.on_ground { + let speed: f32 = attributes.speed.calculate() as f32; speed * (0.216f32 / (friction * friction * friction)) } else { // entity.flying_speed @@ -183,11 +256,11 @@ fn get_friction_influenced_speed(entity: &EntityData, friction: f32) -> f32 { /// Returns the what the entity's jump should be multiplied by based on the /// block they're standing on. -fn block_jump_factor<D: Deref<Target = WeakWorld>>(entity: &Entity<D>) -> f32 { - let block_at_pos = entity.world.get_block_state(&entity.pos().into()); - let block_below = entity - .world - .get_block_state(&get_block_pos_below_that_affects_movement(entity)); +fn block_jump_factor(world: &World, position: &Position) -> f32 { + let block_at_pos = world.chunks.get_block_state(&position.into()); + let block_below = world + .chunks + .get_block_state(&get_block_pos_below_that_affects_movement(position)); let block_at_pos_jump_factor = if let Some(block) = block_at_pos { Box::<dyn Block>::from(block).behavior().jump_factor @@ -211,11 +284,11 @@ fn block_jump_factor<D: Deref<Target = WeakWorld>>(entity: &Entity<D>) -> f32 { // public double getJumpBoostPower() { // return this.hasEffect(MobEffects.JUMP) ? (double)(0.1F * // (float)(this.getEffect(MobEffects.JUMP).getAmplifier() + 1)) : 0.0D; } -fn jump_power<D: Deref<Target = WeakWorld>>(entity: &Entity<D>) -> f32 { - 0.42 * block_jump_factor(entity) +fn jump_power(world: &World, position: &Position) -> f32 { + 0.42 * block_jump_factor(world, position) } -fn jump_boost_power<D: Deref<Target = WeakWorld>>(_entity: &Entity<D>) -> f64 { +fn jump_boost_power() -> f64 { // TODO: potion effects // if let Some(effects) = entity.effects() { // if let Some(jump_effect) = effects.get(&Effect::Jump) { @@ -231,131 +304,218 @@ fn jump_boost_power<D: Deref<Target = WeakWorld>>(_entity: &Entity<D>) -> f64 { #[cfg(test)] mod tests { + use std::time::Duration; + use super::*; - use azalea_core::ChunkPos; + use azalea_core::{ChunkPos, ResourceLocation}; + use azalea_ecs::{app::App, TickPlugin}; use azalea_world::{ - entity::{metadata, EntityMetadata}, - Chunk, PartialWorld, + entity::{EntityBundle, MinecraftEntityId}, + Chunk, EntityPlugin, PartialWorld, }; use uuid::Uuid; + /// You need an app to spawn entities in the world and do updates. + fn make_test_app() -> App { + let mut app = App::new(); + app.add_plugin(TickPlugin { + tick_interval: Duration::ZERO, + }) + .add_plugin(PhysicsPlugin) + .add_plugin(EntityPlugin) + .init_resource::<WorldContainer>(); + app + } + #[test] fn test_gravity() { - let mut world = PartialWorld::default(); - - world.add_entity( - 0, - EntityData::new( - Uuid::nil(), - Vec3 { - x: 0., - y: 70., - z: 0., - }, - EntityMetadata::Player(metadata::Player::default()), - ), - ); - let mut entity = world.entity_mut(0).unwrap(); - // y should start at 70 - assert_eq!(entity.pos().y, 70.); - entity.ai_step(); - // delta is applied before gravity, so the first tick only sets the delta - assert_eq!(entity.pos().y, 70.); - assert!(entity.delta.y < 0.); - entity.ai_step(); - // the second tick applies the delta to the position, so now it should go down - assert!( - entity.pos().y < 70., - "Entity y ({}) didn't go down after physics steps", - entity.pos().y + let mut app = make_test_app(); + let _world_lock = app.world.resource_mut::<WorldContainer>().insert( + ResourceLocation::new("minecraft:overworld").unwrap(), + 384, + -64, ); + + let entity = app + .world + .spawn(( + EntityBundle::new( + Uuid::nil(), + Vec3 { + x: 0., + y: 70., + z: 0., + }, + azalea_registry::EntityKind::Zombie, + ResourceLocation::new("minecraft:overworld").unwrap(), + ), + MinecraftEntityId(0), + Local, + )) + .id(); + { + let entity_pos = *app.world.get::<Position>(entity).unwrap(); + // y should start at 70 + assert_eq!(entity_pos.y, 70.); + } + app.update(); + { + let entity_pos = *app.world.get::<Position>(entity).unwrap(); + // delta is applied before gravity, so the first tick only sets the delta + assert_eq!(entity_pos.y, 70.); + let entity_physics = app.world.get::<Physics>(entity).unwrap().clone(); + assert!(entity_physics.delta.y < 0.); + } + app.update(); + { + let entity_pos = *app.world.get::<Position>(entity).unwrap(); + // the second tick applies the delta to the position, so now it should go down + assert!( + entity_pos.y < 70., + "Entity y ({}) didn't go down after physics steps", + entity_pos.y + ); + } } #[test] fn test_collision() { - let mut world = PartialWorld::default(); - world - .set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default())) - .unwrap(); - world.add_entity( - 0, - EntityData::new( - Uuid::nil(), - Vec3 { - x: 0.5, - y: 70., - z: 0.5, - }, - EntityMetadata::Player(metadata::Player::default()), - ), + let mut app = make_test_app(); + let world_lock = app.world.resource_mut::<WorldContainer>().insert( + ResourceLocation::new("minecraft:overworld").unwrap(), + 384, + -64, + ); + let mut partial_world = PartialWorld::default(); + + partial_world.chunks.set( + &ChunkPos { x: 0, z: 0 }, + Some(Chunk::default()), + &mut world_lock.write().chunks, + ); + let entity = app + .world + .spawn(( + EntityBundle::new( + Uuid::nil(), + Vec3 { + x: 0.5, + y: 70., + z: 0.5, + }, + azalea_registry::EntityKind::Player, + ResourceLocation::new("minecraft:overworld").unwrap(), + ), + MinecraftEntityId(0), + Local, + )) + .id(); + let block_state = partial_world.chunks.set_block_state( + &BlockPos { x: 0, y: 69, z: 0 }, + BlockState::Stone, + &mut world_lock.write().chunks, ); - let block_state = world.set_block_state(&BlockPos { x: 0, y: 69, z: 0 }, BlockState::Stone); assert!( block_state.is_some(), "Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed" ); - let mut entity = world.entity_mut(0).unwrap(); - entity.ai_step(); - // delta will change, but it won't move until next tick - assert_eq!(entity.pos().y, 70.); - assert!(entity.delta.y < 0.); - entity.ai_step(); - // the second tick applies the delta to the position, but it also does collision - assert_eq!(entity.pos().y, 70.); + app.update(); + { + let entity_pos = *app.world.get::<Position>(entity).unwrap(); + // delta will change, but it won't move until next tick + assert_eq!(entity_pos.y, 70.); + let entity_physics = app.world.get::<Physics>(entity).unwrap().clone(); + assert!(entity_physics.delta.y < 0.); + } + app.update(); + { + let entity_pos = *app.world.get::<Position>(entity).unwrap(); + // the second tick applies the delta to the position, but it also does collision + assert_eq!(entity_pos.y, 70.); + } } #[test] fn test_slab_collision() { - let mut world = PartialWorld::default(); - world - .set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default())) - .unwrap(); - world.add_entity( - 0, - EntityData::new( - Uuid::nil(), - Vec3 { - x: 0.5, - y: 71., - z: 0.5, - }, - EntityMetadata::Player(metadata::Player::default()), - ), + let mut app = make_test_app(); + let world_lock = app.world.resource_mut::<WorldContainer>().insert( + ResourceLocation::new("minecraft:overworld").unwrap(), + 384, + -64, ); - let block_state = world.set_block_state( + let mut partial_world = PartialWorld::default(); + + partial_world.chunks.set( + &ChunkPos { x: 0, z: 0 }, + Some(Chunk::default()), + &mut world_lock.write().chunks, + ); + let entity = app + .world + .spawn(( + EntityBundle::new( + Uuid::nil(), + Vec3 { + x: 0.5, + y: 71., + z: 0.5, + }, + azalea_registry::EntityKind::Player, + ResourceLocation::new("minecraft:overworld").unwrap(), + ), + MinecraftEntityId(0), + Local, + )) + .id(); + let block_state = partial_world.chunks.set_block_state( &BlockPos { x: 0, y: 69, z: 0 }, BlockState::StoneSlab_BottomFalse, + &mut world_lock.write().chunks, ); assert!( block_state.is_some(), "Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed" ); - let mut entity = world.entity_mut(0).unwrap(); // do a few steps so we fall on the slab for _ in 0..20 { - entity.ai_step(); + app.update(); } - assert_eq!(entity.pos().y, 69.5); + let entity_pos = app.world.get::<Position>(entity).unwrap(); + assert_eq!(entity_pos.y, 69.5); } #[test] fn test_top_slab_collision() { - let mut world = PartialWorld::default(); - world - .set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default())) - .unwrap(); - world.add_entity( - 0, - EntityData::new( - Uuid::nil(), - Vec3 { - x: 0.5, - y: 71., - z: 0.5, - }, - EntityMetadata::Player(metadata::Player::default()), - ), + let mut app = make_test_app(); + let world_lock = app.world.resource_mut::<WorldContainer>().insert( + ResourceLocation::new("minecraft:overworld").unwrap(), + 384, + -64, + ); + let mut partial_world = PartialWorld::default(); + + partial_world.chunks.set( + &ChunkPos { x: 0, z: 0 }, + Some(Chunk::default()), + &mut world_lock.write().chunks, ); - let block_state = world.set_block_state( + let entity = app + .world + .spawn(( + EntityBundle::new( + Uuid::nil(), + Vec3 { + x: 0.5, + y: 71., + z: 0.5, + }, + azalea_registry::EntityKind::Player, + ResourceLocation::new("minecraft:overworld").unwrap(), + ), + MinecraftEntityId(0), + Local, + )) + .id(); + let block_state = world_lock.write().chunks.set_block_state( &BlockPos { x: 0, y: 69, z: 0 }, BlockState::StoneSlab_TopFalse, ); @@ -363,33 +523,47 @@ mod tests { block_state.is_some(), "Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed" ); - let mut entity = world.entity_mut(0).unwrap(); // do a few steps so we fall on the slab for _ in 0..20 { - entity.ai_step(); + app.update(); } - assert_eq!(entity.pos().y, 70.); + let entity_pos = app.world.get::<Position>(entity).unwrap(); + assert_eq!(entity_pos.y, 70.); } #[test] fn test_weird_wall_collision() { - let mut world = PartialWorld::default(); - world - .set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default())) - .unwrap(); - world.add_entity( - 0, - EntityData::new( - Uuid::nil(), - Vec3 { - x: 0.5, - y: 73., - z: 0.5, - }, - EntityMetadata::Player(metadata::Player::default()), - ), + let mut app = make_test_app(); + let world_lock = app.world.resource_mut::<WorldContainer>().insert( + ResourceLocation::new("minecraft:overworld").unwrap(), + 384, + -64, + ); + let mut partial_world = PartialWorld::default(); + + partial_world.chunks.set( + &ChunkPos { x: 0, z: 0 }, + Some(Chunk::default()), + &mut world_lock.write().chunks, ); - let block_state = world.set_block_state( + let entity = app + .world + .spawn(( + EntityBundle::new( + Uuid::nil(), + Vec3 { + x: 0.5, + y: 73., + z: 0.5, + }, + azalea_registry::EntityKind::Player, + ResourceLocation::new("minecraft:overworld").unwrap(), + ), + MinecraftEntityId(0), + Local, + )) + .id(); + let block_state = world_lock.write().chunks.set_block_state( &BlockPos { x: 0, y: 69, z: 0 }, BlockState::CobblestoneWall_LowLowLowFalseFalseLow, ); @@ -397,11 +571,12 @@ mod tests { block_state.is_some(), "Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed" ); - let mut entity = world.entity_mut(0).unwrap(); // do a few steps so we fall on the slab for _ in 0..20 { - entity.ai_step(); + app.update(); } - assert_eq!(entity.pos().y, 70.5); + + let entity_pos = app.world.get::<Position>(entity).unwrap(); + assert_eq!(entity_pos.y, 70.5); } } |
