mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
azalea compiles lol
This commit is contained in:
parent
259e7b44ce
commit
9f634a4d83
17 changed files with 515 additions and 348 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -174,8 +174,11 @@ dependencies = [
|
|||
"azalea-protocol",
|
||||
"azalea-registry",
|
||||
"azalea-world",
|
||||
"bevy_app",
|
||||
"bevy_ecs",
|
||||
"env_logger",
|
||||
"futures",
|
||||
"iyes_loopless",
|
||||
"log",
|
||||
"nohash-hasher",
|
||||
"num-traits",
|
||||
|
@ -351,6 +354,9 @@ dependencies = [
|
|||
"azalea-block",
|
||||
"azalea-core",
|
||||
"azalea-world",
|
||||
"bevy_app",
|
||||
"bevy_ecs",
|
||||
"iyes_loopless",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"uuid",
|
||||
|
@ -430,6 +436,7 @@ dependencies = [
|
|||
"bevy_ecs",
|
||||
"derive_more",
|
||||
"enum-as-inner",
|
||||
"iyes_loopless",
|
||||
"log",
|
||||
"nohash-hasher",
|
||||
"once_cell",
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
local_player::{
|
||||
death_event, send_tick_event, update_in_loaded_chunk, LocalPlayer, PhysicsState,
|
||||
},
|
||||
movement::{local_player_ai_step, send_position},
|
||||
movement::{local_player_ai_step, send_position, sprint_listener, walk_listener},
|
||||
packet_handling::{self, PacketHandlerPlugin},
|
||||
plugins::PluginStates,
|
||||
Account, PlayerInfo,
|
||||
|
@ -42,7 +42,7 @@ use bevy_ecs::{
|
|||
use iyes_loopless::prelude::*;
|
||||
use log::{debug, error};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use std::{fmt::Debug, io, ops::DerefMut, sync::Arc, time::Duration};
|
||||
use std::{fmt::Debug, io, net::SocketAddr, ops::DerefMut, sync::Arc, time::Duration};
|
||||
use thiserror::Error;
|
||||
use tokio::{sync::mpsc, time};
|
||||
|
||||
|
@ -82,6 +82,7 @@ pub enum Event {
|
|||
|
||||
/// Client has the things that a user interacting with the library will want.
|
||||
/// Things that a player in the world will want to know are in [`LocalPlayer`].
|
||||
#[derive(Clone)]
|
||||
pub struct Client {
|
||||
/// The [`GameProfile`] for our client. This contains your username, UUID,
|
||||
/// and skin data.
|
||||
|
@ -174,8 +175,36 @@ impl Client {
|
|||
let address: ServerAddress = address.try_into().map_err(|_| JoinError::InvalidAddress)?;
|
||||
let resolved_address = resolver::resolve_address(&address).await?;
|
||||
|
||||
let conn = Connection::new(&resolved_address).await?;
|
||||
let (conn, game_profile) = Self::handshake(conn, account, &address).await?;
|
||||
// An event that causes the schedule to run. This is only used internally.
|
||||
let (run_schedule_sender, run_schedule_receiver) = mpsc::unbounded_channel();
|
||||
let ecs_lock = start_ecs(run_schedule_receiver, run_schedule_sender.clone());
|
||||
|
||||
{
|
||||
let mut ecs = ecs_lock.lock();
|
||||
ecs.init_resource::<EntityInfos>();
|
||||
}
|
||||
|
||||
Self::start_client(
|
||||
ecs_lock,
|
||||
account,
|
||||
&address,
|
||||
&resolved_address,
|
||||
run_schedule_sender,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Create a [`Client`] when you already have the ECS made with
|
||||
/// [`start_ecs`]. You'd usually want to use [`Self::join`] instead.
|
||||
pub async fn start_client(
|
||||
ecs_lock: Arc<Mutex<bevy_ecs::world::World>>,
|
||||
account: &Account,
|
||||
address: &ServerAddress,
|
||||
resolved_address: &SocketAddr,
|
||||
run_schedule_sender: mpsc::UnboundedSender<()>,
|
||||
) -> Result<(Self, mpsc::UnboundedReceiver<Event>), JoinError> {
|
||||
let conn = Connection::new(resolved_address).await?;
|
||||
let (conn, game_profile) = Self::handshake(conn, account, address).await?;
|
||||
let (read_conn, write_conn) = conn.into_split();
|
||||
|
||||
// The buffer has to be 1 to avoid a bug where if it lags events are
|
||||
|
@ -184,14 +213,9 @@ impl Client {
|
|||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
tx.send(Event::Init).unwrap();
|
||||
|
||||
// An event that causes the schedule to run. This is only used internally.
|
||||
let (run_schedule_sender, run_schedule_receiver) = mpsc::unbounded_channel();
|
||||
|
||||
let ecs_lock = start_ecs(run_schedule_receiver, run_schedule_sender.clone()).await;
|
||||
|
||||
let mut ecs = ecs_lock.lock();
|
||||
ecs.init_resource::<EntityInfos>();
|
||||
|
||||
// Make the ecs entity for this client
|
||||
let entity_mut = ecs.spawn_empty();
|
||||
let entity = entity_mut.id();
|
||||
|
||||
|
@ -448,20 +472,20 @@ impl Client {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Query data of our player's entity.
|
||||
/// A convenience function for getting components of our player's entity.
|
||||
pub fn query<'w, Q: WorldQuery>(
|
||||
&self,
|
||||
ecs: &'w mut bevy_ecs::world::World,
|
||||
) -> <Q as WorldQuery>::Item<'w> {
|
||||
ecs.query::<Q>()
|
||||
.get_mut(ecs, self.entity)
|
||||
.expect("Player entity should always exist when being queried")
|
||||
.expect("Our client is missing a required component.")
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the protocol and game tick loop.
|
||||
#[doc(hidden)]
|
||||
pub async fn start_ecs(
|
||||
pub fn start_ecs(
|
||||
run_schedule_receiver: mpsc::UnboundedReceiver<()>,
|
||||
run_schedule_sender: mpsc::UnboundedSender<()>,
|
||||
) -> Arc<Mutex<bevy_ecs::world::World>> {
|
||||
|
@ -470,16 +494,22 @@ pub async fn start_ecs(
|
|||
// you might be able to just drop the lock or put it in its own scope to fix
|
||||
|
||||
let mut app = App::new();
|
||||
app.add_fixed_timestep(Duration::from_millis(50), "tick")
|
||||
.add_fixed_timestep_system_set(
|
||||
"tick",
|
||||
0,
|
||||
SystemSet::new()
|
||||
.with_system(send_position)
|
||||
.with_system(update_in_loaded_chunk)
|
||||
.with_system(local_player_ai_step)
|
||||
.with_system(send_tick_event),
|
||||
);
|
||||
app.add_fixed_timestep(Duration::from_millis(50), "tick");
|
||||
app.add_fixed_timestep_system_set(
|
||||
"tick",
|
||||
0,
|
||||
SystemSet::new()
|
||||
.with_system(send_position)
|
||||
.with_system(update_in_loaded_chunk)
|
||||
.with_system(sprint_listener.label("sprint_listener").before("travel"))
|
||||
.with_system(walk_listener.label("walk_listener").before("travel"))
|
||||
.with_system(
|
||||
local_player_ai_step
|
||||
.before("ai_step")
|
||||
.after("sprint_listener"),
|
||||
)
|
||||
.with_system(send_tick_event),
|
||||
);
|
||||
|
||||
// fire the Death event when the player dies.
|
||||
app.add_system(death_event.after("tick").after("packet"));
|
||||
|
|
|
@ -22,7 +22,8 @@ mod player;
|
|||
mod plugins;
|
||||
|
||||
pub use account::Account;
|
||||
pub use client::{ChatPacket, Client, ClientInformation, Event, JoinError};
|
||||
pub use movement::{SprintDirection, WalkDirection};
|
||||
pub use client::{start_ecs, ChatPacket, Client, ClientInformation, Event, JoinError};
|
||||
pub use local_player::LocalPlayer;
|
||||
pub use movement::{SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection};
|
||||
pub use player::PlayerInfo;
|
||||
pub use plugins::{Plugin, PluginState, PluginStates, Plugins};
|
||||
|
|
|
@ -8,8 +8,10 @@ use azalea_protocol::packets::game::{
|
|||
serverbound_move_player_status_only_packet::ServerboundMovePlayerStatusOnlyPacket,
|
||||
};
|
||||
use azalea_world::entity::metadata::Sprinting;
|
||||
use azalea_world::entity::{Attributes, MinecraftEntityId};
|
||||
use azalea_world::entity::{Attributes, Entity, Jumping, MinecraftEntityId};
|
||||
use azalea_world::{entity, MoveEntityError};
|
||||
use bevy_ecs::event::EventReader;
|
||||
use bevy_ecs::prelude::With;
|
||||
use bevy_ecs::system::Query;
|
||||
use std::backtrace::Backtrace;
|
||||
use thiserror::Error;
|
||||
|
@ -40,15 +42,15 @@ impl Client {
|
|||
/// recommended.
|
||||
pub fn set_jumping(&mut self, jumping: bool) {
|
||||
let mut ecs = self.ecs.lock();
|
||||
let mut physics = self.query::<&mut entity::Physics>(&mut ecs);
|
||||
physics.jumping = jumping;
|
||||
let mut jumping_mut = self.query::<&mut Jumping>(&mut ecs);
|
||||
**jumping_mut = jumping;
|
||||
}
|
||||
|
||||
/// Returns whether the player will try to jump next tick.
|
||||
pub fn jumping(&self) -> bool {
|
||||
let mut ecs = self.ecs.lock();
|
||||
let physics = self.query::<&mut entity::Physics>(&mut ecs);
|
||||
physics.jumping
|
||||
let jumping_ref = self.query::<&Jumping>(&mut ecs);
|
||||
**jumping_ref
|
||||
}
|
||||
|
||||
/// Sets your rotation. `y_rot` is yaw (looking to the side), `x_rot` is
|
||||
|
@ -248,7 +250,7 @@ pub fn local_player_ai_step(
|
|||
&mut entity::metadata::Sprinting,
|
||||
&mut entity::Attributes,
|
||||
),
|
||||
&LocalPlayerInLoadedChunk,
|
||||
With<LocalPlayerInLoadedChunk>,
|
||||
>,
|
||||
) {
|
||||
println!("local_player_ai_step");
|
||||
|
@ -289,14 +291,6 @@ pub fn local_player_ai_step(
|
|||
{
|
||||
set_sprinting(true, &mut sprinting, &mut attributes);
|
||||
}
|
||||
|
||||
azalea_physics::ai_step(
|
||||
&local_player.world.read(),
|
||||
&mut physics,
|
||||
&mut position,
|
||||
&sprinting,
|
||||
&attributes,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -319,14 +313,10 @@ impl Client {
|
|||
/// ```
|
||||
pub fn walk(&mut self, direction: WalkDirection) {
|
||||
let mut ecs = self.ecs.lock();
|
||||
let (mut physics_state, mut sprinting, mut attributes) =
|
||||
self.query::<(&mut PhysicsState, &mut Sprinting, &mut Attributes)>(&mut ecs);
|
||||
walk(
|
||||
ecs.send_event(StartWalkEvent {
|
||||
entity: self.entity,
|
||||
direction,
|
||||
&mut physics_state,
|
||||
&mut sprinting,
|
||||
&mut attributes,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
/// Start sprinting in the given direction. To stop moving, call
|
||||
|
@ -346,28 +336,49 @@ impl Client {
|
|||
/// ```
|
||||
pub fn sprint(&mut self, direction: SprintDirection) {
|
||||
let mut ecs = self.ecs.lock();
|
||||
let mut physics_state = self.query::<&mut PhysicsState>(&mut ecs);
|
||||
sprint(direction, &mut physics_state);
|
||||
ecs.send_event(StartSprintEvent {
|
||||
entity: self.entity,
|
||||
direction,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StartWalkEvent {
|
||||
pub entity: Entity,
|
||||
pub direction: WalkDirection,
|
||||
}
|
||||
|
||||
/// Start walking in the given direction. To sprint, use
|
||||
/// [`Client::sprint`]. To stop walking, call walk with
|
||||
/// `WalkDirection::None`.
|
||||
pub fn walk(
|
||||
direction: WalkDirection,
|
||||
physics_state: &mut PhysicsState,
|
||||
sprinting: &mut entity::metadata::Sprinting,
|
||||
attributes: &mut entity::Attributes,
|
||||
pub fn walk_listener(
|
||||
mut events: EventReader<StartWalkEvent>,
|
||||
mut query: Query<(&mut PhysicsState, &mut Sprinting, &mut Attributes)>,
|
||||
) {
|
||||
physics_state.move_direction = direction;
|
||||
set_sprinting(false, sprinting, attributes);
|
||||
for event in events.iter() {
|
||||
if let Ok((mut physics_state, mut sprinting, mut attributes)) = query.get_mut(event.entity)
|
||||
{
|
||||
physics_state.move_direction = event.direction;
|
||||
set_sprinting(false, &mut sprinting, &mut attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StartSprintEvent {
|
||||
pub entity: Entity,
|
||||
pub direction: SprintDirection,
|
||||
}
|
||||
/// Start sprinting in the given direction.
|
||||
pub fn sprint(direction: SprintDirection, physics_state: &mut PhysicsState) {
|
||||
physics_state.move_direction = WalkDirection::from(direction);
|
||||
physics_state.trying_to_sprint = true;
|
||||
pub fn sprint_listener(
|
||||
mut query: Query<&mut PhysicsState>,
|
||||
mut events: EventReader<StartSprintEvent>,
|
||||
) {
|
||||
for event in events.iter() {
|
||||
if let Ok(mut physics_state) = query.get_mut(event.entity) {
|
||||
physics_state.move_direction = WalkDirection::from(event.direction);
|
||||
physics_state.trying_to_sprint = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Change whether we're sprinting by adding an attribute modifier to the
|
||||
|
@ -375,8 +386,8 @@ pub fn sprint(direction: SprintDirection, physics_state: &mut PhysicsState) {
|
|||
/// Returns if the operation was successful.
|
||||
fn set_sprinting(
|
||||
sprinting: bool,
|
||||
currently_sprinting: &mut entity::metadata::Sprinting,
|
||||
attributes: &mut entity::Attributes,
|
||||
currently_sprinting: &mut Sprinting,
|
||||
attributes: &mut Attributes,
|
||||
) -> bool {
|
||||
**currently_sprinting = sprinting;
|
||||
if sprinting {
|
||||
|
|
|
@ -12,6 +12,9 @@ version = "0.5.0"
|
|||
azalea-block = {path = "../azalea-block", version = "^0.5.0" }
|
||||
azalea-core = {path = "../azalea-core", version = "^0.5.0" }
|
||||
azalea-world = {path = "../azalea-world", version = "^0.5.0" }
|
||||
bevy_app = { version = "0.9.1", default-features = false }
|
||||
bevy_ecs = { version = "0.9.1", default-features = false }
|
||||
iyes_loopless = "0.9.1"
|
||||
once_cell = "1.16.0"
|
||||
parking_lot = "^0.12.1"
|
||||
|
||||
|
|
|
@ -5,154 +5,195 @@ pub mod collision;
|
|||
use azalea_block::{Block, BlockState};
|
||||
use azalea_core::{BlockPos, Vec3};
|
||||
use azalea_world::{
|
||||
entity::{self},
|
||||
World,
|
||||
entity::{
|
||||
metadata::Sprinting, move_relative, Attributes, Entity, Jumping, Physics, Position,
|
||||
WorldName,
|
||||
},
|
||||
World, WorldContainer,
|
||||
};
|
||||
use bevy_app::Plugin;
|
||||
use bevy_ecs::{
|
||||
event::{EventReader, EventWriter},
|
||||
schedule::IntoSystemDescriptor,
|
||||
system::Res,
|
||||
};
|
||||
use bevy_ecs::{schedule::SystemSet, system::Query};
|
||||
use collision::{move_colliding, MoverType};
|
||||
use iyes_loopless::prelude::*;
|
||||
|
||||
pub struct PhysicsPlugin;
|
||||
impl Plugin for PhysicsPlugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
app.add_event::<ForceJumpEvent>()
|
||||
.add_fixed_timestep_system_set(
|
||||
"tick",
|
||||
0,
|
||||
SystemSet::new()
|
||||
.with_system(ai_step.label("ai_step"))
|
||||
.with_system(
|
||||
force_jump_listener
|
||||
.label("force_jump_listener")
|
||||
.after("ai_step"),
|
||||
)
|
||||
.with_system(
|
||||
travel
|
||||
.label("travel")
|
||||
.after("ai_step")
|
||||
.after("force_jump_listener"),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Move the entity with the given acceleration while handling friction,
|
||||
/// gravity, collisions, and some other stuff.
|
||||
fn travel(
|
||||
world: &World,
|
||||
physics: &mut entity::Physics,
|
||||
position: &mut entity::Position,
|
||||
attributes: &entity::Attributes,
|
||||
acceleration: &Vec3,
|
||||
mut query: Query<(&mut Physics, &mut Position, &Attributes, &WorldName)>,
|
||||
world_container: Res<WorldContainer>,
|
||||
) {
|
||||
// if !self.is_effective_ai() && !self.is_controlled_by_local_instance() {
|
||||
// // this.calculateEntityAnimation(this, this instanceof FlyingAnimal);
|
||||
// return;
|
||||
// }
|
||||
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;
|
||||
// }
|
||||
|
||||
let gravity: f64 = 0.08;
|
||||
let gravity: f64 = 0.08;
|
||||
|
||||
// TODO: slow falling effect
|
||||
// let is_falling = self.delta.y <= 0.;
|
||||
// TODO: slow falling effect
|
||||
// let is_falling = self.delta.y <= 0.;
|
||||
|
||||
// TODO: fluids
|
||||
// TODO: fluids
|
||||
|
||||
// TODO: elytra
|
||||
// TODO: elytra
|
||||
|
||||
let block_pos_below = get_block_pos_below_that_affects_movement(position);
|
||||
let block_pos_below = get_block_pos_below_that_affects_movement(&position);
|
||||
|
||||
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 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 physics.on_ground {
|
||||
block_friction * 0.91
|
||||
} else {
|
||||
0.91
|
||||
};
|
||||
|
||||
// this applies the current delta
|
||||
let mut movement = handle_relative_friction_and_calculate_movement(
|
||||
acceleration,
|
||||
block_friction,
|
||||
world,
|
||||
physics,
|
||||
position,
|
||||
attributes,
|
||||
);
|
||||
|
||||
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 {
|
||||
physics.delta = movement;
|
||||
} else {
|
||||
physics.delta = Vec3 {
|
||||
x: movement.x * inertia as f64,
|
||||
y: movement.y * 0.98f64,
|
||||
z: movement.z * inertia as f64,
|
||||
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(
|
||||
block_friction,
|
||||
&world,
|
||||
&mut physics,
|
||||
&mut position,
|
||||
attributes,
|
||||
);
|
||||
|
||||
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 {
|
||||
physics.delta = movement;
|
||||
} else {
|
||||
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.
|
||||
pub fn ai_step(
|
||||
world: &World,
|
||||
physics: &mut entity::Physics,
|
||||
position: &mut entity::Position,
|
||||
sprinting: &entity::metadata::Sprinting,
|
||||
attributes: &entity::Attributes,
|
||||
mut query: Query<
|
||||
(Entity, &mut Physics, &Jumping),
|
||||
// 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>,
|
||||
) {
|
||||
// vanilla does movement interpolation here, doesn't really matter much for a
|
||||
// bot though
|
||||
for (entity, mut physics, jumping) in &mut query {
|
||||
// vanilla does movement interpolation here, doesn't really matter much for a
|
||||
// bot though
|
||||
|
||||
if physics.delta.x.abs() < 0.003 {
|
||||
physics.delta.x = 0.;
|
||||
}
|
||||
if physics.delta.y.abs() < 0.003 {
|
||||
physics.delta.y = 0.;
|
||||
}
|
||||
if physics.delta.z.abs() < 0.003 {
|
||||
physics.delta.z = 0.;
|
||||
}
|
||||
if physics.delta.x.abs() < 0.003 {
|
||||
physics.delta.x = 0.;
|
||||
}
|
||||
if physics.delta.y.abs() < 0.003 {
|
||||
physics.delta.y = 0.;
|
||||
}
|
||||
if physics.delta.z.abs() < 0.003 {
|
||||
physics.delta.z = 0.;
|
||||
}
|
||||
|
||||
if physics.jumping {
|
||||
// TODO: jumping in liquids and jump delay
|
||||
if **jumping {
|
||||
// TODO: jumping in liquids and jump delay
|
||||
|
||||
if physics.on_ground {
|
||||
jump_from_ground(world, physics, position, sprinting);
|
||||
if physics.on_ground {
|
||||
force_jump_events.send(ForceJumpEvent(entity));
|
||||
}
|
||||
}
|
||||
|
||||
physics.xxa *= 0.98;
|
||||
physics.zza *= 0.98;
|
||||
|
||||
// TODO: freezing, pushEntities, drowning damage (in their own systems,
|
||||
// after `travel`)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
};
|
||||
}
|
||||
|
||||
physics.has_impulse = true;
|
||||
}
|
||||
}
|
||||
|
||||
physics.xxa *= 0.98;
|
||||
physics.zza *= 0.98;
|
||||
|
||||
travel(
|
||||
world,
|
||||
physics,
|
||||
position,
|
||||
attributes,
|
||||
&Vec3 {
|
||||
x: physics.xxa as f64,
|
||||
y: physics.yya as f64,
|
||||
z: physics.zza as f64,
|
||||
},
|
||||
);
|
||||
// freezing
|
||||
// pushEntities
|
||||
// drowning damage
|
||||
}
|
||||
|
||||
fn jump_from_ground(
|
||||
world: &World,
|
||||
physics: &mut entity::Physics,
|
||||
position: &entity::Position,
|
||||
sprinting: &entity::metadata::Sprinting,
|
||||
) {
|
||||
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 {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
physics.has_impulse = true;
|
||||
}
|
||||
|
||||
fn get_block_pos_below_that_affects_movement(position: &entity::Position) -> BlockPos {
|
||||
fn get_block_pos_below_that_affects_movement(position: &Position) -> BlockPos {
|
||||
BlockPos::new(
|
||||
position.x.floor() as i32,
|
||||
// TODO: this uses bounding_box.min_y instead of position.y
|
||||
|
@ -162,17 +203,20 @@ fn get_block_pos_below_that_affects_movement(position: &entity::Position) -> Blo
|
|||
}
|
||||
|
||||
fn handle_relative_friction_and_calculate_movement(
|
||||
acceleration: &Vec3,
|
||||
block_friction: f32,
|
||||
world: &World,
|
||||
physics: &mut entity::Physics,
|
||||
position: &mut entity::Position,
|
||||
attributes: &entity::Attributes,
|
||||
physics: &mut Physics,
|
||||
position: &mut Position,
|
||||
attributes: &Attributes,
|
||||
) -> Vec3 {
|
||||
entity::move_relative(
|
||||
move_relative(
|
||||
physics,
|
||||
get_friction_influenced_speed(physics, attributes, block_friction),
|
||||
acceleration,
|
||||
&Vec3 {
|
||||
x: physics.xxa as f64,
|
||||
y: physics.yya as f64,
|
||||
z: physics.zza as f64,
|
||||
},
|
||||
);
|
||||
// entity.delta = entity.handle_on_climbable(entity.delta);
|
||||
move_colliding(
|
||||
|
@ -197,11 +241,7 @@ fn handle_relative_friction_and_calculate_movement(
|
|||
// private float getFrictionInfluencedSpeed(float friction) {
|
||||
// return this.onGround ? this.getSpeed() * (0.21600002F / (friction *
|
||||
// friction * friction)) : this.flyingSpeed; }
|
||||
fn get_friction_influenced_speed(
|
||||
physics: &entity::Physics,
|
||||
attributes: &entity::Attributes,
|
||||
friction: f32,
|
||||
) -> f32 {
|
||||
fn get_friction_influenced_speed(physics: &Physics, attributes: &Attributes, friction: f32) -> f32 {
|
||||
// TODO: have speed & flying_speed fields in entity
|
||||
if physics.on_ground {
|
||||
let speed: f32 = attributes.speed.calculate() as f32;
|
||||
|
@ -214,7 +254,7 @@ fn get_friction_influenced_speed(
|
|||
|
||||
/// Returns the what the entity's jump should be multiplied by based on the
|
||||
/// block they're standing on.
|
||||
fn block_jump_factor(world: &World, position: &entity::Position) -> f32 {
|
||||
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
|
||||
|
@ -242,7 +282,7 @@ fn block_jump_factor(world: &World, position: &entity::Position) -> f32 {
|
|||
// public double getJumpBoostPower() {
|
||||
// return this.hasEffect(MobEffects.JUMP) ? (double)(0.1F *
|
||||
// (float)(this.getEffect(MobEffects.JUMP).getAmplifier() + 1)) : 0.0D; }
|
||||
fn jump_power(world: &World, position: &entity::Position) -> f32 {
|
||||
fn jump_power(world: &World, position: &Position) -> f32 {
|
||||
0.42 * block_jump_factor(world, position)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ bevy_app = { version = "0.9.1", default-features = false }
|
|||
bevy_ecs = {version = "0.9.1", default-features = false}
|
||||
derive_more = {version = "0.99.17", features = ["deref", "deref_mut"]}
|
||||
enum-as-inner = "0.5.1"
|
||||
iyes_loopless = "0.9.1"
|
||||
log = "0.4.17"
|
||||
nohash-hasher = "0.2.0"
|
||||
once_cell = "1.16.0"
|
||||
|
|
|
@ -177,6 +177,13 @@ impl From<&LastSentPosition> for BlockPos {
|
|||
#[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)]
|
||||
pub struct WorldName(ResourceLocation);
|
||||
|
||||
/// A component for entities that can jump.
|
||||
///
|
||||
/// If this is true, the entity will try to jump every tick. (It's equivalent to
|
||||
/// the space key being held in vanilla.)
|
||||
#[derive(Debug, Component, Deref, DerefMut)]
|
||||
pub struct Jumping(bool);
|
||||
|
||||
/// The physics data relating to the entity, such as position, velocity, and
|
||||
/// bounding box.
|
||||
#[derive(Debug, Component)]
|
||||
|
@ -205,10 +212,6 @@ pub struct Physics {
|
|||
/// unlike dimensions.
|
||||
pub bounding_box: AABB,
|
||||
|
||||
/// Whether the entity will try to jump every tick
|
||||
/// (equivalent to the space key being held down in vanilla).
|
||||
pub jumping: bool,
|
||||
|
||||
pub has_impulse: bool,
|
||||
}
|
||||
|
||||
|
@ -251,6 +254,7 @@ pub struct EntityBundle {
|
|||
pub last_sent_position: LastSentPosition,
|
||||
pub physics: Physics,
|
||||
pub attributes: Attributes,
|
||||
pub jumping: Jumping,
|
||||
}
|
||||
|
||||
impl EntityBundle {
|
||||
|
@ -293,8 +297,6 @@ impl EntityBundle {
|
|||
dimensions,
|
||||
|
||||
has_impulse: false,
|
||||
|
||||
jumping: false,
|
||||
},
|
||||
|
||||
attributes: Attributes {
|
||||
|
@ -302,6 +304,8 @@ impl EntityBundle {
|
|||
// entities have different defaults
|
||||
speed: AttributeInstance::new(0.1),
|
||||
},
|
||||
|
||||
jumping: Jumping(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ use uuid::Uuid;
|
|||
pub struct EntityPlugin;
|
||||
impl Plugin for EntityPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
// Since it's PostUpdate, these will run after every tick or packet
|
||||
app.add_system_set(
|
||||
SystemSet::new()
|
||||
.after("tick")
|
||||
|
|
|
@ -31,6 +31,9 @@ priority-queue = "1.3.0"
|
|||
thiserror = "^1.0.37"
|
||||
tokio = "^1.21.2"
|
||||
uuid = "1.2.2"
|
||||
bevy_app = { version = "0.9.1", default-features = false }
|
||||
bevy_ecs = { version = "0.9.1", default-features = false }
|
||||
iyes_loopless = "0.9.1"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "^1.0.65"
|
||||
|
|
|
@ -1,56 +1,97 @@
|
|||
use crate::{Client, Event};
|
||||
use async_trait::async_trait;
|
||||
use azalea_core::Vec3;
|
||||
use parking_lot::Mutex;
|
||||
use std::{f64::consts::PI, sync::Arc};
|
||||
use azalea_world::entity::{set_rotation, Entity, Jumping, Physics, Position};
|
||||
use bevy_app::App;
|
||||
use bevy_ecs::schedule::IntoSystemDescriptor;
|
||||
use bevy_ecs::{event::EventReader, prelude::Component, schedule::SystemSet, system::Query};
|
||||
use iyes_loopless::prelude::*;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Plugin;
|
||||
impl crate::Plugin for Plugin {
|
||||
type State = State;
|
||||
|
||||
fn build(&self) -> State {
|
||||
State::default()
|
||||
impl bevy_app::Plugin for Plugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_event::<LookAtEvent>()
|
||||
.add_event::<JumpEvent>()
|
||||
.add_system(look_at_listener)
|
||||
.add_system(jump_listener.label("jump_listener"))
|
||||
.add_fixed_timestep_system_set(
|
||||
"tick",
|
||||
0,
|
||||
// make sure tick_jump happens the tick after a jump event
|
||||
SystemSet::new().with_system(tick_jump.before("jump_listener")),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct State {
|
||||
jumping_once: Arc<Mutex<bool>>,
|
||||
/// Component for all bots.
|
||||
#[derive(Default, Component)]
|
||||
pub struct Bot {
|
||||
jumping_once: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::PluginState for State {
|
||||
async fn handle(self: Box<Self>, event: Event, mut bot: Client) {
|
||||
if let Event::Tick = event {
|
||||
if *self.jumping_once.lock() && bot.jumping() {
|
||||
*self.jumping_once.lock() = false;
|
||||
bot.set_jumping(false);
|
||||
}
|
||||
fn tick_jump(mut query: Query<(&mut Jumping, &mut Bot)>) {
|
||||
for (mut jumping, mut bot) in &mut query {
|
||||
if bot.jumping_once && **jumping {
|
||||
bot.jumping_once = false;
|
||||
**jumping = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BotTrait {
|
||||
pub trait BotClientExt {
|
||||
fn jump(&mut self);
|
||||
fn look_at(&mut self, pos: &Vec3);
|
||||
fn look_at(&mut self, pos: Vec3);
|
||||
}
|
||||
|
||||
impl BotTrait for azalea_client::Client {
|
||||
impl BotClientExt for azalea_client::Client {
|
||||
/// Queue a jump for the next tick.
|
||||
fn jump(&mut self) {
|
||||
self.set_jumping(true);
|
||||
let state = self.plugins.get::<State>().unwrap().clone();
|
||||
*state.jumping_once.lock() = true;
|
||||
let mut ecs = self.ecs.lock();
|
||||
ecs.send_event(JumpEvent(self.entity));
|
||||
}
|
||||
|
||||
/// Turn the bot's head to look at the coordinate in the world.
|
||||
fn look_at(&mut self, pos: &Vec3) {
|
||||
let (y_rot, x_rot) = direction_looking_at(self.entity().pos(), pos);
|
||||
self.set_rotation(y_rot, x_rot);
|
||||
fn look_at(&mut self, position: Vec3) {
|
||||
let mut ecs = self.ecs.lock();
|
||||
ecs.send_event(LookAtEvent {
|
||||
entity: self.entity,
|
||||
position,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Event to jump once.
|
||||
pub struct JumpEvent(pub Entity);
|
||||
|
||||
fn jump_listener(mut query: Query<(&mut Jumping, &mut Bot)>, mut events: EventReader<JumpEvent>) {
|
||||
for event in events.iter() {
|
||||
if let Ok((mut jumping, mut bot)) = query.get_mut(event.0) {
|
||||
**jumping = true;
|
||||
bot.jumping_once = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Make an entity look towards a certain position in the world.
|
||||
pub struct LookAtEvent {
|
||||
pub entity: Entity,
|
||||
/// The position we want the entity to be looking at.
|
||||
pub position: Vec3,
|
||||
}
|
||||
fn look_at_listener(
|
||||
mut events: EventReader<LookAtEvent>,
|
||||
mut query: Query<(&Position, &mut Physics)>,
|
||||
) {
|
||||
for event in events.iter() {
|
||||
if let Ok((position, mut physics)) = query.get_mut(event.entity) {
|
||||
let (y_rot, x_rot) = direction_looking_at(&position, &event.position);
|
||||
set_rotation(&mut physics, y_rot, x_rot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the (y_rot, x_rot) that would make a client at `current` be looking
|
||||
/// at `target`.
|
||||
fn direction_looking_at(current: &Vec3, target: &Vec3) -> (f32, f32) {
|
||||
// borrowed from mineflayer's Bot.lookAt because i didn't want to do math
|
||||
let delta = target - current;
|
||||
|
|
|
@ -89,6 +89,7 @@ pub use azalea_block::*;
|
|||
pub use azalea_client::*;
|
||||
pub use azalea_core::{BlockPos, Vec3};
|
||||
pub use azalea_registry::EntityKind;
|
||||
pub use azalea_world::{entity, World};
|
||||
pub use start::{start, Options};
|
||||
pub use swarm::*;
|
||||
|
||||
|
|
|
@ -1,57 +1,73 @@
|
|||
mod moves;
|
||||
mod mtdstarlite;
|
||||
|
||||
use crate::{prelude::*, SprintDirection, WalkDirection};
|
||||
use crate::{Client, Event};
|
||||
use async_trait::async_trait;
|
||||
use crate::bot::{JumpEvent, LookAtEvent};
|
||||
use crate::{SprintDirection, WalkDirection};
|
||||
|
||||
use azalea_client::{LocalPlayer, StartSprintEvent, StartWalkEvent};
|
||||
use azalea_core::{BlockPos, CardinalDirection};
|
||||
use azalea_world::entity::{Entity, Physics, Position, WorldName};
|
||||
use azalea_world::WorldContainer;
|
||||
use bevy_ecs::event::EventReader;
|
||||
use bevy_ecs::schedule::SystemSet;
|
||||
use bevy_ecs::system::{Query, Res};
|
||||
use bevy_ecs::{component::Component, event::EventWriter};
|
||||
use iyes_loopless::prelude::*;
|
||||
use log::{debug, error};
|
||||
use mtdstarlite::Edge;
|
||||
pub use mtdstarlite::MTDStarLite;
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Plugin;
|
||||
impl crate::Plugin for Plugin {
|
||||
type State = State;
|
||||
|
||||
fn build(&self) -> State {
|
||||
State::default()
|
||||
impl bevy_app::Plugin for Plugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
app.add_fixed_timestep_system_set(
|
||||
"tick",
|
||||
0,
|
||||
SystemSet::new()
|
||||
.with_system(tick_execute_path)
|
||||
.with_system(goto_listener),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct State {
|
||||
// pathfinder: Option<MTDStarLite<Node, f32>>,
|
||||
pub path: Arc<Mutex<VecDeque<Node>>>,
|
||||
/// A component that makes this entity able to pathfind.
|
||||
#[derive(Component)]
|
||||
pub struct Pathfinder {
|
||||
pub path: VecDeque<Node>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::PluginState for State {
|
||||
async fn handle(self: Box<Self>, event: Event, mut bot: Client) {
|
||||
if let Event::Tick = event {
|
||||
let mut path = self.path.lock();
|
||||
pub trait PathfinderClientExt {
|
||||
fn goto(&self, goal: impl Goal + Send + Sync + 'static);
|
||||
}
|
||||
|
||||
if !path.is_empty() {
|
||||
tick_execute_path(&mut bot, &mut path);
|
||||
}
|
||||
}
|
||||
impl PathfinderClientExt for azalea_client::Client {
|
||||
fn goto(&self, goal: impl Goal + Send + Sync + 'static) {
|
||||
self.ecs.lock().send_event(GotoEvent {
|
||||
entity: self.entity,
|
||||
goal: Box::new(goal),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Trait {
|
||||
fn goto(&self, goal: impl Goal);
|
||||
pub struct GotoEvent {
|
||||
pub entity: Entity,
|
||||
pub goal: Box<dyn Goal + Send + Sync>,
|
||||
}
|
||||
|
||||
impl Trait for azalea_client::Client {
|
||||
fn goto(&self, goal: impl Goal) {
|
||||
fn goto_listener(
|
||||
mut events: EventReader<GotoEvent>,
|
||||
mut query: Query<(&Position, &WorldName, &mut Pathfinder)>,
|
||||
world_container: Res<WorldContainer>,
|
||||
) {
|
||||
for event in events.iter() {
|
||||
let (position, world_name, mut pathfinder) = query
|
||||
.get_mut(event.entity)
|
||||
.expect("Called goto on an entity that can't pathfind");
|
||||
let start = Node {
|
||||
pos: BlockPos::from(self.entity().pos()),
|
||||
pos: BlockPos::from(position),
|
||||
vertical_vel: VerticalVel::None,
|
||||
};
|
||||
let end = goal.goal_node();
|
||||
let end = event.goal.goal_node();
|
||||
debug!("start: {start:?}, end: {end:?}");
|
||||
|
||||
let possible_moves: Vec<&dyn moves::Move> = vec![
|
||||
|
@ -79,7 +95,10 @@ impl Trait for azalea_client::Client {
|
|||
let successors = |node: &Node| {
|
||||
let mut edges = Vec::new();
|
||||
|
||||
let world = &self.world.read().shared;
|
||||
let world_lock = world_container
|
||||
.get(world_name)
|
||||
.expect("Entity tried to pathfind but the entity isn't in a valid world");
|
||||
let world = world_lock.read();
|
||||
for possible_move in possible_moves.iter() {
|
||||
edges.push(Edge {
|
||||
target: possible_move.next_node(node),
|
||||
|
@ -92,10 +111,10 @@ impl Trait for azalea_client::Client {
|
|||
let mut pf = MTDStarLite::new(
|
||||
start,
|
||||
end,
|
||||
|n| goal.heuristic(n),
|
||||
|n| event.goal.heuristic(n),
|
||||
successors,
|
||||
successors,
|
||||
|n| goal.success(n),
|
||||
|n| event.goal.success(n),
|
||||
);
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
|
@ -104,43 +123,58 @@ impl Trait for azalea_client::Client {
|
|||
debug!("path: {p:?}");
|
||||
debug!("time: {:?}", end - start);
|
||||
|
||||
let state = self
|
||||
.plugins
|
||||
.get::<State>()
|
||||
.expect("Pathfinder plugin not installed!")
|
||||
.clone();
|
||||
// convert the Option<Vec<Node>> to a VecDeque<Node>
|
||||
if let Some(p) = p {
|
||||
*state.path.lock() = p.into_iter().collect();
|
||||
pathfinder.path = p.into_iter().collect();
|
||||
} else {
|
||||
error!("no path found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tick_execute_path(bot: &mut Client, path: &mut VecDeque<Node>) {
|
||||
let target = if let Some(target) = path.front() {
|
||||
target
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
let center = target.pos.center();
|
||||
// println!("going to {center:?} (at {pos:?})", pos = bot.entity().pos());
|
||||
bot.look_at(¢er);
|
||||
bot.sprint(SprintDirection::Forward);
|
||||
// check if we should jump
|
||||
if target.pos.y > bot.entity().pos().y.floor() as i32 {
|
||||
bot.jump();
|
||||
}
|
||||
fn tick_execute_path(
|
||||
mut query: Query<(Entity, &LocalPlayer, &mut Pathfinder, &Position, &Physics)>,
|
||||
mut look_at_events: EventWriter<LookAtEvent>,
|
||||
mut sprint_events: EventWriter<StartSprintEvent>,
|
||||
mut walk_events: EventWriter<StartWalkEvent>,
|
||||
mut jump_events: EventWriter<JumpEvent>,
|
||||
) {
|
||||
for (entity, local_player, mut pathfinder, position, physics) in &mut query {
|
||||
loop {
|
||||
let target = if let Some(target) = pathfinder.path.front() {
|
||||
target
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
let center = target.pos.center();
|
||||
// println!("going to {center:?} (at {pos:?})", pos = bot.entity().pos());
|
||||
look_at_events.send(LookAtEvent {
|
||||
entity,
|
||||
position: center,
|
||||
});
|
||||
sprint_events.send(StartSprintEvent {
|
||||
entity,
|
||||
direction: SprintDirection::Forward,
|
||||
});
|
||||
// check if we should jump
|
||||
if target.pos.y > position.y.floor() as i32 {
|
||||
jump_events.send(JumpEvent(entity));
|
||||
}
|
||||
|
||||
if target.is_reached(&bot.entity()) {
|
||||
// println!("ok target {target:?} reached");
|
||||
path.pop_front();
|
||||
if path.is_empty() {
|
||||
bot.walk(WalkDirection::None);
|
||||
if target.is_reached(position, physics) {
|
||||
// println!("ok target {target:?} reached");
|
||||
pathfinder.path.pop_front();
|
||||
if pathfinder.path.is_empty() {
|
||||
walk_events.send(StartWalkEvent {
|
||||
entity,
|
||||
direction: WalkDirection::None,
|
||||
});
|
||||
}
|
||||
// tick again, maybe we already reached the next node!
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// tick again, maybe we already reached the next node!
|
||||
tick_execute_path(bot, path);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,7 +205,7 @@ pub trait Goal {
|
|||
impl Node {
|
||||
/// Returns whether the entity is at the node and should start going to the
|
||||
/// next node.
|
||||
pub fn is_reached(&self, entity: &EntityData) -> bool {
|
||||
pub fn is_reached(&self, position: &Position, physics: &Physics) -> bool {
|
||||
// println!(
|
||||
// "entity.delta.y: {} {:?}=={:?}, self.vertical_vel={:?}",
|
||||
// entity.delta.y,
|
||||
|
@ -179,11 +213,11 @@ impl Node {
|
|||
// self.pos,
|
||||
// self.vertical_vel
|
||||
// );
|
||||
BlockPos::from(entity.pos()) == self.pos
|
||||
BlockPos::from(position) == self.pos
|
||||
&& match self.vertical_vel {
|
||||
VerticalVel::NoneMidair => entity.delta.y > -0.1 && entity.delta.y < 0.1,
|
||||
VerticalVel::None => entity.on_ground,
|
||||
VerticalVel::FallingLittle => entity.delta.y < -0.1,
|
||||
VerticalVel::NoneMidair => physics.delta.y > -0.1 && physics.delta.y < 0.1,
|
||||
VerticalVel::None => physics.on_ground,
|
||||
VerticalVel::FallingLittle => physics.delta.y < -0.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! The Azalea prelude. Things that are necessary for a bare-bones bot are
|
||||
//! re-exported here.
|
||||
|
||||
pub use crate::bot::BotTrait;
|
||||
pub use crate::pathfinder::Trait;
|
||||
pub use crate::bot::BotClientExt;
|
||||
pub use crate::pathfinder::PathfinderClientExt;
|
||||
pub use crate::{plugins, swarm_plugins, Plugin};
|
||||
pub use azalea_client::{Account, Client, Event};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{bot, pathfinder, HandleFn};
|
||||
use crate::HandleFn;
|
||||
use azalea_client::{Account, Client, Plugins};
|
||||
use azalea_protocol::ServerAddress;
|
||||
use std::{future::Future, sync::Arc};
|
||||
use std::future::Future;
|
||||
use thiserror::Error;
|
||||
|
||||
/// A helper macro that generates a [`Plugins`] struct from a list of objects
|
||||
|
@ -112,14 +112,9 @@ pub async fn start<
|
|||
Err(_) => return Err(StartError::InvalidAddress),
|
||||
};
|
||||
|
||||
let (mut bot, mut rx) = Client::join(&options.account, address).await?;
|
||||
let (bot, mut rx) = Client::join(&options.account, address).await?;
|
||||
|
||||
let mut plugins = options.plugins;
|
||||
// DEFAULT PLUGINS
|
||||
plugins.add(bot::Plugin);
|
||||
plugins.add(pathfinder::Plugin);
|
||||
|
||||
bot.plugins = Arc::new(plugins.build());
|
||||
let plugins = options.plugins;
|
||||
|
||||
let state = options.state;
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@ mod chat;
|
|||
mod plugins;
|
||||
|
||||
pub use self::plugins::*;
|
||||
use crate::{bot, HandleFn};
|
||||
use azalea_client::{Account, ChatPacket, Client, Event, JoinError, Plugins};
|
||||
use crate::HandleFn;
|
||||
use azalea_client::{start_ecs, Account, ChatPacket, Client, Event, JoinError, Plugins};
|
||||
use azalea_protocol::{
|
||||
connect::{Connection, ConnectionError},
|
||||
connect::ConnectionError,
|
||||
resolver::{self, ResolverError},
|
||||
ServerAddress,
|
||||
};
|
||||
|
@ -53,11 +53,14 @@ pub struct Swarm<S> {
|
|||
resolved_address: SocketAddr,
|
||||
address: ServerAddress,
|
||||
pub worlds: Arc<RwLock<WorldContainer>>,
|
||||
pub ecs_lock: Arc<Mutex<bevy_ecs::world::World>>,
|
||||
/// Plugins that are set for new bots
|
||||
plugins: Plugins,
|
||||
|
||||
bots_tx: UnboundedSender<(Option<Event>, (Client, S))>,
|
||||
swarm_tx: UnboundedSender<SwarmEvent>,
|
||||
|
||||
run_schedule_sender: UnboundedSender<()>,
|
||||
}
|
||||
|
||||
/// An event about something that doesn't have to do with a single bot.
|
||||
|
@ -230,18 +233,15 @@ pub async fn start_swarm<
|
|||
|
||||
let world_container = Arc::new(RwLock::new(WorldContainer::default()));
|
||||
|
||||
let mut plugins = options.plugins;
|
||||
let swarm_plugins = options.swarm_plugins;
|
||||
|
||||
// DEFAULT CLIENT PLUGINS
|
||||
plugins.add(bot::Plugin);
|
||||
plugins.add(crate::pathfinder::Plugin);
|
||||
// DEFAULT SWARM PLUGINS
|
||||
let plugins = options.plugins;
|
||||
|
||||
// we can't modify the swarm plugins after this
|
||||
let (bots_tx, mut bots_rx) = mpsc::unbounded_channel();
|
||||
let (swarm_tx, mut swarm_rx) = mpsc::unbounded_channel();
|
||||
|
||||
let (run_schedule_sender, run_schedule_receiver) = mpsc::unbounded_channel();
|
||||
let ecs_lock = start_ecs(run_schedule_receiver, run_schedule_sender.clone());
|
||||
|
||||
let mut swarm = Swarm {
|
||||
bot_datas: Arc::new(Mutex::new(Vec::new())),
|
||||
|
||||
|
@ -253,6 +253,9 @@ pub async fn start_swarm<
|
|||
bots_tx,
|
||||
|
||||
swarm_tx: swarm_tx.clone(),
|
||||
|
||||
ecs_lock,
|
||||
run_schedule_sender,
|
||||
};
|
||||
|
||||
{
|
||||
|
@ -264,8 +267,6 @@ pub async fn start_swarm<
|
|||
});
|
||||
}
|
||||
|
||||
let swarm_plugins = swarm_plugins.build();
|
||||
|
||||
let mut swarm_clone = swarm.clone();
|
||||
let join_task = tokio::spawn(async move {
|
||||
if let Some(join_delay) = options.join_delay {
|
||||
|
@ -294,14 +295,10 @@ pub async fn start_swarm<
|
|||
let swarm_state = options.swarm_state;
|
||||
let mut internal_state = InternalSwarmState::default();
|
||||
|
||||
// Watch swarm_rx and send those events to the plugins and swarm_handle.
|
||||
// Watch swarm_rx and send those events to the swarm_handle.
|
||||
let swarm_clone = swarm.clone();
|
||||
let swarm_plugins_clone = swarm_plugins.clone();
|
||||
tokio::spawn(async move {
|
||||
while let Some(event) = swarm_rx.recv().await {
|
||||
for plugin in swarm_plugins_clone.clone().into_iter() {
|
||||
tokio::spawn(plugin.handle(event.clone(), swarm_clone.clone()));
|
||||
}
|
||||
tokio::spawn((options.swarm_handle)(
|
||||
swarm_clone.clone(),
|
||||
event,
|
||||
|
@ -312,14 +309,8 @@ pub async fn start_swarm<
|
|||
|
||||
// bot events
|
||||
while let Some((Some(event), (bot, state))) = bots_rx.recv().await {
|
||||
// bot event handling
|
||||
let cloned_plugins = (*bot.plugins).clone();
|
||||
for plugin in cloned_plugins.into_iter() {
|
||||
tokio::spawn(plugin.handle(event.clone(), bot.clone()));
|
||||
}
|
||||
|
||||
// swarm event handling
|
||||
// remove this #[allow] when more checks are added
|
||||
// TODO: actually it'd be better to just have this in a system
|
||||
#[allow(clippy::single_match)]
|
||||
match &event {
|
||||
Event::Login => {
|
||||
|
@ -330,7 +321,6 @@ pub async fn start_swarm<
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
tokio::spawn((options.handle)(bot, event, state));
|
||||
}
|
||||
|
||||
|
@ -346,17 +336,19 @@ where
|
|||
/// Add a new account to the swarm. You can remove it later by calling
|
||||
/// [`Client::disconnect`].
|
||||
pub async fn add(&mut self, account: &Account, state: S) -> Result<Client, JoinError> {
|
||||
let conn = Connection::new(&self.resolved_address).await?;
|
||||
let (conn, game_profile) = Client::handshake(conn, account, &self.address.clone()).await?;
|
||||
|
||||
// tx is moved to the bot so it can send us events
|
||||
// rx is used to receive events from the bot
|
||||
let (tx, mut rx) = mpsc::channel(1);
|
||||
let mut bot = Client::new(game_profile, conn, Some(self.worlds.clone()));
|
||||
tx.send(Event::Init).await.expect("Failed to send event");
|
||||
bot.start_tasks(tx);
|
||||
|
||||
bot.plugins = Arc::new(self.plugins.clone().build());
|
||||
// An event that causes the schedule to run. This is only used internally.
|
||||
// let (run_schedule_sender, run_schedule_receiver) = mpsc::unbounded_channel();
|
||||
// let ecs_lock = start_ecs(run_schedule_receiver, run_schedule_sender.clone());
|
||||
let (bot, mut rx) = Client::start_client(
|
||||
self.ecs_lock.clone(),
|
||||
account,
|
||||
&self.address,
|
||||
&self.resolved_address,
|
||||
self.run_schedule_sender.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let cloned_bots_tx = self.bots_tx.clone();
|
||||
let cloned_bot = bot.clone();
|
||||
|
@ -432,7 +424,7 @@ where
|
|||
/// }
|
||||
/// ```
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.bot_datas.clone().lock().into_iter()
|
||||
self.bot_datas.lock().clone().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use azalea::entity::metadata::Player;
|
||||
use azalea::pathfinder::BlockPosGoal;
|
||||
// use azalea::ClientInformation;
|
||||
use azalea::{prelude::*, BlockPos, Swarm, SwarmEvent, WalkDirection};
|
||||
|
@ -79,20 +80,24 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
|
|||
// .await?;
|
||||
}
|
||||
Event::Login => {
|
||||
bot.chat("Hello world").await?;
|
||||
bot.chat("Hello world");
|
||||
}
|
||||
Event::Chat(m) => {
|
||||
if m.content() == bot.profile.name {
|
||||
bot.chat("Bye").await?;
|
||||
bot.chat("Bye");
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
bot.disconnect().await?;
|
||||
bot.disconnect();
|
||||
}
|
||||
let Some(sender) = m.username() else {
|
||||
return Ok(())
|
||||
};
|
||||
let mut ecs = bot.ecs.lock();
|
||||
let entity = bot
|
||||
.world()
|
||||
.entity_by(|e| e.kind() == azalea::EntityKind::Player && e.name() == Some(sender));
|
||||
.ecs
|
||||
.lock()
|
||||
.query::<&Player>()
|
||||
.iter(&mut ecs)
|
||||
.find(|e| e.name() == Some(sender));
|
||||
// let entity = None;
|
||||
if let Some(entity) = entity {
|
||||
if m.content() == "goto" {
|
||||
|
@ -119,7 +124,7 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
|
|||
Event::Death(_) => {
|
||||
bot.write_packet(ServerboundClientCommandPacket {
|
||||
action: azalea_protocol::packets::game::serverbound_client_command_packet::Action::PerformRespawn,
|
||||
}.get()).await?;
|
||||
}.get());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -144,7 +149,7 @@ async fn swarm_handle(
|
|||
for (name, world) in &swarm.worlds.read().worlds {
|
||||
println!("world name: {}", name);
|
||||
if let Some(w) = world.upgrade() {
|
||||
for chunk_pos in w.chunks.read().chunks.values() {
|
||||
for chunk_pos in w.read().chunks.chunks.values() {
|
||||
println!("chunk: {:?}", chunk_pos);
|
||||
}
|
||||
} else {
|
||||
|
@ -154,7 +159,7 @@ async fn swarm_handle(
|
|||
}
|
||||
if m.message().to_string() == "<py5> hi" {
|
||||
for (bot, _) in swarm {
|
||||
bot.chat("hello").await?;
|
||||
bot.chat("hello");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue