Compare commits

..

10 Commits

Author SHA1 Message Date
a746a0d9b4 fix player spawn and send move packets 2025-05-07 18:27:38 +03:00
8b5084460e send players 2025-05-07 17:55:32 +03:00
4036be8cc1 fancy animation 2025-05-07 14:22:43 +03:00
3ff2af9e28 rearrange play modules 2025-05-07 13:50:09 +03:00
221a8dd849 unload chunks out of distance 2025-05-07 13:39:12 +03:00
fc93e519b5 fix shell nix 2025-05-07 12:40:34 +03:00
f4873b1f21 add play handler to readme example 2025-05-07 12:23:17 +03:00
68fa183c0e move play state to its own module 2025-05-07 12:22:42 +03:00
b83efa34cb move play state to a packet handler 2025-05-07 12:20:48 +03:00
a7636c4028 rename id.rs to packet_id.rs 2025-05-07 12:04:55 +03:00
21 changed files with 772 additions and 428 deletions

64
Cargo.lock generated
View File

@ -109,6 +109,15 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.17.0"
@ -214,6 +223,16 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "darling"
version = "0.20.11"
@ -273,6 +292,16 @@ dependencies = [
"serde",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "either"
version = "1.15.0"
@ -330,6 +359,16 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@ -495,6 +534,16 @@ version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "md-5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest",
]
[[package]]
name = "memchr"
version = "2.7.4"
@ -967,6 +1016,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076"
[[package]]
name = "typenum"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "unicode-ident"
version = "1.0.18"
@ -984,6 +1039,15 @@ name = "uuid"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
dependencies = [
"md-5",
]
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasm-bindgen"

View File

@ -1,3 +1,5 @@
cargo-features = ["edition2024"]
[package]
name = "rust_mc_serv"
version = "0.1.0"
@ -15,7 +17,7 @@ palette = "0.7.6"
craftflow-nbt = "2.1.0"
colog = "1.3.0"
log = "0.4.27"
uuid = "1.16.0"
uuid = { version = "1.16.0", features = ["v3"] }
dashmap = "6.1.0"
paste = "1.0.15"
ignore-result = "0.2.0"

View File

@ -46,6 +46,8 @@ rust_mc_serv = { git = "https://github.com/GIKExe/rust_mc_serv.git" }
let config = Arc::new(Config::default());
let mut server = ServerContext::new(config);
server.add_packet_handler(Box::new(PlayHandler)); // Добавляем дефолтную обработку режима Play
server.add_listener(Box::new(ExampleListener)); // Добавляем пример листенера
server.add_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера

View File

@ -2,7 +2,7 @@
# Использование:
#
# ./parse_ids.py < Packets.html > src/server/protocol/id.rs
# ./parse_ids.py < Packets.html > src/server/protocol/packet_id.rs
import sys
from bs4 import BeautifulSoup

View File

@ -1,4 +1,3 @@
with import <nixpkgs> { };
mkShell {
@ -11,6 +10,4 @@ mkShell {
python3Packages.beautifulsoup4
python3Packages.requests
];
NIX_ENFORCE_PURITY = true;
}

View File

@ -1,4 +1,7 @@
use std::{net::SocketAddr, sync::Arc};
use std::{
net::SocketAddr,
sync::{Arc, atomic::AtomicI32},
};
use dashmap::DashMap;
use itertools::Itertools;
@ -15,6 +18,7 @@ use super::{
pub struct ServerContext {
pub config: Arc<Config>,
pub clients: DashMap<SocketAddr, Arc<ClientContext>>,
pub world: WorldContext,
listeners: Vec<Box<dyn Listener>>,
handlers: Vec<Box<dyn PacketHandler>>,
}
@ -26,6 +30,7 @@ impl ServerContext {
listeners: Vec::new(),
handlers: Vec::new(),
clients: DashMap::new(),
world: WorldContext::new(),
}
}
@ -92,3 +97,15 @@ impl ServerContext {
self.listeners.iter().sorted_by_key(sort_by).collect_vec()
}
}
pub struct WorldContext {
pub entity_id_counter: AtomicI32,
}
impl WorldContext {
pub fn new() -> WorldContext {
WorldContext {
entity_id_counter: AtomicI32::new(0),
}
}
}

View File

@ -58,7 +58,9 @@ impl TextComponent {
.collect::<Vec<TextComponent>>();
let mut parent = children[0].clone();
parent.extra = Some(children[1..].to_vec());
if children.len() > 1 {
parent.extra = Some(children[1..].to_vec());
}
parent
}

View File

@ -11,6 +11,7 @@ pub mod config;
pub mod context;
pub mod data;
pub mod event;
pub mod play;
pub mod player;
pub mod protocol;

View File

@ -8,6 +8,7 @@ use rust_mc_serv::{
context::ServerContext,
data::text_component::TextComponent,
event::{Listener, PacketHandler},
play::PlayHandler,
player::context::ClientContext,
protocol::ConnectionState,
start_server,
@ -153,6 +154,8 @@ fn main() {
// Передается во все подключения
let mut server = ServerContext::new(config);
server.add_packet_handler(Box::new(PlayHandler)); // Добавляем дефолтную обработку режима Play
server.add_listener(Box::new(ExampleListener)); // Добавляем пример листенера
server.add_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера

51
src/play/config.rs Normal file
View File

@ -0,0 +1,51 @@
use std::io::Cursor;
use std::sync::Arc;
use rust_mc_proto::{DataWriter, Packet, read_packet};
use crate::protocol::packet_id::*;
use crate::{ServerError, player::context::ClientContext};
pub fn send_update_tags(client: Arc<ClientContext>) -> Result<(), ServerError> {
// TODO: rewrite this hardcode bullshit
client.write_packet(&Packet::from_bytes(
clientbound::configuration::UPDATE_TAGS,
include_bytes!("update-tags.bin"),
))
}
pub fn send_registry_data(client: Arc<ClientContext>) -> Result<(), ServerError> {
// TODO: rewrite this hardcode bullshit
let mut registry_data = Cursor::new(include_bytes!("registry-data.bin"));
while let Ok(mut packet) = read_packet(&mut registry_data, None) {
packet.set_id(clientbound::configuration::REGISTRY_DATA);
client.write_packet(&packet)?;
}
Ok(())
}
// Добавки в Configuration стейт чтобы все работало
pub fn handle_configuration_state(
client: Arc<ClientContext>, // Контекст клиента
) -> Result<(), ServerError> {
let mut packet = Packet::empty(clientbound::configuration::FEATURE_FLAGS);
packet.write_varint(1)?;
packet.write_string("minecraft:vanilla")?;
client.write_packet(&packet)?;
let mut packet = Packet::empty(clientbound::configuration::KNOWN_PACKS);
packet.write_varint(1)?;
packet.write_string("minecraft")?;
packet.write_string("core")?;
packet.write_string("1.21.5")?;
client.write_packet(&packet)?;
client.read_packet(&[serverbound::configuration::KNOWN_PACKS])?;
send_registry_data(client.clone())?;
send_update_tags(client.clone())
}

109
src/play/helper.rs Normal file
View File

@ -0,0 +1,109 @@
use std::{
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
};
use rust_mc_proto::{DataReader, DataWriter, Packet};
use crate::{
ServerError,
data::{ReadWriteNBT, text_component::TextComponent},
player::context::ClientContext,
protocol::packet_id::{clientbound, serverbound},
};
pub fn send_game_event(
client: Arc<ClientContext>,
event: u8,
value: f32,
) -> Result<(), ServerError> {
let mut packet = Packet::empty(clientbound::play::GAME_EVENT);
packet.write_byte(event)?;
packet.write_float(value)?;
client.write_packet(&packet)
}
pub fn sync_player_pos(
client: Arc<ClientContext>,
x: f64,
y: f64,
z: f64,
vel_x: f64,
vel_y: f64,
vel_z: f64,
yaw: f32,
pitch: f32,
flags: i32,
) -> Result<(), ServerError> {
let timestamp = (SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis()
& 0xFFFFFFFF) as i32;
let mut packet = Packet::empty(clientbound::play::SYNCHRONIZE_PLAYER_POSITION);
packet.write_varint(timestamp)?;
packet.write_double(x)?;
packet.write_double(y)?;
packet.write_double(z)?;
packet.write_double(vel_x)?;
packet.write_double(vel_y)?;
packet.write_double(vel_z)?;
packet.write_float(yaw)?;
packet.write_float(pitch)?;
packet.write_int(flags)?;
client.write_packet(&packet)?;
Ok(())
}
pub fn set_center_chunk(client: Arc<ClientContext>, x: i32, z: i32) -> Result<(), ServerError> {
let mut packet = Packet::empty(clientbound::play::SET_CENTER_CHUNK);
packet.write_varint(x)?;
packet.write_varint(z)?;
client.write_packet(&packet)
}
pub fn send_keep_alive(client: Arc<ClientContext>) -> Result<(), ServerError> {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let mut packet = Packet::empty(clientbound::play::KEEP_ALIVE);
packet.write_long(timestamp)?;
client.write_packet(&packet)?;
let mut packet = client.read_packet(&[serverbound::play::KEEP_ALIVE])?;
let timestamp2 = packet.read_long()?;
if timestamp2 != timestamp {
// Послать клиента нахуй
Err(ServerError::WrongPacket)
} else {
Ok(())
}
}
pub fn send_system_message(
client: Arc<ClientContext>,
message: TextComponent,
is_action_bar: bool,
) -> Result<(), ServerError> {
let mut packet = Packet::empty(clientbound::play::SYSTEM_CHAT_MESSAGE);
packet.write_nbt(&message)?;
packet.write_boolean(is_action_bar)?;
client.write_packet(&packet)
}
pub fn unload_chunk(client: Arc<ClientContext>, x: i32, z: i32) -> Result<(), ServerError> {
let mut packet = Packet::empty(clientbound::play::UNLOAD_CHUNK);
packet.write_int(z)?;
packet.write_int(x)?;
client.write_packet(&packet)
}

453
src/play/mod.rs Normal file
View File

@ -0,0 +1,453 @@
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> {
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 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
));
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
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_system_message(
client.clone(),
TextComponent::rainbow(format!("Your Name: {}", client.player_info().unwrap().name)),
false,
)?;
send_system_message(
client.clone(),
TextComponent::rainbow(format!(
"Your Entity ID: {}",
client.entity_info().entity_id
)),
false,
)?;
send_system_message(
client.clone(),
TextComponent::rainbow(format!("Your UUID: {}", client.entity_info().uuid)),
false,
)?;
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,
])?;
match packet.id() {
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(())
}

1
src/play/planner.rs Normal file
View File

@ -0,0 +1 @@
// TODO: thread-safe planner like BukkitScheduler

View File

@ -29,9 +29,7 @@ pub struct ClientContext {
packet_buffer: Mutex<VecDeque<Packet>>,
read_loop: AtomicBool,
is_alive: AtomicBool,
position: RwLock<(f64, f64, f64)>,
velocity: RwLock<(f64, f64, f64)>,
rotation: RwLock<(f32, f32)>,
entity_info: RwLock<Option<Arc<EntityInfo>>>,
}
// Реализуем сравнение через адрес
@ -63,9 +61,7 @@ impl ClientContext {
packet_buffer: Mutex::new(VecDeque::new()),
read_loop: AtomicBool::new(false),
is_alive: AtomicBool::new(true),
position: RwLock::new((0.0, 0.0, 0.0)),
velocity: RwLock::new((0.0, 0.0, 0.0)),
rotation: RwLock::new((0.0, 0.0)),
entity_info: RwLock::new(None),
}
}
@ -81,6 +77,10 @@ impl ClientContext {
*self.player_info.write().unwrap() = Some(player_info);
}
pub fn set_entity_info(self: &Arc<Self>, entity_info: EntityInfo) {
*self.entity_info.write().unwrap() = Some(Arc::new(entity_info));
}
pub fn set_state(self: &Arc<Self>, state: ConnectionState) -> Result<(), ServerError> {
*self.state.write().unwrap() = state.clone();
@ -107,34 +107,14 @@ impl ClientContext {
self.player_info.read().unwrap().clone()
}
pub fn entity_info(self: &Arc<Self>) -> Arc<EntityInfo> {
self.entity_info.read().unwrap().clone().unwrap()
}
pub fn state(self: &Arc<Self>) -> ConnectionState {
self.state.read().unwrap().clone()
}
pub fn set_position(self: &Arc<Self>, position: (f64, f64, f64)) {
*self.position.write().unwrap() = position;
}
pub fn set_velocity(self: &Arc<Self>, velocity: (f64, f64, f64)) {
*self.velocity.write().unwrap() = velocity;
}
pub fn set_rotation(self: &Arc<Self>, rotation: (f32, f32)) {
*self.rotation.write().unwrap() = rotation;
}
pub fn position(self: &Arc<Self>) -> (f64, f64, f64) {
self.position.read().unwrap().clone()
}
pub fn velocity(self: &Arc<Self>) -> (f64, f64, f64) {
self.velocity.read().unwrap().clone()
}
pub fn rotation(self: &Arc<Self>) -> (f32, f32) {
self.rotation.read().unwrap().clone()
}
pub fn write_packet(self: &Arc<Self>, packet: &Packet) -> Result<(), ServerError> {
let state = self.state();
let mut packet = packet.clone();
@ -304,3 +284,47 @@ pub struct PlayerInfo {
pub name: String,
pub uuid: Uuid,
}
pub struct EntityInfo {
pub entity_id: i32,
pub uuid: Uuid,
position: RwLock<(f64, f64, f64)>,
velocity: RwLock<(f64, f64, f64)>,
rotation: RwLock<(f32, f32)>,
}
impl EntityInfo {
pub fn new(entity_id: i32, uuid: Uuid) -> EntityInfo {
EntityInfo {
entity_id,
uuid,
position: RwLock::new((0.0, 0.0, 0.0)),
velocity: RwLock::new((0.0, 0.0, 0.0)),
rotation: RwLock::new((0.0, 0.0)),
}
}
pub fn set_position(self: &Arc<Self>, position: (f64, f64, f64)) {
*self.position.write().unwrap() = position;
}
pub fn set_velocity(self: &Arc<Self>, velocity: (f64, f64, f64)) {
*self.velocity.write().unwrap() = velocity;
}
pub fn set_rotation(self: &Arc<Self>, rotation: (f32, f32)) {
*self.rotation.write().unwrap() = rotation;
}
pub fn position(self: &Arc<Self>) -> (f64, f64, f64) {
self.position.read().unwrap().clone()
}
pub fn velocity(self: &Arc<Self>) -> (f64, f64, f64) {
self.velocity.read().unwrap().clone()
}
pub fn rotation(self: &Arc<Self>) -> (f32, f32) {
self.rotation.read().unwrap().clone()
}
}

View File

@ -10,7 +10,7 @@ use crate::{
ServerError,
data::{ReadWriteNBT, text_component::TextComponent},
protocol::{
id::{clientbound, serverbound},
packet_id::{clientbound, serverbound},
*,
},
};

View File

@ -8,12 +8,9 @@ use rust_mc_proto::{DataReader, DataWriter, Packet};
use crate::trigger_event;
use super::{
ConnectionState,
id::*,
play::{handle_configuration_state, handle_play_state},
};
use super::{ConnectionState, packet_id::*};
// TODO: move brand to the config
pub const BRAND: &str = "rust_mc_serv";
pub fn handle_connection(
@ -171,15 +168,15 @@ pub fn handle_connection(
},
)?)?;
handle_configuration_state(client.clone())?;
client.write_packet(&Packet::empty(clientbound::configuration::FINISH))?;
// На этом моменте пакет хандер ловит пакет и перед ним делает свое мракобесие
client.read_packet(&[serverbound::configuration::ACKNOWLEDGE_FINISH])?;
client.set_state(ConnectionState::Play)?; // Мы перешли в режим Play
// Дальше работаем с режимом игры
handle_play_state(client)?;
// Тут работают уже приколы из пакет хандлера
}
_ => {
// Тип подключения не рукопожатный

View File

@ -1,8 +1,7 @@
pub mod handler;
pub mod id;
pub mod play;
pub mod packet_id;
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub enum ConnectionState {
Handshake,
Status,

View File

@ -1,378 +0,0 @@
use std::{
io::Cursor,
sync::Arc,
thread,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use rust_mc_proto::{DataReader, DataWriter, Packet, read_packet};
use crate::{
ServerError,
data::{ReadWriteNBT, text_component::TextComponent},
player::context::ClientContext,
};
use super::id::*;
pub fn send_update_tags(client: Arc<ClientContext>) -> Result<(), ServerError> {
// TODO: rewrite this hardcode bullshit
client.write_packet(&Packet::from_bytes(
clientbound::configuration::UPDATE_TAGS,
include_bytes!("update-tags.bin"),
))
}
pub fn send_registry_data(client: Arc<ClientContext>) -> Result<(), ServerError> {
// TODO: rewrite this hardcode bullshit
let mut registry_data = Cursor::new(include_bytes!("registry-data.bin"));
while let Ok(mut packet) = read_packet(&mut registry_data, None) {
packet.set_id(clientbound::configuration::REGISTRY_DATA);
client.write_packet(&packet)?;
}
Ok(())
}
// Добавки в Configuration стейт чтобы все работало
pub fn handle_configuration_state(
client: Arc<ClientContext>, // Контекст клиента
) -> Result<(), ServerError> {
let mut packet = Packet::empty(clientbound::configuration::FEATURE_FLAGS);
packet.write_varint(1)?;
packet.write_string("minecraft:vanilla")?;
client.write_packet(&packet)?;
let mut packet = Packet::empty(clientbound::configuration::KNOWN_PACKS);
packet.write_varint(1)?;
packet.write_string("minecraft")?;
packet.write_string("core")?;
packet.write_string("1.21.5")?;
client.write_packet(&packet)?;
client.read_packet(&[serverbound::configuration::KNOWN_PACKS])?;
send_registry_data(client.clone())?;
send_update_tags(client.clone())
}
pub fn send_login(client: Arc<ClientContext>) -> Result<(), ServerError> {
// Отправка пакета Login
let mut packet = Packet::empty(clientbound::play::LOGIN);
packet.write_int(0)?; // 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_game_event(
client: Arc<ClientContext>,
event: u8,
value: f32,
) -> Result<(), ServerError> {
let mut packet = Packet::empty(clientbound::play::GAME_EVENT);
packet.write_byte(event)?;
packet.write_float(value)?;
client.write_packet(&packet)
}
pub fn sync_player_pos(
client: Arc<ClientContext>,
x: f64,
y: f64,
z: f64,
vel_x: f64,
vel_y: f64,
vel_z: f64,
yaw: f32,
pitch: f32,
flags: i32,
) -> Result<(), ServerError> {
let timestamp = (SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis()
& 0xFFFFFFFF) as i32;
let mut packet = Packet::empty(clientbound::play::SYNCHRONIZE_PLAYER_POSITION);
packet.write_varint(timestamp)?;
packet.write_double(x)?;
packet.write_double(y)?;
packet.write_double(z)?;
packet.write_double(vel_x)?;
packet.write_double(vel_y)?;
packet.write_double(vel_z)?;
packet.write_float(yaw)?;
packet.write_float(pitch)?;
packet.write_int(flags)?;
client.write_packet(&packet)?;
Ok(())
}
pub fn set_center_chunk(client: Arc<ClientContext>, x: i32, z: i32) -> Result<(), ServerError> {
let mut packet = Packet::empty(clientbound::play::SET_CENTER_CHUNK);
packet.write_varint(x)?;
packet.write_varint(z)?;
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
packet.write_varint(256)?; // Length of the following long array (16 * 16 = 256)
for _ in 0..256 {
packet.write_long(0)?; // height - 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)
// 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
}
// 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_keep_alive(client: Arc<ClientContext>) -> Result<(), ServerError> {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let mut packet = Packet::empty(clientbound::play::KEEP_ALIVE);
packet.write_long(timestamp)?;
client.write_packet(&packet)?;
let mut packet = client.read_packet(&[serverbound::play::KEEP_ALIVE])?;
let timestamp2 = packet.read_long()?;
if timestamp2 != timestamp {
// Послать клиента нахуй
Err(ServerError::WrongPacket)
} else {
Ok(())
}
}
pub fn send_system_message(
client: Arc<ClientContext>,
message: TextComponent,
is_action_bar: bool,
) -> Result<(), ServerError> {
let mut packet = Packet::empty(clientbound::play::SYSTEM_CHAT_MESSAGE);
packet.write_nbt(&message)?;
packet.write_boolean(is_action_bar)?;
client.write_packet(&packet)
}
pub fn send_example_chunks_in_distance(
client: Arc<ClientContext>,
chunks: &mut Vec<(i32, i32)>,
distance: i32,
center: (i32, i32),
) -> Result<(), ServerError> {
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)?;
chunks.push((x, z));
}
}
}
Ok(())
}
// Отдельная функция для работы с самой игрой
pub fn handle_play_state(
client: Arc<ClientContext>, // Контекст клиента
) -> Result<(), ServerError> {
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;
send_example_chunks_in_distance(client.clone(), &mut chunks, view_distance, (0, 0))?;
thread::spawn({
let client = client.clone();
move || -> Result<(), ServerError> {
while client.is_alive() {
let mut packet = client.read_any_packet()?;
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.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.set_position((x, y, z));
client.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.set_rotation((yaw, pitch));
}
_ => {
client.push_packet_back(packet);
}
}
}
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, y, z) = client.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),
)?;
send_system_message(
client.clone(),
TextComponent::rainbow(format!("Pos: {} {} {}", x as i64, y as i64, z as i64)),
false,
)?;
}
send_system_message(
client.clone(),
TextComponent::rainbow(format!("Ticks alive: {}", ticks_alive)),
true,
)?;
thread::sleep(Duration::from_millis(50)); // 1 tick
ticks_alive += 1;
}
Ok(())
}