mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
Climbing (#121)
* start implementing climbing * fix tests * fix bots running at lower tick rate
This commit is contained in:
parent
f15f0325c0
commit
348c71b97b
10 changed files with 237 additions and 61 deletions
|
@ -327,17 +327,11 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
}
|
||||
|
||||
property_enums.extend(quote! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum #property_struct_name {
|
||||
#property_enum_variants
|
||||
}
|
||||
|
||||
// impl Property for #property_struct_name {
|
||||
// type Value = Self;
|
||||
|
||||
// fn try_from_block_state
|
||||
// }
|
||||
|
||||
impl From<u32> for #property_struct_name {
|
||||
fn from(value: u32) -> Self {
|
||||
match value {
|
||||
|
@ -354,13 +348,9 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
property_variant_types = vec!["true".to_string(), "false".to_string()];
|
||||
|
||||
property_enums.extend(quote! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct #property_struct_name(pub bool);
|
||||
|
||||
// impl Property for #property_struct_name {
|
||||
// type Value = bool;
|
||||
// }
|
||||
|
||||
impl From<u32> for #property_struct_name {
|
||||
fn from(value: u32) -> Self {
|
||||
match value {
|
||||
|
@ -542,10 +532,9 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
// add to properties_to_state_ids
|
||||
let property_variants = properties_to_state_ids
|
||||
.entry(property_value_name_ident.to_string())
|
||||
.or_insert_with(Vec::new);
|
||||
let property_variant_data = property_variants
|
||||
.iter_mut()
|
||||
.find(|v| v.ident.to_string() == variant.to_string());
|
||||
.or_default();
|
||||
let property_variant_data =
|
||||
property_variants.iter_mut().find(|v| v.ident == variant);
|
||||
if let Some(property_variant_data) = property_variant_data {
|
||||
property_variant_data.block_state_ids.push(state_id);
|
||||
} else {
|
||||
|
|
|
@ -167,6 +167,12 @@ impl From<FluidState> for BlockState {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<BlockState> for azalea_registry::Block {
|
||||
fn from(value: BlockState) -> Self {
|
||||
Box::<dyn Block>::from(value).as_registry_block()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -731,7 +731,11 @@ async fn run_schedule_loop(
|
|||
.map(|last_tick| last_tick.elapsed() > Duration::from_millis(50))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
last_tick = Some(Instant::now());
|
||||
if let Some(last_tick) = &mut last_tick {
|
||||
*last_tick += Duration::from_millis(50);
|
||||
} else {
|
||||
last_tick = Some(Instant::now());
|
||||
}
|
||||
ecs.run_schedule(GameTick);
|
||||
}
|
||||
|
||||
|
|
|
@ -134,7 +134,7 @@ pub struct Rotations {
|
|||
pub z: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, McBuf, Default, Component)]
|
||||
#[derive(Clone, Debug, Copy, McBuf, Default, Component, Eq, PartialEq)]
|
||||
pub enum Pose {
|
||||
#[default]
|
||||
Standing = 0,
|
||||
|
|
|
@ -116,8 +116,13 @@ pub fn on_pos(offset: f32, chunk_storage: &ChunkStorage, pos: &Position) -> Bloc
|
|||
|
||||
/// The Minecraft UUID of the entity. For players, this is their actual player
|
||||
/// UUID, and for other entities it's just random.
|
||||
#[derive(Component, Deref, DerefMut, Clone, Copy)]
|
||||
#[derive(Component, Deref, DerefMut, Clone, Copy, Default)]
|
||||
pub struct EntityUuid(Uuid);
|
||||
impl EntityUuid {
|
||||
pub fn new(uuid: Uuid) -> Self {
|
||||
Self(uuid)
|
||||
}
|
||||
}
|
||||
impl Debug for EntityUuid {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
(self.0).fmt(f)
|
||||
|
@ -228,6 +233,10 @@ pub struct Physics {
|
|||
pub bounding_box: AABB,
|
||||
|
||||
pub has_impulse: bool,
|
||||
|
||||
pub horizontal_collision: bool,
|
||||
// pub minor_horizontal_collision: bool,
|
||||
pub vertical_collision: bool,
|
||||
}
|
||||
|
||||
impl Physics {
|
||||
|
@ -246,6 +255,9 @@ impl Physics {
|
|||
dimensions,
|
||||
|
||||
has_impulse: false,
|
||||
|
||||
horizontal_collision: false,
|
||||
vertical_collision: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -311,6 +323,7 @@ pub struct EntityBundle {
|
|||
pub attributes: Attributes,
|
||||
pub jumping: Jumping,
|
||||
pub fluid_on_eyes: FluidOnEyes,
|
||||
pub on_climbable: OnClimbable,
|
||||
}
|
||||
|
||||
impl EntityBundle {
|
||||
|
@ -346,6 +359,7 @@ impl EntityBundle {
|
|||
|
||||
jumping: Jumping(false),
|
||||
fluid_on_eyes: FluidOnEyes(azalea_registry::Fluid::Empty),
|
||||
on_climbable: OnClimbable(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -373,6 +387,9 @@ impl FluidOnEyes {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)]
|
||||
pub struct OnClimbable(bool);
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
|
|
|
@ -3,6 +3,7 @@ mod relative_updates;
|
|||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use azalea_block::BlockState;
|
||||
use azalea_core::position::{BlockPos, ChunkPos, Vec3};
|
||||
use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId};
|
||||
use bevy_app::{App, Plugin, PreUpdate, Update};
|
||||
|
@ -11,7 +12,8 @@ use derive_more::{Deref, DerefMut};
|
|||
use tracing::debug;
|
||||
|
||||
use crate::{
|
||||
metadata::Health, Dead, EyeHeight, FluidOnEyes, LocalEntity, LookDirection, Physics, Position,
|
||||
metadata::Health, Dead, EyeHeight, FluidOnEyes, LocalEntity, LookDirection, OnClimbable,
|
||||
Physics, Position,
|
||||
};
|
||||
|
||||
use indexing::EntityUuidIndex;
|
||||
|
@ -48,6 +50,7 @@ impl Plugin for EntityPlugin {
|
|||
add_dead,
|
||||
clamp_look_direction,
|
||||
update_fluid_on_eyes,
|
||||
update_on_climbable,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -106,6 +109,72 @@ pub fn update_fluid_on_eyes(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn update_on_climbable(
|
||||
mut query: Query<(&mut OnClimbable, &Position, &InstanceName)>,
|
||||
instance_container: Res<InstanceContainer>,
|
||||
) {
|
||||
for (mut on_climbable, position, instance_name) in query.iter_mut() {
|
||||
// TODO: there's currently no gamemode component that can be accessed from here,
|
||||
// maybe LocalGameMode should be replaced with two components, maybe called
|
||||
// EntityGameMode and PreviousGameMode?
|
||||
|
||||
// if game_mode == GameMode::Spectator {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
let Some(instance) = instance_container.get(instance_name) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let instance = instance.read();
|
||||
|
||||
let block_pos = BlockPos::from(position);
|
||||
let block_state_at_feet = instance.get_block_state(&block_pos).unwrap_or_default();
|
||||
let block_at_feet = Box::<dyn azalea_block::Block>::from(block_state_at_feet);
|
||||
let registry_block_at_feet = block_at_feet.as_registry_block();
|
||||
|
||||
**on_climbable = azalea_registry::tags::blocks::CLIMBABLE.contains(®istry_block_at_feet)
|
||||
|| (azalea_registry::tags::blocks::TRAPDOORS.contains(®istry_block_at_feet)
|
||||
&& is_trapdoor_useable_as_ladder(block_state_at_feet, block_pos, &instance));
|
||||
}
|
||||
}
|
||||
|
||||
fn is_trapdoor_useable_as_ladder(
|
||||
block_state: BlockState,
|
||||
block_pos: BlockPos,
|
||||
instance: &azalea_world::Instance,
|
||||
) -> bool {
|
||||
// trapdoor must be open
|
||||
if !block_state
|
||||
.property::<azalea_block::properties::Open>()
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// block below must be a ladder
|
||||
let block_below = instance
|
||||
.get_block_state(&block_pos.down(1))
|
||||
.unwrap_or_default();
|
||||
let registry_block_below =
|
||||
Box::<dyn azalea_block::Block>::from(block_below).as_registry_block();
|
||||
if registry_block_below != azalea_registry::Block::Ladder {
|
||||
return false;
|
||||
}
|
||||
// and the ladder must be facing the same direction as the trapdoor
|
||||
let ladder_facing = block_below
|
||||
.property::<azalea_block::properties::Facing>()
|
||||
.expect("ladder block must have facing property");
|
||||
let trapdoor_facing = block_state
|
||||
.property::<azalea_block::properties::Facing>()
|
||||
.expect("trapdoor block must have facing property");
|
||||
if ladder_facing != trapdoor_facing {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// A component that lists all the local player entities that have this entity
|
||||
/// loaded. If this is empty, the entity will be removed from the ECS.
|
||||
#[derive(Component, Clone, Deref, DerefMut)]
|
||||
|
|
|
@ -136,7 +136,7 @@ pub fn move_colliding(
|
|||
_mover_type: &MoverType,
|
||||
movement: &Vec3,
|
||||
world: &Instance,
|
||||
mut position: Mut<azalea_entity::Position>,
|
||||
position: &mut Mut<azalea_entity::Position>,
|
||||
physics: &mut azalea_entity::Physics,
|
||||
) -> Result<(), MoveEntityError> {
|
||||
// TODO: do all these
|
||||
|
@ -175,8 +175,8 @@ pub fn move_colliding(
|
|||
}
|
||||
};
|
||||
|
||||
if new_pos != **position {
|
||||
**position = new_pos;
|
||||
if new_pos != ***position {
|
||||
***position = new_pos;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,11 +185,14 @@ pub fn move_colliding(
|
|||
let horizontal_collision = x_collision || z_collision;
|
||||
let vertical_collision = movement.y != collide_result.y;
|
||||
let on_ground = vertical_collision && movement.y < 0.;
|
||||
|
||||
physics.horizontal_collision = horizontal_collision;
|
||||
physics.vertical_collision = vertical_collision;
|
||||
physics.on_ground = on_ground;
|
||||
|
||||
// TODO: minecraft checks for a "minor" horizontal collision here
|
||||
|
||||
let _block_pos_below = azalea_entity::on_pos_legacy(&world.chunks, &position);
|
||||
let _block_pos_below = azalea_entity::on_pos_legacy(&world.chunks, position);
|
||||
// let _block_state_below = self
|
||||
// .world
|
||||
// .get_block_state(&block_pos_below)
|
||||
|
|
|
@ -13,7 +13,7 @@ use azalea_core::{
|
|||
};
|
||||
use azalea_entity::{
|
||||
metadata::Sprinting, move_relative, Attributes, InLoadedChunk, Jumping, LocalEntity,
|
||||
LookDirection, Physics, Position,
|
||||
LookDirection, OnClimbable, Physics, Pose, Position,
|
||||
};
|
||||
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
||||
use bevy_app::{App, Plugin};
|
||||
|
@ -52,14 +52,28 @@ fn travel(
|
|||
&mut LookDirection,
|
||||
&mut Position,
|
||||
Option<&Sprinting>,
|
||||
Option<&Pose>,
|
||||
&Attributes,
|
||||
&InstanceName,
|
||||
&OnClimbable,
|
||||
&Jumping,
|
||||
),
|
||||
(With<LocalEntity>, With<InLoadedChunk>),
|
||||
>,
|
||||
instance_container: Res<InstanceContainer>,
|
||||
) {
|
||||
for (mut physics, direction, position, sprinting, attributes, world_name) in &mut query {
|
||||
for (
|
||||
mut physics,
|
||||
direction,
|
||||
position,
|
||||
sprinting,
|
||||
pose,
|
||||
attributes,
|
||||
world_name,
|
||||
on_climbable,
|
||||
jumping,
|
||||
) in &mut query
|
||||
{
|
||||
let world_lock = instance_container
|
||||
.get(world_name)
|
||||
.expect("All entities should be in a valid world");
|
||||
|
@ -95,13 +109,18 @@ fn travel(
|
|||
|
||||
// this applies the current delta
|
||||
let mut movement = handle_relative_friction_and_calculate_movement(
|
||||
block_friction,
|
||||
&world,
|
||||
&mut physics,
|
||||
&direction,
|
||||
position,
|
||||
attributes,
|
||||
sprinting.map(|s| **s).unwrap_or(false),
|
||||
HandleRelativeFrictionAndCalculateMovementOpts {
|
||||
block_friction,
|
||||
world: &world,
|
||||
physics: &mut physics,
|
||||
direction: &direction,
|
||||
position,
|
||||
attributes,
|
||||
is_sprinting: sprinting.map(|s| **s).unwrap_or(false),
|
||||
on_climbable,
|
||||
pose,
|
||||
jumping,
|
||||
},
|
||||
);
|
||||
|
||||
movement.y -= gravity;
|
||||
|
@ -223,15 +242,33 @@ fn get_block_pos_below_that_affects_movement(position: &Position) -> BlockPos {
|
|||
)
|
||||
}
|
||||
|
||||
fn handle_relative_friction_and_calculate_movement(
|
||||
// opts for handle_relative_friction_and_calculate_movement
|
||||
struct HandleRelativeFrictionAndCalculateMovementOpts<'a> {
|
||||
block_friction: f32,
|
||||
world: &Instance,
|
||||
physics: &mut Physics,
|
||||
direction: &LookDirection,
|
||||
// this is kept as a Mut for bevy change tracking
|
||||
position: Mut<Position>,
|
||||
attributes: &Attributes,
|
||||
world: &'a Instance,
|
||||
physics: &'a mut Physics,
|
||||
direction: &'a LookDirection,
|
||||
position: Mut<'a, Position>,
|
||||
attributes: &'a Attributes,
|
||||
is_sprinting: bool,
|
||||
on_climbable: &'a OnClimbable,
|
||||
pose: Option<&'a Pose>,
|
||||
jumping: &'a Jumping,
|
||||
}
|
||||
|
||||
fn handle_relative_friction_and_calculate_movement(
|
||||
HandleRelativeFrictionAndCalculateMovementOpts {
|
||||
block_friction,
|
||||
world,
|
||||
physics,
|
||||
direction,
|
||||
mut position,
|
||||
attributes,
|
||||
is_sprinting,
|
||||
on_climbable,
|
||||
pose,
|
||||
jumping,
|
||||
}: HandleRelativeFrictionAndCalculateMovementOpts<'_>,
|
||||
) -> Vec3 {
|
||||
move_relative(
|
||||
physics,
|
||||
|
@ -243,12 +280,14 @@ fn handle_relative_friction_and_calculate_movement(
|
|||
z: physics.zza as f64,
|
||||
},
|
||||
);
|
||||
// entity.delta = entity.handle_on_climbable(entity.delta);
|
||||
|
||||
physics.velocity = handle_on_climbable(physics.velocity, on_climbable, &position, world, pose);
|
||||
|
||||
move_colliding(
|
||||
&MoverType::Own,
|
||||
&physics.velocity.clone(),
|
||||
world,
|
||||
position,
|
||||
&mut position,
|
||||
physics,
|
||||
)
|
||||
.expect("Entity should exist.");
|
||||
|
@ -258,11 +297,58 @@ fn handle_relative_friction_and_calculate_movement(
|
|||
// || entity.getFeetBlockState().is(Blocks.POWDER_SNOW) &&
|
||||
// PowderSnowBlock.canEntityWalkOnPowderSnow(entity))) { var3 = new
|
||||
// Vec3(var3.x, 0.2D, var3.z); }
|
||||
// TODO: powdered snow
|
||||
|
||||
if physics.horizontal_collision || **jumping {
|
||||
let block_at_feet: azalea_registry::Block = world
|
||||
.chunks
|
||||
.get_block_state(&(*position).into())
|
||||
.unwrap_or_default()
|
||||
.into();
|
||||
|
||||
// TODO: powdered snow
|
||||
if **on_climbable || block_at_feet == azalea_registry::Block::PowderSnow {
|
||||
physics.velocity.y = 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
physics.velocity
|
||||
}
|
||||
|
||||
fn handle_on_climbable(
|
||||
velocity: Vec3,
|
||||
on_climbable: &OnClimbable,
|
||||
position: &Position,
|
||||
world: &Instance,
|
||||
pose: Option<&Pose>,
|
||||
) -> Vec3 {
|
||||
if !**on_climbable {
|
||||
return velocity;
|
||||
}
|
||||
|
||||
// minecraft does resetFallDistance here
|
||||
|
||||
const CLIMBING_SPEED: f64 = 0.15_f32 as f64;
|
||||
|
||||
let x = f64::clamp(velocity.x, -CLIMBING_SPEED, CLIMBING_SPEED);
|
||||
let z = f64::clamp(velocity.z, -CLIMBING_SPEED, CLIMBING_SPEED);
|
||||
let mut y = f64::max(velocity.y, -CLIMBING_SPEED);
|
||||
|
||||
// sneaking on ladders/vines
|
||||
if y < 0.0
|
||||
&& pose.copied() == Some(Pose::Sneaking)
|
||||
&& azalea_registry::Block::from(
|
||||
world
|
||||
.chunks
|
||||
.get_block_state(&position.into())
|
||||
.unwrap_or_default(),
|
||||
) != azalea_registry::Block::Scaffolding
|
||||
{
|
||||
y = 0.;
|
||||
}
|
||||
|
||||
Vec3 { x, y, z }
|
||||
}
|
||||
|
||||
// private float getFrictionInfluencedSpeed(float friction) {
|
||||
// return this.onGround ? this.getSpeed() * (0.21600002F / (friction *
|
||||
// friction * friction)) : this.flyingSpeed; }
|
||||
|
|
|
@ -43,6 +43,8 @@ pub fn best_tool_in_hotbar_for_block(block: BlockState, menu: &Menu) -> BestTool
|
|||
dimensions: Default::default(),
|
||||
bounding_box: Default::default(),
|
||||
has_impulse: Default::default(),
|
||||
horizontal_collision: Default::default(),
|
||||
vertical_collision: Default::default(),
|
||||
},
|
||||
&FluidOnEyes::new(Fluid::Empty),
|
||||
)
|
||||
|
|
|
@ -7,13 +7,13 @@ use azalea_client::{
|
|||
};
|
||||
use azalea_core::{position::Vec3, resource_location::ResourceLocation, tick::GameTick};
|
||||
use azalea_entity::{
|
||||
attributes::AttributeInstance, metadata::Sprinting, Attributes, EntityDimensions, Physics,
|
||||
Position,
|
||||
attributes::AttributeInstance, Attributes, EntityDimensions, Physics, Position,
|
||||
};
|
||||
use azalea_world::{ChunkStorage, Instance, InstanceContainer, InstanceName, MinecraftEntityId};
|
||||
use azalea_world::{ChunkStorage, Instance, InstanceContainer, MinecraftEntityId};
|
||||
use bevy_app::App;
|
||||
use bevy_ecs::prelude::*;
|
||||
use parking_lot::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Bundle, Clone)]
|
||||
pub struct SimulatedPlayerBundle {
|
||||
|
@ -82,24 +82,24 @@ impl Simulation {
|
|||
schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded);
|
||||
});
|
||||
|
||||
let entity = app
|
||||
.world
|
||||
.spawn((
|
||||
MinecraftEntityId(0),
|
||||
InstanceName(instance_name),
|
||||
azalea_entity::LocalEntity,
|
||||
azalea_entity::Jumping::default(),
|
||||
azalea_entity::LookDirection::default(),
|
||||
Sprinting(true),
|
||||
azalea_entity::metadata::Player,
|
||||
azalea_entity::EyeHeight::new(player.physics.dimensions.height * 0.85),
|
||||
player,
|
||||
))
|
||||
.id();
|
||||
let mut entity = app.world.spawn((
|
||||
MinecraftEntityId(0),
|
||||
azalea_entity::LocalEntity,
|
||||
azalea_entity::metadata::PlayerMetadataBundle::default(),
|
||||
azalea_entity::EntityBundle::new(
|
||||
Uuid::nil(),
|
||||
*player.position,
|
||||
azalea_registry::EntityKind::Player,
|
||||
instance_name,
|
||||
),
|
||||
));
|
||||
entity.insert(player);
|
||||
|
||||
let entity_id = entity.id();
|
||||
|
||||
Self {
|
||||
app,
|
||||
entity,
|
||||
entity: entity_id,
|
||||
_instance: instance,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue