Compare commits

..

No commits in common. "a1b0da37d96bbae4a573891e81f118a0dc29f85f" and "a746a0d9b443191c14475852ade59425130dbc1a" have entirely different histories.

13 changed files with 48 additions and 229 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -37,35 +37,6 @@ 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 {
if text.is_empty() {
return TextComponent::new(text);

View File

@ -37,27 +37,9 @@ 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 {
generate_handlers!(status, &mut String);
generate_handlers!(plugin_message, &str, &[u8]);
generate_handlers!(disconnect);
}
pub trait PacketHandler: Sync + Send {

View File

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

View File

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

View File

@ -7,7 +7,7 @@ use rust_mc_proto::{DataReader, DataWriter, Packet};
use crate::{
ServerError,
data::{ReadWriteNBT, component::TextComponent},
data::{ReadWriteNBT, text_component::TextComponent},
player::context::ClientContext,
protocol::packet_id::{clientbound, serverbound},
};
@ -25,19 +25,6 @@ pub fn send_game_event(
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(
client: Arc<ClientContext>,
x: f64,

View File

@ -3,16 +3,16 @@ use std::{sync::Arc, thread, time::Duration};
use config::handle_configuration_state;
use helper::{
send_entity_event, send_game_event, send_keep_alive, send_system_message, set_center_chunk,
sync_player_pos, unload_chunk,
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,
ServerError, data::text_component::TextComponent, event::PacketHandler,
player::context::ClientContext,
};
use crate::protocol::{ConnectionState, packet_id::*};
@ -56,14 +56,6 @@ 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> {
// Отправка пакета Login
let mut packet = Packet::empty(clientbound::play::LOGIN);
@ -127,7 +119,7 @@ pub fn send_example_chunk(client: Arc<ClientContext>, x: i32, z: i32) -> Result<
// blocks paletted container
chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format
chunk_data.write_varint(1)?; // block state id in the registry
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
@ -195,27 +187,6 @@ pub fn send_example_chunks_in_distance(
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(
receiver: Arc<ClientContext>,
player: Arc<ClientContext>,
@ -268,26 +239,18 @@ pub fn get_offline_uuid(name: &str) -> Uuid {
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.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
@ -303,8 +266,6 @@ pub fn handle_play_state(
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
send_entity_event(client.clone(), entity_id, 28)?; // 28 - give op level 4
set_center_chunk(client.clone(), 0, 0)?;
let mut chunks = Vec::new();
@ -315,18 +276,24 @@ 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)?;
// 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))?;
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 {
@ -334,7 +301,6 @@ pub fn handle_play_state(
}
send_player(client.clone(), player.clone())?;
send_player(player.clone(), client.clone())?;
send_rainbow_message(&player, format!("{} joined the game", player_name))?;
}
thread::spawn({
@ -346,57 +312,9 @@ pub fn handle_play_state(
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,
serverbound::play::CHAT_COMMAND,
serverbound::play::SIGNED_CHAT_COMMAND,
])?;
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 => {
let x = packet.read_double()?;
let y = packet.read_double()?;
@ -515,22 +433,15 @@ pub fn handle_play_state(
// text animation
{
if ticks_alive > 40 {
let animation_text = format!(
"жёпа .-.-.-.-.-.- Ticks passed during the aliveness of the connection: {} ticks (1/20 of second) -.-.-.-.-.-. жёпа",
ticks_alive
);
let animation_text = format!("Ticks alive: {} жёпа", ticks_alive);
let animation_index = ((ticks_alive + 40) % 300) as usize;
let animation_end = animation_text.len() + 20;
let now_length = ((ticks_alive - 40 + 1) as usize).min(animation_text.chars().count());
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();
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)?;
send_system_message(client.clone(), TextComponent::rainbow(now_text), true)?;
}
}
@ -540,21 +451,3 @@ pub fn handle_play_state(
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,10 +107,6 @@ impl ClientContext {
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> {
self.entity_info.read().unwrap().clone().unwrap()
}

View File

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

View File

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