Compare commits

..

15 Commits

Author SHA1 Message Date
a1b0da37d9 left the game message fix 2025-05-08 00:54:15 +03:00
424b33d47c connect disconnect mesages 2025-05-08 00:53:47 +03:00
abb0e019af beatiful ticks hint 2025-05-08 00:44:46 +03:00
4df4f2ec91 give op level 2025-05-08 00:24:21 +03:00
8081720ea6 gamemode command 2025-05-08 00:04:57 +03:00
caea03c0c7 remoe player on disconnect 2025-05-07 23:51:48 +03:00
6dcb061271 make floor stone 2025-05-07 23:44:51 +03:00
03b1a25c06 good comment 2025-05-07 23:38:48 +03:00
32e7737131 fix some stuff 2025-05-07 23:36:35 +03:00
03a23eb267 on_disconnect 2025-05-07 21:36:47 +03:00
9d6c0cd04d set creative and diamond blocks 2025-05-07 21:30:02 +03:00
7f6bc59d14 fix players find 2025-05-07 19:39:12 +03:00
dc02f22545 chat message 2025-05-07 19:27:08 +03:00
934ca660c4 fix brand reading and more debug messages 2025-05-07 18:54:00 +03:00
0266958443 remove cargo features 2025-05-07 18:28:03 +03:00
13 changed files with 229 additions and 48 deletions

View File

@ -1,5 +1,3 @@
cargo-features = ["edition2024"]
[package] [package]
name = "rust_mc_serv" name = "rust_mc_serv"
version = "0.1.0" version = "0.1.0"

View File

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

View File

@ -38,6 +38,7 @@ impl ServerContext {
self self
.clients .clients
.iter() .iter()
.filter(|o| o.entity_info_opt().is_some())
.find(|o| { .find(|o| {
let info = o.player_info(); let info = o.player_info();
if let Some(info) = info { if let Some(info) = info {
@ -53,6 +54,7 @@ impl ServerContext {
self self
.clients .clients
.iter() .iter()
.filter(|o| o.entity_info_opt().is_some())
.find(|o| { .find(|o| {
let info = o.player_info(); let info = o.player_info();
if let Some(info) = info { if let Some(info) = info {
@ -69,6 +71,7 @@ impl ServerContext {
.clients .clients
.iter() .iter()
.filter(|o| o.player_info().is_some()) .filter(|o| o.player_info().is_some())
.filter(|o| o.entity_info_opt().is_some())
.map(|o| o.clone()) .map(|o| o.clone())
.collect() .collect()
} }

View File

@ -37,6 +37,35 @@ impl TextComponent {
} }
} }
pub fn rainbow_offset(text: String, offset: i64) -> TextComponent {
if text.is_empty() {
return TextComponent::new(text);
}
let children = text
.char_indices()
.map(|(i, c)| {
let hue = (((i as i64 + offset) % text.chars().count() as i64) as f32)
/ (text.chars().count() as f32)
* 360.0;
let hsl = Hsl::new(hue, 1.0, 0.5);
let rgb: Srgb = hsl.into_color();
let r = (rgb.red * 255.0).round() as u8;
let g = (rgb.green * 255.0).round() as u8;
let b = (rgb.blue * 255.0).round() as u8;
let mut component = TextComponent::new(c.to_string());
component.color = Some(format!("#{:02X}{:02X}{:02X}", r, g, b));
component
})
.collect::<Vec<TextComponent>>();
let mut parent = children[0].clone();
if children.len() > 1 {
parent.extra = Some(children[1..].to_vec());
}
parent
}
pub fn rainbow(text: String) -> TextComponent { pub fn rainbow(text: String) -> TextComponent {
if text.is_empty() { if text.is_empty() {
return TextComponent::new(text); return TextComponent::new(text);

View File

@ -5,7 +5,7 @@ use rust_mc_proto::{DataReader, DataWriter, Packet};
use super::ServerError; use super::ServerError;
pub mod text_component; pub mod component;
// Трейт для чтения NBT-совместимых приколов // Трейт для чтения NBT-совместимых приколов
pub trait ReadWriteNBT<T>: DataReader + DataWriter { pub trait ReadWriteNBT<T>: DataReader + DataWriter {

View File

@ -37,9 +37,27 @@ macro_rules! trigger_event {
}}; }};
} }
/// Игнорирует результат листенеров
#[macro_export]
macro_rules! trigger_event_ignore {
($client:ident, $event:ident $(, $arg_ty:expr)* $(,)?) => {{
paste::paste! {
for handler in $client.server.listeners(
|o| o.[<on_ $event _priority>]()
).iter() {
let _ = handler.[<on_ $event>](
$client.clone()
$(, $arg_ty)*
);
}
}
}};
}
pub trait Listener: Sync + Send { pub trait Listener: Sync + Send {
generate_handlers!(status, &mut String); generate_handlers!(status, &mut String);
generate_handlers!(plugin_message, &str, &[u8]); generate_handlers!(plugin_message, &str, &[u8]);
generate_handlers!(disconnect);
} }
pub trait PacketHandler: Sync + Send { pub trait PacketHandler: Sync + Send {

View File

@ -98,6 +98,8 @@ pub fn start_server(server: Arc<ServerContext>) {
} }
}; };
trigger_event_ignore!(client, disconnect);
// Удаляем клиента из списка клиентов // Удаляем клиента из списка клиентов
server.clients.remove(&client.addr); server.clients.remove(&client.addr);

View File

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

View File

@ -7,7 +7,7 @@ use rust_mc_proto::{DataReader, DataWriter, Packet};
use crate::{ use crate::{
ServerError, ServerError,
data::{ReadWriteNBT, text_component::TextComponent}, data::{ReadWriteNBT, component::TextComponent},
player::context::ClientContext, player::context::ClientContext,
protocol::packet_id::{clientbound, serverbound}, protocol::packet_id::{clientbound, serverbound},
}; };
@ -25,6 +25,19 @@ pub fn send_game_event(
client.write_packet(&packet) client.write_packet(&packet)
} }
pub fn send_entity_event(
client: Arc<ClientContext>,
entity_id: i32,
status: u8,
) -> Result<(), ServerError> {
let mut packet = Packet::empty(clientbound::play::ENTITY_EVENT);
packet.write_int(entity_id)?;
packet.write_byte(status)?;
client.write_packet(&packet)
}
pub fn sync_player_pos( pub fn sync_player_pos(
client: Arc<ClientContext>, client: Arc<ClientContext>,
x: f64, x: f64,

View File

@ -3,16 +3,16 @@ use std::{sync::Arc, thread, time::Duration};
use config::handle_configuration_state; use config::handle_configuration_state;
use helper::{ use helper::{
send_game_event, send_keep_alive, send_system_message, set_center_chunk, sync_player_pos, send_entity_event, send_game_event, send_keep_alive, send_system_message, set_center_chunk,
unload_chunk, sync_player_pos, unload_chunk,
}; };
use rust_mc_proto::{DataReader, DataWriter, Packet}; use rust_mc_proto::{DataReader, DataWriter, Packet};
use uuid::Uuid; use uuid::Uuid;
use crate::event::Listener;
use crate::player::context::EntityInfo; use crate::player::context::EntityInfo;
use crate::{ use crate::{
ServerError, data::text_component::TextComponent, event::PacketHandler, ServerError, data::component::TextComponent, event::PacketHandler, player::context::ClientContext,
player::context::ClientContext,
}; };
use crate::protocol::{ConnectionState, packet_id::*}; use crate::protocol::{ConnectionState, packet_id::*};
@ -56,6 +56,14 @@ impl PacketHandler for PlayHandler {
} }
} }
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> { pub fn send_login(client: Arc<ClientContext>) -> Result<(), ServerError> {
// Отправка пакета Login // Отправка пакета Login
let mut packet = Packet::empty(clientbound::play::LOGIN); let mut packet = Packet::empty(clientbound::play::LOGIN);
@ -119,7 +127,7 @@ pub fn send_example_chunk(client: Arc<ClientContext>, x: i32, z: i32) -> Result<
// blocks paletted container // blocks paletted container
chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format 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) chunk_data.write_varint(1)?; // block state id in the registry
// biomes palleted container // biomes palleted container
chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format
@ -187,6 +195,27 @@ pub fn send_example_chunks_in_distance(
Ok(()) Ok(())
} }
pub fn remove_player(
receiver: Arc<ClientContext>,
player: Arc<ClientContext>,
) -> Result<(), ServerError> {
let mut packet = Packet::empty(clientbound::play::PLAYER_INFO_REMOVE);
packet.write_varint(1)?;
packet.write_uuid(&player.entity_info().uuid)?;
receiver.write_packet(&packet)?;
let mut packet = Packet::empty(clientbound::play::REMOVE_ENTITIES);
packet.write_varint(1)?;
packet.write_varint(player.entity_info().entity_id)?; // Entity ID
receiver.write_packet(&packet)?;
Ok(())
}
pub fn send_player( pub fn send_player(
receiver: Arc<ClientContext>, receiver: Arc<ClientContext>,
player: Arc<ClientContext>, player: Arc<ClientContext>,
@ -239,18 +268,26 @@ pub fn get_offline_uuid(name: &str) -> Uuid {
Uuid::new_v3(&namespace, (&name[2..]).as_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( pub fn handle_play_state(
client: Arc<ClientContext>, // Контекст клиента client: Arc<ClientContext>, // Контекст клиента
) -> Result<(), ServerError> { ) -> Result<(), ServerError> {
client.set_entity_info(EntityInfo::new( let player_name = client.player_info().unwrap().name;
client let player_uuid = get_offline_uuid(&client.player_info().unwrap().name); // TODO: authenticated uuid
.server let entity_id = client
.world .server
.entity_id_counter .world
.fetch_add(1, Ordering::SeqCst), .entity_id_counter
get_offline_uuid(&client.player_info().unwrap().name), // TODO: authenticated uuid .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 client.entity_info().set_position((8.0, 0.0, 8.0)); // set 8 0 8 as position
@ -266,6 +303,8 @@ pub fn handle_play_state(
send_login(client.clone())?; 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)?; 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(), 13, 0.0)?; // 13 - Start waiting for level chunks
// send_game_event(client.clone(), 3, 1.0)?; // 3 - Set gamemode, 1.0 - creative
send_entity_event(client.clone(), entity_id, 28)?; // 28 - give op level 4
set_center_chunk(client.clone(), 0, 0)?; set_center_chunk(client.clone(), 0, 0)?;
let mut chunks = Vec::new(); let mut chunks = Vec::new();
@ -276,24 +315,18 @@ pub fn handle_play_state(
// sync_player_pos(client.clone(), 8.0, 0.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.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( // send_rainbow_message(&client, format!("Your IP: {}", client.addr))?;
client.clone(), // send_rainbow_message(
TextComponent::rainbow(format!("Your Name: {}", client.player_info().unwrap().name)), // &client,
false, // format!("Your brand: {}", client.client_info().unwrap().brand),
)?; // )?;
send_system_message( // send_rainbow_message(
client.clone(), // &client,
TextComponent::rainbow(format!( // format!("Your locale: {}", client.client_info().unwrap().locale),
"Your Entity ID: {}", // )?;
client.entity_info().entity_id // send_rainbow_message(&client, format!("Your UUID: {}", client.entity_info().uuid))?;
)), // send_rainbow_message(&client, format!("Your Name: {}", &player_name))?;
false, // send_rainbow_message(&client, format!("Your Entity ID: {}", entity_id))?;
)?;
send_system_message(
client.clone(),
TextComponent::rainbow(format!("Your UUID: {}", client.entity_info().uuid)),
false,
)?;
for player in client.server.players() { for player in client.server.players() {
if client.addr == player.addr { if client.addr == player.addr {
@ -301,6 +334,7 @@ pub fn handle_play_state(
} }
send_player(client.clone(), player.clone())?; send_player(client.clone(), player.clone())?;
send_player(player.clone(), client.clone())?; send_player(player.clone(), client.clone())?;
send_rainbow_message(&player, format!("{} joined the game", player_name))?;
} }
thread::spawn({ thread::spawn({
@ -312,9 +346,57 @@ pub fn handle_play_state(
serverbound::play::SET_PLAYER_POSITION, serverbound::play::SET_PLAYER_POSITION,
serverbound::play::SET_PLAYER_POSITION_AND_ROTATION, serverbound::play::SET_PLAYER_POSITION_AND_ROTATION,
serverbound::play::SET_PLAYER_ROTATION, serverbound::play::SET_PLAYER_ROTATION,
serverbound::play::CHAT_MESSAGE,
serverbound::play::CLICK_CONTAINER,
serverbound::play::CHAT_COMMAND,
serverbound::play::SIGNED_CHAT_COMMAND,
])?; ])?;
match packet.id() { match packet.id() {
serverbound::play::CLICK_CONTAINER => {
let _ = packet.read_varint()?; // window id
let _ = packet.read_varint()?; // state id
let slot = packet.read_short()?; // slot
let _ = packet.read_byte()?; // button
let _ = packet.read_varint()?; // mode
// i cannot read item slots now
send_rainbow_message(&client, format!("index clicked: {slot}"))?;
}
serverbound::play::CHAT_COMMAND | serverbound::play::SIGNED_CHAT_COMMAND => {
let command = packet.read_string()?;
if command == "gamemode creative" {
send_game_event(client.clone(), 3, 1.0)?; // 3 - Set gamemode
send_rainbow_message(&client, format!("gamemode creative installed"))?;
} else if command == "gamemode survival" {
send_game_event(client.clone(), 3, 0.0)?; // 3 - Set gamemode
send_rainbow_message(&client, format!("gamemode survival installed"))?;
}
}
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 => { serverbound::play::SET_PLAYER_POSITION => {
let x = packet.read_double()?; let x = packet.read_double()?;
let y = packet.read_double()?; let y = packet.read_double()?;
@ -433,15 +515,22 @@ pub fn handle_play_state(
// text animation // text animation
{ {
let animation_text = format!("Ticks alive: {} жёпа", ticks_alive); if ticks_alive > 40 {
let animation_index = ((ticks_alive + 40) % 300) as usize; let animation_text = format!(
let animation_end = animation_text.len() + 20; "жёпа .-.-.-.-.-.- Ticks passed during the aliveness of the connection: {} ticks (1/20 of second) -.-.-.-.-.-. жёпа",
ticks_alive
);
if animation_index < animation_end { let now_length = ((ticks_alive - 40 + 1) as usize).min(animation_text.chars().count());
let now_length = (animation_index + 1).min(animation_text.chars().count());
let now_text = animation_text.chars().take(now_length).collect(); let now_text = animation_text.chars().take(now_length).collect();
send_system_message(client.clone(), TextComponent::rainbow(now_text), true)?; let mut text = TextComponent::rainbow_offset(now_text, -(ticks_alive as i64));
text.bold = Some(true);
text.italic = Some(true);
text.underlined = Some(true);
send_system_message(client.clone(), text, true)?;
} }
} }
@ -451,3 +540,21 @@ pub fn handle_play_state(
Ok(()) Ok(())
} }
pub fn handle_disconnect(
client: Arc<ClientContext>, // Контекст клиента
) -> Result<(), ServerError> {
for player in client.server.players() {
if client.addr == player.addr {
continue;
}
remove_player(player.clone(), client.clone())?;
send_rainbow_message(
&player,
format!("{} left the game", client.player_info().unwrap().name),
)?;
}
Ok(())
}

View File

@ -107,6 +107,10 @@ impl ClientContext {
self.player_info.read().unwrap().clone() self.player_info.read().unwrap().clone()
} }
pub fn entity_info_opt(self: &Arc<Self>) -> Option<Arc<EntityInfo>> {
self.entity_info.read().unwrap().clone()
}
pub fn entity_info(self: &Arc<Self>) -> Arc<EntityInfo> { pub fn entity_info(self: &Arc<Self>) -> Arc<EntityInfo> {
self.entity_info.read().unwrap().clone().unwrap() self.entity_info.read().unwrap().clone().unwrap()
} }

View File

@ -8,7 +8,7 @@ use rust_mc_proto::{DataReader, DataWriter, Packet};
use crate::{ use crate::{
ServerError, ServerError,
data::{ReadWriteNBT, text_component::TextComponent}, data::{ReadWriteNBT, component::TextComponent},
protocol::{ protocol::{
packet_id::{clientbound, serverbound}, packet_id::{clientbound, serverbound},
*, *,

View File

@ -1,4 +1,7 @@
use std::{io::Read, sync::Arc}; use std::{
io::{Cursor, Read},
sync::Arc,
};
use crate::{ use crate::{
ServerError, ServerError,
@ -129,7 +132,7 @@ pub fn handle_connection(
packet.get_mut().read_to_end(&mut data).unwrap(); packet.get_mut().read_to_end(&mut data).unwrap();
if identifier == "minecraft:brand" { if identifier == "minecraft:brand" {
break String::from_utf8_lossy(&data).to_string(); break Cursor::new(data).read_string()?;
} else { } else {
trigger_event!(client, plugin_message, &identifier, &data); trigger_event!(client, plugin_message, &identifier, &data);
} }