nurtex 0.5.2

Lightweight library for creating Minecraft bots. Asynchronous, optimized, ease of coding.
Documentation
use std::io::{self, Error, ErrorKind};
use std::pin::Pin;
use std::sync::Arc;

use azalea_core::position::{BlockPos, ChunkPos};
use azalea_protocol::packets::game::{
  ClientboundGamePacket, ServerboundAcceptTeleportation, ServerboundClientCommand, ServerboundGamePacket, ServerboundKeepAlive, ServerboundPong,
  s_resource_pack::ServerboundResourcePack,
};
use hashbrown::HashMap;

use crate::bot::Bot;
use crate::bot::components::inventory::InventoryItem;
use crate::bot::components::position::Position;
use crate::bot::components::rotation::Rotation;
use crate::bot::components::velocity::Velocity;
use crate::bot::events::{BotEvent, ChatPayload};
use crate::bot::events::{ChunkPayload, DisconnectPayload};
use crate::bot::transmitter::BotPackage;
use crate::bot::world::entity::{Entity, PlayerInfo};
use crate::utils::time::timestamp;

/// Тип обработчика пакетов
pub type PacketProcessorFn<P> = for<'a> fn(&'a mut Bot<P>, Arc<ClientboundGamePacket>) -> Pin<Box<dyn std::future::Future<Output = io::Result<bool>> + Send + 'a>>;

/// Дефолтный обработчик пакетов
pub fn default_packet_processor<P: BotPackage>(
  bot: &mut Bot<P>,
  packet: Arc<ClientboundGamePacket>,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = io::Result<bool>> + Send + '_>> {
  Box::pin(process_packet(bot, packet))
}

/// Функция обработки пакета (в состоянии Play)
async fn process_packet<P: BotPackage>(bot: &mut Bot<P>, packet: Arc<ClientboundGamePacket>) -> io::Result<bool> {
  match &*packet {
    ClientboundGamePacket::AddEntity(p) => {
      let entity = Entity {
        entity_type: p.entity_type.to_string(),
        uuid: p.uuid,
        position: Position::from_vec3(p.position),
        velocity: Velocity::zero(),
        rotation: Rotation::new(p.y_rot.into(), p.x_rot.into()),
        on_ground: false,
        player_info: None,
      };

      bot
        .lock_storage(|storage| {
          storage.entities.insert(p.id.0, entity);
        })
        .await;
    }
    ClientboundGamePacket::RemoveEntities(p) => {
      let mut ids = Vec::new();
      p.entity_ids.iter().for_each(|id| ids.push(id.0));

      bot
        .lock_storage(|storage| {
          storage.entities.retain(|id, _| !ids.contains(id));
        })
        .await;
    }
    ClientboundGamePacket::LevelChunkWithLight(p) => {
      let chunk_data = p.chunk_data.data.to_vec();

      bot
        .lock_storage(|storage| {
          storage.load_chunk(p.x, p.z, chunk_data);
        })
        .await;

      bot.emit_event(BotEvent::ChunkLoaded(ChunkPayload { x: p.x, z: p.z }));
    }
    ClientboundGamePacket::ForgetLevelChunk(p) => {
      let chunk_pos = ChunkPos::new(p.pos.x, p.pos.z);

      bot
        .lock_storage(|storage| {
          storage.remove_chunk(&chunk_pos);
        })
        .await;
    }
    ClientboundGamePacket::BlockUpdate(p) => {
      let pos = BlockPos::new(p.pos.x, p.pos.y, p.pos.z);
      let block_state = p.block_state.id() as u32;

      bot
        .lock_storage(|storage| {
          storage.set_block(&pos, block_state);
        })
        .await;
    }
    ClientboundGamePacket::SectionBlocksUpdate(p) => {
      let mut blocks = HashMap::new();

      for state in &p.states {
        let local_x = state.pos.x as i32;
        let local_y = state.pos.y as i32;
        let local_z = state.pos.z as i32;

        let pos = BlockPos::new((p.section_pos.x << 4) + local_x, (p.section_pos.y << 4) + local_y, (p.section_pos.z << 4) + local_z);

        let block_state = state.state.id() as u32;

        blocks.insert(pos, block_state);
      }

      bot
        .lock_storage(|storage| {
          storage.set_block_section(blocks);
        })
        .await;
    }
    ClientboundGamePacket::Login(p) => {
      bot.emit_event(BotEvent::Spawn);

      if p.show_death_screen && bot.plugins.auto_respawn.enabled {
        let Some(conn) = &mut bot.connection else {
          return Err(Error::new(ErrorKind::NotConnected, "Connection could not be obtained"));
        };

        conn
          .write(ServerboundGamePacket::ClientCommand(ServerboundClientCommand {
            action: azalea_protocol::packets::game::s_client_command::Action::PerformRespawn,
          }))
          .await?;
      }

      let profile = &mut bot.components.profile;

      profile.entity_id = Some(p.player_id.0);
      profile.game_mode = p.common.game_type.name().to_string();
    }
    ClientboundGamePacket::MoveEntityPos(p) => {
      if bot.is_this_my_entity_id(p.entity_id.0) {
        return Ok(true);
      }

      bot
        .lock_storage(|storage| {
          if let Some(entity) = storage.entities.get_mut(&p.entity_id.0) {
            entity.position.apply_velocity(Velocity::from_vec3(p.delta.clone().into()));
            entity.on_ground = p.on_ground;
          }
        })
        .await;
    }
    ClientboundGamePacket::MoveEntityRot(p) => {
      bot
        .lock_storage(|storage| {
          if let Some(entity) = storage.entities.get_mut(&p.entity_id.0) {
            entity.on_ground = p.on_ground;

            let yaw = entity.rotation.yaw + p.y_rot as f32;
            let pitch = entity.rotation.pitch + p.x_rot as f32;

            entity.rotation = Rotation::new(yaw, pitch);
          }
        })
        .await;
    }
    ClientboundGamePacket::MoveEntityPosRot(p) => {
      bot
        .lock_storage(|storage| {
          if let Some(entity) = storage.entities.get_mut(&p.entity_id.0) {
            entity.position.apply_velocity(Velocity::from_vec3(p.delta.clone().into()));
            entity.on_ground = p.on_ground;

            let yaw = entity.rotation.yaw + p.y_rot as f32;
            let pitch = entity.rotation.pitch + p.x_rot as f32;

            entity.rotation = Rotation::new(yaw, pitch);
          }
        })
        .await;
    }
    ClientboundGamePacket::PlayerInfoUpdate(p) => {
      for entry in &p.entries {
        if entry.profile.name == bot.username {
          bot.components.profile.ping = entry.latency;
        } else {
          bot
            .lock_storage(|storage| {
              for (_id, entity) in &mut storage.entities {
                if entity.uuid != entry.profile.uuid {
                  continue;
                }

                let player_info = PlayerInfo {
                  username: entry.profile.name.clone(),
                  game_mode: entry.game_mode.name().to_string(),
                  ping: entry.latency,
                };

                entity.player_info = Some(player_info);
              }
            })
            .await;
        }
      }
    }
    ClientboundGamePacket::SetHealth(p) => {
      let state = &mut bot.components.state;
      state.health = p.health;
      state.satiety = p.food;
      state.saturation = p.saturation;
    }
    ClientboundGamePacket::PlayerRotation(p) => {
      bot.update_rotation(Rotation::new(p.y_rot, p.x_rot));
    }
    ClientboundGamePacket::PlayerPosition(p) => {
      bot.update_position(Position::new(p.change.pos.x, p.change.pos.y, p.change.pos.z));
      bot.update_rotation(Rotation::from_look_direction(p.change.look_direction));

      let Some(conn) = &mut bot.connection else {
        return Err(Error::new(ErrorKind::NotConnected, "Connection could not be obtained"));
      };

      conn.write(ServerboundGamePacket::AcceptTeleportation(ServerboundAcceptTeleportation { id: p.id })).await?;
    }
    ClientboundGamePacket::SetEntityMotion(p) => {
      if bot.is_this_my_entity_id(p.id.0) {
        bot.components.velocity = Velocity::from_vec3(p.delta.to_vec3());
      } else {
        bot
          .lock_storage(|storage| {
            if let Some(entity) = storage.entities.get_mut(&p.id.0) {
              entity.velocity = Velocity::from_vec3(p.delta.to_vec3());
            }
          })
          .await;
      }
    }
    ClientboundGamePacket::EntityPositionSync(p) => {
      if bot.is_this_my_entity_id(p.id.0) {
        return Ok(true);
      }

      bot
        .lock_storage(|storage| {
          if let Some(entity) = storage.entities.get_mut(&p.id.0) {
            entity.position = Position::from_vec3(p.values.pos);
            entity.velocity = Velocity::from_vec3(p.values.delta);
            entity.rotation = Rotation::from_look_direction(p.values.look_direction);
            entity.on_ground = p.on_ground;
          }
        })
        .await;
    }
    ClientboundGamePacket::KeepAlive(p) => {
      let Some(conn) = &mut bot.connection else {
        return Err(Error::new(ErrorKind::NotConnected, "Connection could not be obtained"));
      };

      conn.write(ServerboundGamePacket::KeepAlive(ServerboundKeepAlive { id: p.id })).await?;
    }
    ClientboundGamePacket::Ping(p) => {
      let Some(conn) = &mut bot.connection else {
        return Err(Error::new(ErrorKind::NotConnected, "Connection could not be obtained"));
      };

      conn.write(ServerboundGamePacket::Pong(ServerboundPong { id: p.id })).await?;
    }
    ClientboundGamePacket::PlayerCombatKill(_p) => {
      bot.emit_event(BotEvent::Death);

      if bot.plugins.auto_respawn.enabled {
        let Some(conn) = &mut bot.connection else {
          return Err(Error::new(ErrorKind::NotConnected, "Connection could not be obtained"));
        };

        conn
          .write(ServerboundGamePacket::ClientCommand(ServerboundClientCommand {
            action: azalea_protocol::packets::game::s_client_command::Action::PerformRespawn,
          }))
          .await?;
      }
    }
    ClientboundGamePacket::SystemChat(p) => {
      bot.emit_event(BotEvent::Chat(ChatPayload {
        sender_uuid: None,
        message: p.content.to_string(),
        timestamp: timestamp(),
      }));
    }
    ClientboundGamePacket::PlayerChat(p) => {
      bot.emit_event(BotEvent::Chat(ChatPayload {
        sender_uuid: Some(p.sender),
        message: p.message().to_string(),
        timestamp: timestamp(),
      }));
    }
    ClientboundGamePacket::Disconnect(p) => {
      bot.emit_event(BotEvent::Disconnect(DisconnectPayload {
        reason: p.reason.to_string(),
        timestamp: timestamp(),
      }));

      return Err(Error::new(ErrorKind::ConnectionAborted, format!("Disconnected (Play): {}", p.reason.to_string())));
    }
    ClientboundGamePacket::ResourcePackPush(p) => {
      let Some(conn) = &mut bot.connection else {
        return Err(Error::new(ErrorKind::NotConnected, "Connection could not be obtained"));
      };
      conn
        .write(ServerboundGamePacket::ResourcePack(ServerboundResourcePack {
          id: p.id,
          action: azalea_protocol::packets::game::s_resource_pack::Action::Accepted,
        }))
        .await?;
      conn
        .write(ServerboundGamePacket::ResourcePack(ServerboundResourcePack {
          id: p.id,
          action: azalea_protocol::packets::game::s_resource_pack::Action::SuccessfullyLoaded,
        }))
        .await?;
    }
    ClientboundGamePacket::SetExperience(p) => {
      let experience = &mut bot.components.experience;
      experience.level = p.experience_level;
      experience.progress = p.experience_progress;
      experience.total = p.total_experience;
    }
    ClientboundGamePacket::ContainerSetContent(p) => {
      if bot.components.inventory.get_container(p.container_id).is_none() {
        bot.components.inventory.add_container(p.container_id);
      }

      if let Some(container) = bot.components.inventory.get_mut_container(p.container_id) {
        container.carried_item = p.carried_item.clone();

        for (slot, item) in p.items.iter().enumerate() {
          container.items.push(InventoryItem {
            name: item.kind().to_string(),
            count: item.count(),
            slot: slot as u32,
          });
        }
      }
    }
    _ => return Ok(true),
  }

  Ok(true)
}