361 lines
10 KiB
Rust
361 lines
10 KiB
Rust
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<ClientContext>,
|
||
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<ClientContext>,
|
||
state: ConnectionState,
|
||
) -> Result<(), ServerError> {
|
||
if state == ConnectionState::Play {
|
||
// перешли в режим плей, отлично! делаем дела
|
||
|
||
handle_play_state(client)?;
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
pub fn send_login(client: Arc<ClientContext>) -> 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<ClientContext>, 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<ClientContext>,
|
||
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<ClientContext>,
|
||
player: Arc<ClientContext>,
|
||
) -> 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<ClientContext>, // Контекст клиента
|
||
) -> 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(())
|
||
}
|