mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
Attacking (#96)
* add Client::attack * partially implement attack cooldowns * attack speed modifiers * don't care clippy --------- Co-authored-by: mat <git@matdoes.dev>
This commit is contained in:
parent
148f203817
commit
cde7e35046
15 changed files with 359 additions and 65 deletions
138
azalea-client/src/attack.rs
Normal file
138
azalea-client/src/attack.rs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
use azalea_core::GameMode;
|
||||||
|
use azalea_entity::{
|
||||||
|
metadata::{ShiftKeyDown, Sprinting},
|
||||||
|
Attributes, Physics,
|
||||||
|
};
|
||||||
|
use azalea_protocol::packets::game::serverbound_interact_packet::{
|
||||||
|
self, ServerboundInteractPacket,
|
||||||
|
};
|
||||||
|
use azalea_world::MinecraftEntityId;
|
||||||
|
use bevy_app::{App, FixedUpdate, Plugin, Update};
|
||||||
|
use bevy_ecs::prelude::*;
|
||||||
|
use derive_more::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
interact::SwingArmEvent,
|
||||||
|
local_player::{LocalGameMode, SendPacketEvent},
|
||||||
|
Client,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct AttackPlugin;
|
||||||
|
impl Plugin for AttackPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_event::<AttackEvent>()
|
||||||
|
.add_systems(Update, handle_attack_event)
|
||||||
|
.add_systems(
|
||||||
|
FixedUpdate,
|
||||||
|
(
|
||||||
|
increment_ticks_since_last_attack,
|
||||||
|
update_attack_strength_scale,
|
||||||
|
)
|
||||||
|
.chain(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
/// Attack the entity with the given id.
|
||||||
|
pub fn attack(&mut self, entity_id: MinecraftEntityId) {
|
||||||
|
self.ecs.lock().send_event(AttackEvent {
|
||||||
|
entity: self.entity,
|
||||||
|
target: entity_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the player has an attack cooldown.
|
||||||
|
pub fn has_attack_cooldown(&self) -> bool {
|
||||||
|
let ticks_since_last_attack = *self.component::<AttackStrengthScale>();
|
||||||
|
ticks_since_last_attack < 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct AttackEvent {
|
||||||
|
pub entity: Entity,
|
||||||
|
pub target: MinecraftEntityId,
|
||||||
|
}
|
||||||
|
pub fn handle_attack_event(
|
||||||
|
mut events: EventReader<AttackEvent>,
|
||||||
|
mut query: Query<(
|
||||||
|
&LocalGameMode,
|
||||||
|
&mut TicksSinceLastAttack,
|
||||||
|
&mut Physics,
|
||||||
|
&mut Sprinting,
|
||||||
|
&mut ShiftKeyDown,
|
||||||
|
)>,
|
||||||
|
mut send_packet_events: EventWriter<SendPacketEvent>,
|
||||||
|
mut swing_arm_event: EventWriter<SwingArmEvent>,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
let (game_mode, mut ticks_since_last_attack, mut physics, mut sprinting, sneaking) =
|
||||||
|
query.get_mut(event.entity).unwrap();
|
||||||
|
|
||||||
|
swing_arm_event.send(SwingArmEvent {
|
||||||
|
entity: event.entity,
|
||||||
|
});
|
||||||
|
send_packet_events.send(SendPacketEvent {
|
||||||
|
entity: event.entity,
|
||||||
|
packet: ServerboundInteractPacket {
|
||||||
|
entity_id: *event.target,
|
||||||
|
action: serverbound_interact_packet::ActionType::Attack,
|
||||||
|
using_secondary_action: **sneaking,
|
||||||
|
}
|
||||||
|
.get(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// we can't attack if we're in spectator mode but it still sends the attack
|
||||||
|
// packet
|
||||||
|
if game_mode.current == GameMode::Spectator {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
ticks_since_last_attack.0 = 0;
|
||||||
|
|
||||||
|
physics.delta = physics.delta.multiply(0.6, 1.0, 0.6);
|
||||||
|
**sprinting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Bundle)]
|
||||||
|
pub struct AttackBundle {
|
||||||
|
pub ticks_since_last_attack: TicksSinceLastAttack,
|
||||||
|
pub attack_strength_scale: AttackStrengthScale,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Component, Clone, Deref, DerefMut)]
|
||||||
|
pub struct TicksSinceLastAttack(pub u32);
|
||||||
|
pub fn increment_ticks_since_last_attack(mut query: Query<&mut TicksSinceLastAttack>) {
|
||||||
|
for mut ticks_since_last_attack in query.iter_mut() {
|
||||||
|
**ticks_since_last_attack += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Component, Clone, Deref, DerefMut)]
|
||||||
|
pub struct AttackStrengthScale(pub f32);
|
||||||
|
pub fn update_attack_strength_scale(
|
||||||
|
mut query: Query<(&TicksSinceLastAttack, &Attributes, &mut AttackStrengthScale)>,
|
||||||
|
) {
|
||||||
|
for (ticks_since_last_attack, attributes, mut attack_strength_scale) in query.iter_mut() {
|
||||||
|
// look 0.5 ticks into the future because that's what vanilla does
|
||||||
|
**attack_strength_scale =
|
||||||
|
get_attack_strength_scale(ticks_since_last_attack.0, attributes, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns how long it takes for the attack cooldown to reset (in ticks).
|
||||||
|
pub fn get_attack_strength_delay(attributes: &Attributes) -> f32 {
|
||||||
|
((1. / attributes.attack_speed.calculate()) * 20.) as f32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_attack_strength_scale(
|
||||||
|
ticks_since_last_attack: u32,
|
||||||
|
attributes: &Attributes,
|
||||||
|
in_ticks: f32,
|
||||||
|
) -> f32 {
|
||||||
|
let attack_strength_delay = get_attack_strength_delay(attributes);
|
||||||
|
let attack_strength = (ticks_since_last_attack as f32 + in_ticks) / attack_strength_delay;
|
||||||
|
attack_strength.clamp(0., 1.)
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
attack::{self, AttackPlugin},
|
||||||
chat::ChatPlugin,
|
chat::ChatPlugin,
|
||||||
disconnect::{DisconnectEvent, DisconnectPlugin},
|
disconnect::{DisconnectEvent, DisconnectPlugin},
|
||||||
events::{Event, EventPlugin, LocalPlayerEvents},
|
events::{Event, EventPlugin, LocalPlayerEvents},
|
||||||
|
@ -306,6 +307,7 @@ impl Client {
|
||||||
abilities: PlayerAbilities::default(),
|
abilities: PlayerAbilities::default(),
|
||||||
permission_level: PermissionLevel::default(),
|
permission_level: PermissionLevel::default(),
|
||||||
mining: mining::MineBundle::default(),
|
mining: mining::MineBundle::default(),
|
||||||
|
attack: attack::AttackBundle::default(),
|
||||||
_local: Local,
|
_local: Local,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -574,6 +576,7 @@ pub struct JoinedClientBundle {
|
||||||
pub permission_level: PermissionLevel,
|
pub permission_level: PermissionLevel,
|
||||||
|
|
||||||
pub mining: mining::MineBundle,
|
pub mining: mining::MineBundle,
|
||||||
|
pub attack: attack::AttackBundle,
|
||||||
|
|
||||||
pub _local: Local,
|
pub _local: Local,
|
||||||
}
|
}
|
||||||
|
@ -722,6 +725,7 @@ impl PluginGroup for DefaultPlugins {
|
||||||
.add(InteractPlugin)
|
.add(InteractPlugin)
|
||||||
.add(RespawnPlugin)
|
.add(RespawnPlugin)
|
||||||
.add(MinePlugin)
|
.add(MinePlugin)
|
||||||
|
.add(AttackPlugin)
|
||||||
.add(TickBroadcastPlugin);
|
.add(TickBroadcastPlugin);
|
||||||
#[cfg(feature = "log")]
|
#[cfg(feature = "log")]
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,9 @@ use std::ops::AddAssign;
|
||||||
|
|
||||||
use azalea_block::BlockState;
|
use azalea_block::BlockState;
|
||||||
use azalea_core::{BlockHitResult, BlockPos, Direction, GameMode, Vec3};
|
use azalea_core::{BlockHitResult, BlockPos, Direction, GameMode, Vec3};
|
||||||
use azalea_entity::{clamp_look_direction, view_vector, EyeHeight, LookDirection, Position};
|
use azalea_entity::{
|
||||||
|
clamp_look_direction, view_vector, Attributes, EyeHeight, Local, LookDirection, Position,
|
||||||
|
};
|
||||||
use azalea_inventory::{ItemSlot, ItemSlotData};
|
use azalea_inventory::{ItemSlot, ItemSlotData};
|
||||||
use azalea_nbt::NbtList;
|
use azalea_nbt::NbtList;
|
||||||
use azalea_physics::clip::{BlockShapeType, ClipContext, FluidPickType};
|
use azalea_physics::clip::{BlockShapeType, ClipContext, FluidPickType};
|
||||||
|
@ -17,6 +19,7 @@ use bevy_ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::{Event, EventReader, EventWriter},
|
event::{Event, EventReader, EventWriter},
|
||||||
|
query::{Changed, With},
|
||||||
schedule::IntoSystemConfigs,
|
schedule::IntoSystemConfigs,
|
||||||
system::{Commands, Query, Res},
|
system::{Commands, Query, Res},
|
||||||
};
|
};
|
||||||
|
@ -39,12 +42,15 @@ impl Plugin for InteractPlugin {
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
update_hit_result_component.after(clamp_look_direction),
|
(
|
||||||
handle_block_interact_event,
|
update_hit_result_component.after(clamp_look_direction),
|
||||||
handle_swing_arm_event,
|
handle_block_interact_event,
|
||||||
)
|
handle_swing_arm_event,
|
||||||
.before(handle_send_packet_event)
|
)
|
||||||
.chain(),
|
.before(handle_send_packet_event)
|
||||||
|
.chain(),
|
||||||
|
update_modifiers_for_held_item,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -305,3 +311,66 @@ fn handle_swing_arm_event(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn update_modifiers_for_held_item(
|
||||||
|
mut query: Query<
|
||||||
|
(&mut Attributes, &InventoryComponent),
|
||||||
|
(With<Local>, Changed<InventoryComponent>),
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
for (mut attributes, inventory) in &mut query {
|
||||||
|
let held_item = inventory.held_item();
|
||||||
|
|
||||||
|
use azalea_registry::Item;
|
||||||
|
let added_attack_speed = match held_item.kind() {
|
||||||
|
Item::WoodenSword => -2.4,
|
||||||
|
Item::WoodenShovel => -3.0,
|
||||||
|
Item::WoodenPickaxe => -2.8,
|
||||||
|
Item::WoodenAxe => -3.2,
|
||||||
|
Item::WoodenHoe => -3.0,
|
||||||
|
|
||||||
|
Item::StoneSword => -2.4,
|
||||||
|
Item::StoneShovel => -3.0,
|
||||||
|
Item::StonePickaxe => -2.8,
|
||||||
|
Item::StoneAxe => -3.2,
|
||||||
|
Item::StoneHoe => -2.0,
|
||||||
|
|
||||||
|
Item::GoldenSword => -2.4,
|
||||||
|
Item::GoldenShovel => -3.0,
|
||||||
|
Item::GoldenPickaxe => -2.8,
|
||||||
|
Item::GoldenAxe => -3.0,
|
||||||
|
Item::GoldenHoe => -3.0,
|
||||||
|
|
||||||
|
Item::IronSword => -2.4,
|
||||||
|
Item::IronShovel => -3.0,
|
||||||
|
Item::IronPickaxe => -2.8,
|
||||||
|
Item::IronAxe => -3.1,
|
||||||
|
Item::IronHoe => -1.0,
|
||||||
|
|
||||||
|
Item::DiamondSword => -2.4,
|
||||||
|
Item::DiamondShovel => -3.0,
|
||||||
|
Item::DiamondPickaxe => -2.8,
|
||||||
|
Item::DiamondAxe => -3.0,
|
||||||
|
Item::DiamondHoe => 0.0,
|
||||||
|
|
||||||
|
Item::NetheriteSword => -2.4,
|
||||||
|
Item::NetheriteShovel => -3.0,
|
||||||
|
Item::NetheritePickaxe => -2.8,
|
||||||
|
Item::NetheriteAxe => -3.0,
|
||||||
|
Item::NetheriteHoe => 0.0,
|
||||||
|
|
||||||
|
Item::Trident => -2.9,
|
||||||
|
_ => 0.,
|
||||||
|
};
|
||||||
|
attributes
|
||||||
|
.attack_speed
|
||||||
|
.remove(&azalea_entity::attributes::BASE_ATTACK_SPEED_UUID);
|
||||||
|
attributes
|
||||||
|
.attack_speed
|
||||||
|
.insert(azalea_entity::attributes::tool_attack_speed_modifier(
|
||||||
|
added_attack_speed,
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#![feature(type_alias_impl_trait)]
|
#![feature(type_alias_impl_trait)]
|
||||||
|
|
||||||
mod account;
|
mod account;
|
||||||
|
pub mod attack;
|
||||||
pub mod chat;
|
pub mod chat;
|
||||||
mod client;
|
mod client;
|
||||||
pub mod disconnect;
|
pub mod disconnect;
|
||||||
|
|
|
@ -18,6 +18,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
inventory::InventoryComponent,
|
inventory::InventoryComponent,
|
||||||
local_player::{LocalGameMode, SendPacketEvent},
|
local_player::{LocalGameMode, SendPacketEvent},
|
||||||
|
Client,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A plugin that allows clients to break blocks in the world.
|
/// A plugin that allows clients to break blocks in the world.
|
||||||
|
@ -44,6 +45,15 @@ impl Plugin for MinePlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
pub fn start_mining(&mut self, position: BlockPos) {
|
||||||
|
self.ecs.lock().send_event(StartMiningBlockEvent {
|
||||||
|
entity: self.entity,
|
||||||
|
position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Information about the block we're currently mining. This is only present if
|
/// Information about the block we're currently mining. This is only present if
|
||||||
/// we're currently mining a block.
|
/// we're currently mining a block.
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
|
|
|
@ -574,13 +574,14 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
ClientboundGamePacket::AddEntity(p) => {
|
ClientboundGamePacket::AddEntity(p) => {
|
||||||
debug!("Got add entity packet {:?}", p);
|
debug!("Got add entity packet {:?}", p);
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
let mut system_state: SystemState<(
|
let mut system_state: SystemState<(
|
||||||
Commands,
|
Commands,
|
||||||
Query<Option<&InstanceName>>,
|
Query<Option<&InstanceName>>,
|
||||||
ResMut<InstanceContainer>,
|
Res<InstanceContainer>,
|
||||||
ResMut<EntityInfos>,
|
ResMut<EntityInfos>,
|
||||||
)> = SystemState::new(ecs);
|
)> = SystemState::new(ecs);
|
||||||
let (mut commands, mut query, mut instance_container, mut entity_infos) =
|
let (mut commands, mut query, instance_container, mut entity_infos) =
|
||||||
system_state.get_mut(ecs);
|
system_state.get_mut(ecs);
|
||||||
let instance_name = query.get_mut(player_entity).unwrap();
|
let instance_name = query.get_mut(player_entity).unwrap();
|
||||||
|
|
||||||
|
@ -694,9 +695,8 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
ClientboundGamePacket::SetHealth(p) => {
|
ClientboundGamePacket::SetHealth(p) => {
|
||||||
debug!("Got set health packet {:?}", p);
|
debug!("Got set health packet {:?}", p);
|
||||||
|
|
||||||
let mut system_state: SystemState<(Query<&mut Health>, EventWriter<DeathEvent>)> =
|
let mut system_state: SystemState<Query<&mut Health>> = SystemState::new(ecs);
|
||||||
SystemState::new(ecs);
|
let mut query = system_state.get_mut(ecs);
|
||||||
let (mut query, mut death_events) = system_state.get_mut(ecs);
|
|
||||||
let mut health = query.get_mut(player_entity).unwrap();
|
let mut health = query.get_mut(player_entity).unwrap();
|
||||||
|
|
||||||
**health = p.health;
|
**health = p.health;
|
||||||
|
|
|
@ -13,6 +13,7 @@ use uuid::{uuid, Uuid};
|
||||||
#[derive(Clone, Debug, Component)]
|
#[derive(Clone, Debug, Component)]
|
||||||
pub struct Attributes {
|
pub struct Attributes {
|
||||||
pub speed: AttributeInstance,
|
pub speed: AttributeInstance,
|
||||||
|
pub attack_speed: AttributeInstance,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -92,6 +93,24 @@ pub fn sprinting_modifier() -> AttributeModifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static BASE_ATTACK_SPEED_UUID: Uuid = uuid!("FA233E1C-4180-4865-B01B-BCCE9785ACA3");
|
||||||
|
pub fn weapon_attack_speed_modifier(amount: f64) -> AttributeModifier {
|
||||||
|
AttributeModifier {
|
||||||
|
uuid: BASE_ATTACK_SPEED_UUID,
|
||||||
|
name: "Weapon modifier".to_string(),
|
||||||
|
amount,
|
||||||
|
operation: AttributeModifierOperation::Addition,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn tool_attack_speed_modifier(amount: f64) -> AttributeModifier {
|
||||||
|
AttributeModifier {
|
||||||
|
uuid: BASE_ATTACK_SPEED_UUID,
|
||||||
|
name: "Tool modifier".to_string(),
|
||||||
|
amount,
|
||||||
|
operation: AttributeModifierOperation::Addition,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl McBufReadable for AttributeModifier {
|
impl McBufReadable for AttributeModifier {
|
||||||
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||||
let uuid = Uuid::read_from(buf)?;
|
let uuid = Uuid::read_from(buf)?;
|
||||||
|
|
|
@ -344,6 +344,7 @@ impl EntityBundle {
|
||||||
// TODO: do the correct defaults for everything, some
|
// TODO: do the correct defaults for everything, some
|
||||||
// entities have different defaults
|
// entities have different defaults
|
||||||
speed: AttributeInstance::new(0.1),
|
speed: AttributeInstance::new(0.1),
|
||||||
|
attack_speed: AttributeInstance::new(4.0),
|
||||||
},
|
},
|
||||||
|
|
||||||
jumping: Jumping(false),
|
jumping: Jumping(false),
|
||||||
|
|
|
@ -11,6 +11,8 @@ use azalea::pathfinder::BlockPosGoal;
|
||||||
use azalea::protocol::packets::game::ClientboundGamePacket;
|
use azalea::protocol::packets::game::ClientboundGamePacket;
|
||||||
use azalea::{prelude::*, swarm::prelude::*, BlockPos, GameProfileComponent, WalkDirection};
|
use azalea::{prelude::*, swarm::prelude::*, BlockPos, GameProfileComponent, WalkDirection};
|
||||||
use azalea::{Account, Client, Event};
|
use azalea::{Account, Client, Event};
|
||||||
|
use azalea_core::Vec3;
|
||||||
|
use azalea_world::{InstanceName, MinecraftEntityId};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(Default, Clone, Component)]
|
#[derive(Default, Clone, Component)]
|
||||||
|
@ -220,6 +222,56 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
|
||||||
println!("no container found");
|
println!("no container found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"attack" => {
|
||||||
|
let mut nearest_entity = None;
|
||||||
|
let mut nearest_distance = f64::INFINITY;
|
||||||
|
let mut nearest_pos = Vec3::default();
|
||||||
|
let bot_position = bot.position();
|
||||||
|
let bot_entity = bot.entity;
|
||||||
|
let bot_instance_name = bot.component::<InstanceName>();
|
||||||
|
{
|
||||||
|
let mut ecs = bot.ecs.lock();
|
||||||
|
let mut query = ecs.query_filtered::<(
|
||||||
|
azalea::ecs::entity::Entity,
|
||||||
|
&MinecraftEntityId,
|
||||||
|
&Position,
|
||||||
|
&InstanceName,
|
||||||
|
&EyeHeight,
|
||||||
|
), With<MinecraftEntityId>>(
|
||||||
|
);
|
||||||
|
for (entity, &entity_id, position, instance_name, eye_height) in
|
||||||
|
query.iter(&ecs)
|
||||||
|
{
|
||||||
|
if entity == bot_entity {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if instance_name != &bot_instance_name {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let distance = bot_position.distance_to(position);
|
||||||
|
if distance < 4.0 && distance < nearest_distance {
|
||||||
|
nearest_entity = Some(entity_id);
|
||||||
|
nearest_distance = distance;
|
||||||
|
nearest_pos = position.up(**eye_height as f64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(nearest_entity) = nearest_entity {
|
||||||
|
bot.look_at(nearest_pos);
|
||||||
|
bot.attack(nearest_entity);
|
||||||
|
bot.chat("attacking");
|
||||||
|
let mut ticks = bot.get_tick_broadcaster();
|
||||||
|
while ticks.recv().await.is_ok() {
|
||||||
|
if !bot.has_attack_cooldown() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bot.chat("finished attacking");
|
||||||
|
} else {
|
||||||
|
bot.chat("no entities found");
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,5 @@ fn auto_respawn(
|
||||||
perform_respawn_events.send(PerformRespawnEvent {
|
perform_respawn_events.send(PerformRespawnEvent {
|
||||||
entity: event.entity,
|
entity: event.entity,
|
||||||
});
|
});
|
||||||
println!("auto respawning");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,10 @@ use crate::ecs::{
|
||||||
query::{With, Without},
|
query::{With, Without},
|
||||||
system::{Commands, Query},
|
system::{Commands, Query},
|
||||||
};
|
};
|
||||||
use azalea_core::Vec3;
|
use azalea_client::interact::SwingArmEvent;
|
||||||
|
use azalea_client::mining::Mining;
|
||||||
|
use azalea_client::TickBroadcast;
|
||||||
|
use azalea_core::{BlockPos, Vec3};
|
||||||
use azalea_entity::{
|
use azalea_entity::{
|
||||||
clamp_look_direction, metadata::Player, EyeHeight, Jumping, Local, LookDirection, Position,
|
clamp_look_direction, metadata::Player, EyeHeight, Jumping, Local, LookDirection, Position,
|
||||||
};
|
};
|
||||||
|
@ -67,18 +70,25 @@ fn stop_jumping(mut query: Query<(&mut Jumping, &mut Bot)>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait BotClientExt {
|
pub trait BotClientExt {
|
||||||
|
/// Queue a jump for the next tick.
|
||||||
fn jump(&mut self);
|
fn jump(&mut self);
|
||||||
|
/// Turn the bot's head to look at the coordinate in the world.
|
||||||
fn look_at(&mut self, pos: Vec3);
|
fn look_at(&mut self, pos: Vec3);
|
||||||
|
/// Get a receiver that will receive a message every tick.
|
||||||
|
fn get_tick_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()>;
|
||||||
|
/// Mine a block. This won't turn the bot's head towards the block, so if
|
||||||
|
/// that's necessary you'll have to do that yourself with [`look_at`].
|
||||||
|
///
|
||||||
|
/// [`look_at`]: crate::prelude::BotClientExt::look_at
|
||||||
|
async fn mine(&mut self, position: BlockPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BotClientExt for azalea_client::Client {
|
impl BotClientExt for azalea_client::Client {
|
||||||
/// Queue a jump for the next tick.
|
|
||||||
fn jump(&mut self) {
|
fn jump(&mut self) {
|
||||||
let mut ecs = self.ecs.lock();
|
let mut ecs = self.ecs.lock();
|
||||||
ecs.send_event(JumpEvent(self.entity));
|
ecs.send_event(JumpEvent(self.entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turn the bot's head to look at the coordinate in the world.
|
|
||||||
fn look_at(&mut self, position: Vec3) {
|
fn look_at(&mut self, position: Vec3) {
|
||||||
let mut ecs = self.ecs.lock();
|
let mut ecs = self.ecs.lock();
|
||||||
ecs.send_event(LookAtEvent {
|
ecs.send_event(LookAtEvent {
|
||||||
|
@ -86,6 +96,40 @@ impl BotClientExt for azalea_client::Client {
|
||||||
position,
|
position,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// # use azalea::prelude::*;
|
||||||
|
/// # async fn example(mut bot: azalea::Client) {
|
||||||
|
/// let mut ticks = self.get_tick_broadcaster();
|
||||||
|
/// while ticks.recv().await.is_ok() {
|
||||||
|
/// let ecs = bot.ecs.lock();
|
||||||
|
/// if ecs.get::<WaitingForInventoryOpen>(self.entity).is_none() {
|
||||||
|
/// break;
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
fn get_tick_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()> {
|
||||||
|
let ecs = self.ecs.lock();
|
||||||
|
let tick_broadcast = ecs.resource::<TickBroadcast>();
|
||||||
|
tick_broadcast.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn mine(&mut self, position: BlockPos) {
|
||||||
|
self.start_mining(position);
|
||||||
|
// vanilla sends an extra swing arm packet when we start mining
|
||||||
|
self.ecs.lock().send_event(SwingArmEvent {
|
||||||
|
entity: self.entity,
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut receiver = self.get_tick_broadcaster();
|
||||||
|
while receiver.recv().await.is_ok() {
|
||||||
|
let ecs = self.ecs.lock();
|
||||||
|
if ecs.get::<Mining>(self.entity).is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Event to jump once.
|
/// Event to jump once.
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::fmt::Formatter;
|
||||||
use azalea_client::{
|
use azalea_client::{
|
||||||
inventory::{CloseContainerEvent, ContainerClickEvent, InventoryComponent},
|
inventory::{CloseContainerEvent, ContainerClickEvent, InventoryComponent},
|
||||||
packet_handling::PacketEvent,
|
packet_handling::PacketEvent,
|
||||||
Client, TickBroadcast,
|
Client,
|
||||||
};
|
};
|
||||||
use azalea_core::BlockPos;
|
use azalea_core::BlockPos;
|
||||||
use azalea_inventory::{operations::ClickOperation, ItemSlot, Menu};
|
use azalea_inventory::{operations::ClickOperation, ItemSlot, Menu};
|
||||||
|
@ -12,6 +12,8 @@ use bevy_app::{App, Plugin, Update};
|
||||||
use bevy_ecs::{component::Component, prelude::EventReader, system::Commands};
|
use bevy_ecs::{component::Component, prelude::EventReader, system::Commands};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use crate::bot::BotClientExt;
|
||||||
|
|
||||||
pub struct ContainerPlugin;
|
pub struct ContainerPlugin;
|
||||||
impl Plugin for ContainerPlugin {
|
impl Plugin for ContainerPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
|
@ -49,11 +51,7 @@ impl ContainerClientExt for Client {
|
||||||
.insert(WaitingForInventoryOpen);
|
.insert(WaitingForInventoryOpen);
|
||||||
self.block_interact(pos);
|
self.block_interact(pos);
|
||||||
|
|
||||||
let mut receiver = {
|
let mut receiver = self.get_tick_broadcaster();
|
||||||
let ecs = self.ecs.lock();
|
|
||||||
let tick_broadcast = ecs.resource::<TickBroadcast>();
|
|
||||||
tick_broadcast.subscribe()
|
|
||||||
};
|
|
||||||
while receiver.recv().await.is_ok() {
|
while receiver.recv().await.is_ok() {
|
||||||
let ecs = self.ecs.lock();
|
let ecs = self.ecs.lock();
|
||||||
if ecs.get::<WaitingForInventoryOpen>(self.entity).is_none() {
|
if ecs.get::<WaitingForInventoryOpen>(self.entity).is_none() {
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
mod auto_respawn;
|
mod auto_respawn;
|
||||||
mod bot;
|
mod bot;
|
||||||
mod container;
|
mod container;
|
||||||
pub mod mining;
|
|
||||||
pub mod pathfinder;
|
pub mod pathfinder;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
pub mod swarm;
|
pub mod swarm;
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
use azalea_client::{
|
|
||||||
interact::SwingArmEvent,
|
|
||||||
mining::{Mining, StartMiningBlockEvent},
|
|
||||||
Client, TickBroadcast,
|
|
||||||
};
|
|
||||||
use azalea_core::BlockPos;
|
|
||||||
|
|
||||||
pub trait MiningExt {
|
|
||||||
/// Start mining a block.
|
|
||||||
async fn mine(&mut self, position: BlockPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MiningExt for Client {
|
|
||||||
/// Start mining a block. This won't turn the bot's head towards the block,
|
|
||||||
/// so you'll have to do that yourself with [`look_at`].
|
|
||||||
///
|
|
||||||
/// [`look_at`]: crate::prelude::BotClientExt::look_at
|
|
||||||
async fn mine(&mut self, position: BlockPos) {
|
|
||||||
self.ecs.lock().send_event(StartMiningBlockEvent {
|
|
||||||
entity: self.entity,
|
|
||||||
position,
|
|
||||||
});
|
|
||||||
// vanilla sends an extra swing arm packet when we start mining
|
|
||||||
self.ecs.lock().send_event(SwingArmEvent {
|
|
||||||
entity: self.entity,
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut receiver = {
|
|
||||||
let ecs = self.ecs.lock();
|
|
||||||
let tick_broadcast = ecs.resource::<TickBroadcast>();
|
|
||||||
tick_broadcast.subscribe()
|
|
||||||
};
|
|
||||||
while receiver.recv().await.is_ok() {
|
|
||||||
let ecs = self.ecs.lock();
|
|
||||||
if ecs.get::<Mining>(self.entity).is_none() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,8 +2,8 @@
|
||||||
//! re-exported here.
|
//! re-exported here.
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
bot::BotClientExt, container::ContainerClientExt, mining::MiningExt,
|
bot::BotClientExt, container::ContainerClientExt, pathfinder::PathfinderClientExt,
|
||||||
pathfinder::PathfinderClientExt, ClientBuilder,
|
ClientBuilder,
|
||||||
};
|
};
|
||||||
pub use azalea_client::{Account, Client, Event};
|
pub use azalea_client::{Account, Client, Event};
|
||||||
// this is necessary to make the macros that reference bevy_ecs work
|
// this is necessary to make the macros that reference bevy_ecs work
|
||||||
|
|
Loading…
Add table
Reference in a new issue