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))
}
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)
}