mirror of
https://github.com/GIKExe/rust_mc_serv.git
synced 2025-06-24 10:22:57 +03:00
Compare commits
15 Commits
a746a0d9b4
...
a1b0da37d9
Author | SHA1 | Date | |
---|---|---|---|
a1b0da37d9 | |||
424b33d47c | |||
abb0e019af | |||
4df4f2ec91 | |||
8081720ea6 | |||
caea03c0c7 | |||
6dcb061271 | |||
03b1a25c06 | |||
32e7737131 | |||
03a23eb267 | |||
9d6c0cd04d | |||
7f6bc59d14 | |||
dc02f22545 | |||
934ca660c4 | |||
0266958443 |
@ -1,5 +1,3 @@
|
||||
cargo-features = ["edition2024"]
|
||||
|
||||
[package]
|
||||
name = "rust_mc_serv"
|
||||
version = "0.1.0"
|
||||
|
@ -46,7 +46,9 @@ 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
|
||||
// Добавляем дефолтную обработку режима Play
|
||||
server.add_packet_handler(Box::new(PlayHandler));
|
||||
server.add_listener(Box::new(PlayListener));
|
||||
|
||||
server.add_listener(Box::new(ExampleListener)); // Добавляем пример листенера
|
||||
server.add_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера
|
||||
|
@ -38,6 +38,7 @@ 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 {
|
||||
@ -53,6 +54,7 @@ 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 {
|
||||
@ -69,6 +71,7 @@ impl ServerContext {
|
||||
.clients
|
||||
.iter()
|
||||
.filter(|o| o.player_info().is_some())
|
||||
.filter(|o| o.entity_info_opt().is_some())
|
||||
.map(|o| o.clone())
|
||||
.collect()
|
||||
}
|
||||
|
@ -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 {
|
||||
if text.is_empty() {
|
||||
return TextComponent::new(text);
|
@ -5,7 +5,7 @@ use rust_mc_proto::{DataReader, DataWriter, Packet};
|
||||
|
||||
use super::ServerError;
|
||||
|
||||
pub mod text_component;
|
||||
pub mod component;
|
||||
|
||||
// Трейт для чтения NBT-совместимых приколов
|
||||
pub trait ReadWriteNBT<T>: DataReader + DataWriter {
|
||||
|
@ -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 {
|
||||
generate_handlers!(status, &mut String);
|
||||
generate_handlers!(plugin_message, &str, &[u8]);
|
||||
generate_handlers!(disconnect);
|
||||
}
|
||||
|
||||
pub trait PacketHandler: Sync + Send {
|
||||
|
@ -98,6 +98,8 @@ pub fn start_server(server: Arc<ServerContext>) {
|
||||
}
|
||||
};
|
||||
|
||||
trigger_event_ignore!(client, disconnect);
|
||||
|
||||
// Удаляем клиента из списка клиентов
|
||||
server.clients.remove(&client.addr);
|
||||
|
||||
|
@ -6,9 +6,9 @@ use rust_mc_serv::{
|
||||
ServerError,
|
||||
config::Config,
|
||||
context::ServerContext,
|
||||
data::text_component::TextComponent,
|
||||
data::component::TextComponent,
|
||||
event::{Listener, PacketHandler},
|
||||
play::PlayHandler,
|
||||
play::{PlayHandler, PlayListener},
|
||||
player::context::ClientContext,
|
||||
protocol::ConnectionState,
|
||||
start_server,
|
||||
@ -154,7 +154,9 @@ fn main() {
|
||||
// Передается во все подключения
|
||||
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_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера
|
||||
|
@ -7,7 +7,7 @@ use rust_mc_proto::{DataReader, DataWriter, Packet};
|
||||
|
||||
use crate::{
|
||||
ServerError,
|
||||
data::{ReadWriteNBT, text_component::TextComponent},
|
||||
data::{ReadWriteNBT, component::TextComponent},
|
||||
player::context::ClientContext,
|
||||
protocol::packet_id::{clientbound, serverbound},
|
||||
};
|
||||
@ -25,6 +25,19 @@ 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,
|
||||
|
181
src/play/mod.rs
181
src/play/mod.rs
@ -3,16 +3,16 @@ 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,
|
||||
send_entity_event, 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::text_component::TextComponent, event::PacketHandler,
|
||||
player::context::ClientContext,
|
||||
ServerError, data::component::TextComponent, event::PacketHandler, player::context::ClientContext,
|
||||
};
|
||||
|
||||
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> {
|
||||
// Отправка пакета 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
|
||||
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
|
||||
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(())
|
||||
}
|
||||
|
||||
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>,
|
||||
@ -239,18 +268,26 @@ 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> {
|
||||
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
|
||||
));
|
||||
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
|
||||
|
||||
@ -266,6 +303,8 @@ 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();
|
||||
@ -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)?;
|
||||
|
||||
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,
|
||||
)?;
|
||||
// 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 {
|
||||
@ -301,6 +334,7 @@ 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({
|
||||
@ -312,9 +346,57 @@ 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()?;
|
||||
@ -433,15 +515,22 @@ pub fn handle_play_state(
|
||||
|
||||
// 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 ticks_alive > 40 {
|
||||
let animation_text = format!(
|
||||
"жёпа .-.-.-.-.-.- Ticks passed during the aliveness of the connection: {} ticks (1/20 of second) -.-.-.-.-.-. жёпа",
|
||||
ticks_alive
|
||||
);
|
||||
|
||||
if animation_index < animation_end {
|
||||
let now_length = (animation_index + 1).min(animation_text.chars().count());
|
||||
let now_length = ((ticks_alive - 40 + 1) as usize).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)?;
|
||||
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(())
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
@ -107,6 +107,10 @@ 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()
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use rust_mc_proto::{DataReader, DataWriter, Packet};
|
||||
|
||||
use crate::{
|
||||
ServerError,
|
||||
data::{ReadWriteNBT, text_component::TextComponent},
|
||||
data::{ReadWriteNBT, component::TextComponent},
|
||||
protocol::{
|
||||
packet_id::{clientbound, serverbound},
|
||||
*,
|
||||
|
@ -1,4 +1,7 @@
|
||||
use std::{io::Read, sync::Arc};
|
||||
use std::{
|
||||
io::{Cursor, Read},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ServerError,
|
||||
@ -129,7 +132,7 @@ pub fn handle_connection(
|
||||
packet.get_mut().read_to_end(&mut data).unwrap();
|
||||
|
||||
if identifier == "minecraft:brand" {
|
||||
break String::from_utf8_lossy(&data).to_string();
|
||||
break Cursor::new(data).read_string()?;
|
||||
} else {
|
||||
trigger_event!(client, plugin_message, &identifier, &data);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user