use std::sync::atomic::Ordering; use std::{sync::Arc, thread, time::Duration}; use config::handle_configuration_state; use helper::{ send_game_event, send_keep_alive, send_system_message, set_center_chunk, sync_player_pos, unload_chunk, }; use rust_mc_proto::{DataReader, DataWriter, Packet}; use uuid::Uuid; use crate::player::context::EntityInfo; use crate::{ ServerError, data::text_component::TextComponent, event::PacketHandler, player::context::ClientContext, }; use crate::protocol::{ConnectionState, packet_id::*}; pub mod config; pub mod helper; pub mod planner; pub struct PlayHandler; impl PacketHandler for PlayHandler { fn on_outcoming_packet( &self, client: Arc, packet: &mut Packet, cancel: &mut bool, state: ConnectionState, ) -> Result<(), ServerError> { if !*cancel // проверяем что пакет не отмененный, облегчаем себе задачу, ведь проверять айди наверняка сложней && state == ConnectionState::Configuration // проверяем стейт, т.к айди могут быть одинаковыми между стейтами && packet.id() == clientbound::configuration::FINISH { handle_configuration_state(client)?; // делаем наши грязные дела } Ok(()) } fn on_state( &self, client: Arc, state: ConnectionState, ) -> Result<(), ServerError> { if state == ConnectionState::Play { // перешли в режим плей, отлично! делаем дела handle_play_state(client)?; } Ok(()) } } pub fn send_login(client: Arc) -> Result<(), ServerError> { // Отправка пакета Login let mut packet = Packet::empty(clientbound::play::LOGIN); packet.write_int(client.entity_info().entity_id)?; // Entity ID packet.write_boolean(false)?; // Is hardcore packet.write_varint(4)?; // Dimension Names packet.write_string("minecraft:overworld")?; packet.write_string("minecraft:nether")?; packet.write_string("minecraft:the_end")?; packet.write_string("minecraft:overworld_caves")?; packet.write_varint(0)?; // Max Players packet.write_varint(8)?; // View Distance packet.write_varint(5)?; // Simulation Distance packet.write_boolean(false)?; // Reduced Debug Info packet.write_boolean(true)?; // Enable respawn screen packet.write_boolean(false)?; // Do limited crafting packet.write_varint(0)?; // Dimension Type packet.write_string("minecraft:overworld")?; // Dimension Name packet.write_long(0x0f38f26ad09c3e20)?; // Hashed seed packet.write_byte(0)?; // Game mode packet.write_signed_byte(-1)?; // Previous Game mode packet.write_boolean(false)?; // Is Debug packet.write_boolean(true)?; // Is Flat packet.write_boolean(false)?; // Has death location packet.write_varint(20)?; // Portal cooldown packet.write_varint(60)?; // Sea level packet.write_boolean(false)?; // Enforces Secure Chat client.write_packet(&packet) } pub fn send_example_chunk(client: Arc, x: i32, z: i32) -> Result<(), ServerError> { let mut packet = Packet::empty(clientbound::play::CHUNK_DATA_AND_UPDATE_LIGHT); packet.write_int(x)?; packet.write_int(z)?; // heightmap packet.write_varint(1)?; // heightmaps count packet.write_varint(0)?; // MOTION_BLOCKING - 0 // bits per entry is ceil(log2(385)) = 9 where 385 is the world height // so, the length of the following array is (9 * 16 * 16) / 8 = 37 // ... idk how it came to that packet.write_varint(37)?; // Length of the following long array for _ in 0..37 { packet.write_long(0)?; // THIS WORKS ONLY BECAUSE OUR HEIGHT IS 0 } // sending chunk data let mut chunk_data = Vec::new(); // we want to fill the area from -64 to 0, so it will be 4 chunk sections for _ in 0..4 { chunk_data.write_short(4096)?; // non-air blocks count, 16 * 16 * 16 = 4096 stone blocks // blocks paletted container chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format chunk_data.write_varint(10)?; // block state id in the registry (1 for stone, 10 for dirt) // biomes palleted container chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format chunk_data.write_varint(1)?; // biome id in the registry } // air chunk sections for _ in 0..20 { chunk_data.write_short(0)?; // non-air blocks count, 0 // blocks paletted container chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format chunk_data.write_varint(0)?; // block state id in the registry (0 for air) // biomes palleted container chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format chunk_data.write_varint(27)?; // biome id in the registry } packet.write_usize_varint(chunk_data.len())?; packet.write_bytes(&chunk_data)?; packet.write_byte(0)?; // light data packet.write_byte(0)?; packet.write_byte(0)?; packet.write_byte(0)?; packet.write_byte(0)?; packet.write_byte(0)?; packet.write_byte(0)?; client.write_packet(&packet)?; Ok(()) } pub fn send_example_chunks_in_distance( client: Arc, chunks: &mut Vec<(i32, i32)>, distance: i32, center: (i32, i32), ) -> Result<(), ServerError> { let mut new_chunks = Vec::new(); for x in -distance + center.0..=distance + center.0 { for z in -distance + center.1..=distance + center.1 { if !chunks.contains(&(x, z)) { send_example_chunk(client.clone(), x as i32, z as i32)?; } new_chunks.push((x, z)); } } for (x, z) in chunks.iter() { if !new_chunks.contains(&(*x, *z)) { unload_chunk(client.clone(), *x, *z)?; } } *chunks = new_chunks; Ok(()) } pub fn send_player( receiver: Arc, player: Arc, ) -> Result<(), ServerError> { // Отправка пакета Login let mut packet = Packet::empty(clientbound::play::SPAWN_ENTITY); let (x, y, z) = player.entity_info().position(); let (yaw, pitch) = player.entity_info().rotation(); let (vel_x, vel_y, vel_z) = player.entity_info().velocity(); packet.write_varint(player.entity_info().entity_id)?; // Entity ID packet.write_uuid(&player.entity_info().uuid)?; // Entity UUID packet.write_varint(148)?; // Entity type TODO: move to const packet.write_double(x)?; packet.write_double(y)?; packet.write_double(z)?; packet.write_byte((pitch / 360.0 * 256.0) as u8)?; packet.write_byte((yaw / 360.0 * 256.0) as u8)?; packet.write_byte((yaw / 360.0 * 256.0) as u8)?; // head yaw TODO: make player head yaw field packet.write_varint(0)?; packet.write_short(vel_x as i16)?; packet.write_short(vel_y as i16)?; packet.write_short(vel_z as i16)?; receiver.write_packet(&packet) } pub fn get_offline_uuid(name: &str) -> Uuid { let mut namespaces_bytes: [u8; 16] = [0; 16]; for (i, byte) in format!("OfflinePlayer:{}", &name[..2]) .as_bytes() .iter() .enumerate() { namespaces_bytes[i] = *byte; } let namespace = Uuid::from_bytes(namespaces_bytes); Uuid::new_v3(&namespace, (&name[2..]).as_bytes()) } // Отдельная функция для работы с самой игрой pub fn handle_play_state( client: Arc, // Контекст клиента ) -> Result<(), ServerError> { client.set_entity_info(EntityInfo::new( client .server .world .entity_id_counter .fetch_add(1, Ordering::SeqCst), get_offline_uuid(&client.player_info().unwrap().name), // TODO: authenticated uuid )); thread::spawn({ let client = client.clone(); move || { let _ = client.run_read_loop(); client.close(); } }); send_login(client.clone())?; sync_player_pos(client.clone(), 8.0, 0.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0)?; send_game_event(client.clone(), 13, 0.0)?; // 13 - Start waiting for level chunks set_center_chunk(client.clone(), 0, 0)?; let mut chunks = Vec::new(); let view_distance = client.client_info().unwrap().view_distance as i32 / 2; send_example_chunks_in_distance(client.clone(), &mut chunks, view_distance, (0, 0))?; // sync_player_pos(client.clone(), 8.0, 0.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0)?; for player in client.server.players() { send_player(client.clone(), player.clone())?; send_player(player.clone(), client.clone())?; } thread::spawn({ let client = client.clone(); move || -> Result<(), ServerError> { while client.is_alive() { let mut packet = client.read_packet(&[ serverbound::play::SET_PLAYER_POSITION, serverbound::play::SET_PLAYER_POSITION_AND_ROTATION, serverbound::play::SET_PLAYER_ROTATION, ])?; match packet.id() { serverbound::play::SET_PLAYER_POSITION => { let x = packet.read_double()?; let y = packet.read_double()?; let z = packet.read_double()?; let _ = packet.read_byte()?; // flags client.entity_info().set_position((x, y, z)); } serverbound::play::SET_PLAYER_POSITION_AND_ROTATION => { let x = packet.read_double()?; let y = packet.read_double()?; let z = packet.read_double()?; let yaw = packet.read_float()?; let pitch = packet.read_float()?; let _ = packet.read_byte()?; // flags client.entity_info().set_position((x, y, z)); client.entity_info().set_rotation((yaw, pitch)); } serverbound::play::SET_PLAYER_ROTATION => { let yaw = packet.read_float()?; let pitch = packet.read_float()?; let _ = packet.read_byte()?; // flags client.entity_info().set_rotation((yaw, pitch)); } _ => {} } } Ok(()) } }); let mut ticks_alive = 0u64; while client.is_alive() { if ticks_alive % 200 == 0 { // 10 secs timer send_keep_alive(client.clone())?; } if ticks_alive % 20 == 0 { // 1 sec timer let (x, _, z) = client.entity_info().position(); let (chunk_x, chunk_z) = ((x / 16.0) as i64, (z / 16.0) as i64); let (chunk_x, chunk_z) = (chunk_x as i32, chunk_z as i32); set_center_chunk(client.clone(), chunk_x, chunk_z)?; send_example_chunks_in_distance( client.clone(), &mut chunks, view_distance, (chunk_x, chunk_z), )?; } // text animation { let animation_text = format!("Ticks alive: {} жёпа", ticks_alive); let animation_index = ((ticks_alive + 40) % 300) as usize; let animation_end = animation_text.len() + 20; if animation_index < animation_end { let now_length = (animation_index + 1).min(animation_text.chars().count()); let now_text = animation_text.chars().take(now_length).collect(); send_system_message(client.clone(), TextComponent::rainbow(now_text), true)?; } } thread::sleep(Duration::from_millis(50)); // 1 tick ticks_alive += 1; } Ok(()) }