2025-05-07 23:38:48 +03:00

506 lines
16 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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::event::Listener;
use crate::player::context::EntityInfo;
use crate::{
ServerError, data::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 struct PlayListener;
impl Listener for PlayListener {
fn on_disconnect(&self, client: Arc<ClientContext>) -> Result<(), ServerError> {
handle_disconnect(client)
}
}
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(57)?; // block state id in the registry (https://minecraft-ids.grahamedgecombe.com/)
// 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> {
let mut packet = Packet::empty(clientbound::play::PLAYER_INFO_UPDATE);
packet.write_byte(0x01)?; // only Add Player
packet.write_varint(1)?; // players list
packet.write_uuid(&player.entity_info().uuid)?; // player uuid
packet.write_string(&player.player_info().unwrap().name)?; // player name
packet.write_varint(0)?; // no properties
receiver.write_packet(&packet)?;
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_signed_byte((pitch / 360.0 * 256.0) as i8)?;
packet.write_signed_byte((yaw / 360.0 * 256.0) as i8)?;
packet.write_signed_byte((yaw / 360.0 * 256.0) as i8)?; // 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)?;
Ok(())
}
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 send_rainbow_message(
client: &Arc<ClientContext>,
message: String,
) -> Result<(), ServerError> {
send_system_message(client.clone(), TextComponent::rainbow(message), false)
}
// Отдельная функция для работы с самой игрой
pub fn handle_play_state(
client: Arc<ClientContext>, // Контекст клиента
) -> Result<(), ServerError> {
let player_name = client.player_info().unwrap().name;
let player_uuid = get_offline_uuid(&client.player_info().unwrap().name); // TODO: authenticated uuid
let entity_id = client
.server
.world
.entity_id_counter
.fetch_add(1, Ordering::SeqCst);
client.set_entity_info(EntityInfo::new(entity_id, player_uuid));
client.entity_info().set_position((8.0, 0.0, 8.0)); // set 8 0 8 as position
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
send_game_event(client.clone(), 3, 1.0)?; // 3 - Set gamemode, 1.0 - creative
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)?;
send_rainbow_message(&client, format!("Your IP: {}", client.addr))?;
send_rainbow_message(
&client,
format!("Your brand: {}", client.client_info().unwrap().brand),
)?;
send_rainbow_message(
&client,
format!("Your locale: {}", client.client_info().unwrap().locale),
)?;
send_rainbow_message(&client, format!("Your UUID: {}", client.entity_info().uuid))?;
send_rainbow_message(&client, format!("Your Name: {}", &player_name))?;
send_rainbow_message(&client, format!("Your Entity ID: {}", entity_id))?;
for player in client.server.players() {
if client.addr == player.addr {
continue;
}
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,
serverbound::play::CHAT_MESSAGE,
serverbound::play::CLICK_CONTAINER,
])?;
match packet.id() {
serverbound::play::CLICK_CONTAINER => {
let window_id = packet.read_varint()?;
let state_id = packet.read_varint()?;
let slot = packet.read_short()?;
let button = packet.read_byte()?;
let mode = packet.read_varint()?;
// i cannot read item slots now
send_rainbow_message(&client, format!("index clicked: {slot}"))?;
}
serverbound::play::CHAT_MESSAGE => {
let message_text = packet.read_string()?;
// skip remaining data coz they suck
let mut message =
TextComponent::rainbow(format!("{} said: ", client.player_info().unwrap().name));
message.italic = Some(true);
let text_message = TextComponent::builder()
.color("white")
.text(&message_text)
.italic(false)
.build();
if let Some(extra) = &mut message.extra {
extra.push(text_message);
}
for player in client.server.players() {
send_system_message(player, message.clone(), false)?;
}
}
serverbound::play::SET_PLAYER_POSITION => {
let x = packet.read_double()?;
let y = packet.read_double()?;
let z = packet.read_double()?;
let flags = packet.read_byte()?; // flags
let prev = client.entity_info().position();
for player in client.server.players() {
if client.addr == player.addr {
continue;
}
let mut packet = Packet::empty(clientbound::play::UPDATE_ENTITY_POSITION);
packet.write_varint(client.entity_info().entity_id)?;
packet.write_short((x * 4096.0 - prev.0 * 4096.0) as i16)?; // formula: currentX * 4096 - prevX * 4096
packet.write_short((y * 4096.0 - prev.1 * 4096.0) as i16)?;
packet.write_short((z * 4096.0 - prev.2 * 4096.0) as i16)?;
packet.write_boolean(flags & 0x01 != 0)?;
player.write_packet(&packet)?;
}
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 flags = packet.read_byte()?; // flags
let prev = client.entity_info().position();
for player in client.server.players() {
if client.addr == player.addr {
continue;
}
let mut packet =
Packet::empty(clientbound::play::UPDATE_ENTITY_POSITION_AND_ROTATION);
packet.write_varint(client.entity_info().entity_id)?;
packet.write_short((x * 4096.0 - prev.0 * 4096.0) as i16)?; // formula: currentX * 4096 - prevX * 4096
packet.write_short((y * 4096.0 - prev.1 * 4096.0) as i16)?;
packet.write_short((z * 4096.0 - prev.2 * 4096.0) as i16)?;
packet.write_signed_byte((yaw / 360.0 * 256.0) as i8)?;
packet.write_signed_byte((pitch / 360.0 * 256.0) as i8)?;
packet.write_boolean(flags & 0x01 != 0)?;
player.write_packet(&packet)?;
let mut packet = Packet::empty(clientbound::play::SET_HEAD_ROTATION);
packet.write_varint(client.entity_info().entity_id)?;
packet.write_signed_byte((yaw / 360.0 * 256.0) as i8)?;
player.write_packet(&packet)?;
}
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 flags = packet.read_byte()?; // flags
for player in client.server.players() {
if client.addr == player.addr {
continue;
}
let mut packet = Packet::empty(clientbound::play::UPDATE_ENTITY_ROTATION);
packet.write_varint(client.entity_info().entity_id)?;
packet.write_signed_byte((yaw / 360.0 * 256.0) as i8)?;
packet.write_signed_byte((pitch / 360.0 * 256.0) as i8)?;
packet.write_boolean(flags & 0x01 != 0)?;
player.write_packet(&packet)?;
let mut packet = Packet::empty(clientbound::play::SET_HEAD_ROTATION);
packet.write_varint(client.entity_info().entity_id)?;
packet.write_signed_byte((yaw / 360.0 * 256.0) as i8)?;
player.write_packet(&packet)?;
}
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(())
}
pub fn handle_disconnect(
client: Arc<ClientContext>, // Контекст клиента
) -> Result<(), ServerError> {
Ok(())
}