aboutsummaryrefslogtreecommitdiff
path: root/azalea-physics/src
diff options
context:
space:
mode:
Diffstat (limited to 'azalea-physics/src')
-rwxr-xr-xazalea-physics/src/collision/discrete_voxel_shape.rs2
-rw-r--r--azalea-physics/src/collision/mod.rs304
-rwxr-xr-xazalea-physics/src/collision/shape.rs2
-rw-r--r--azalea-physics/src/collision/world_collisions.rs39
-rw-r--r--azalea-physics/src/lib.rs571
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);
}
}