diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..5c5187748 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +# A whitelist of files that should be included into docker +# Put an exclaimation mark before everything to include + +# Ignore everything +* + +# Allow the source code folders +!/pumpkin*/ + +# Dependencies +!Cargo.lock +!Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index d01e7e507..6e165f7d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,6 +154,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "cipher" version = "0.4.4" @@ -267,6 +273,16 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "ctrlc" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" +dependencies = [ + "nix", + "windows-sys 0.59.0", +] + [[package]] name = "der" version = "0.7.9" @@ -823,6 +839,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -1066,6 +1094,7 @@ dependencies = [ "base64", "bytes", "crossbeam-channel", + "ctrlc", "dhat", "digest 0.11.0-pre.9", "hmac", @@ -2065,6 +2094,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..69631ab81 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM rust:1-alpine3.19 AS builder +ENV RUSTFLAGS="-C target-feature=-crt-static -C target-cpu=native" +RUN apk add --no-cache musl-dev +WORKDIR /pumpkin +COPY . /pumpkin +RUN cargo build --release +RUN strip target/release/pumpkin + +FROM alpine:3.19 +WORKDIR /pumpkin +RUN apk add --no-cache libgcc +COPY --from=builder /pumpkin/target/release/pumpkin /pumpkin/pumpkin +ENTRYPOINT ["/pumpkin/pumpkin"] diff --git a/README.md b/README.md index b0c341e70..32f57450d 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,21 @@ Then run: RUSTFLAGS="-C target-cpu=native" cargo run --release ``` +### Docker + +Experimental Docker support is available. +The image is currently not published anywhere, but you can use the following command to build it: + +```shell +docker build . -t pumpkin +``` + +To run it use the following command: + +```shell +docker run --rm -v "./world:/pumpkin/world" pumpkin +``` + ## Contributions Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) diff --git a/pumpkin-protocol/src/client/play/c_entity_velocity.rs b/pumpkin-protocol/src/client/play/c_entity_velocity.rs index 842fc1cda..fcfdccd59 100644 --- a/pumpkin-protocol/src/client/play/c_entity_velocity.rs +++ b/pumpkin-protocol/src/client/play/c_entity_velocity.rs @@ -6,16 +6,16 @@ use crate::VarInt; #[derive(Serialize)] #[packet(0x5A)] pub struct CEntityVelocity<'a> { - entitiy_id: &'a VarInt, + entity_id: &'a VarInt, velocity_x: i16, velocity_y: i16, velocity_z: i16, } impl<'a> CEntityVelocity<'a> { - pub fn new(entitiy_id: &'a VarInt, velocity_x: f32, velocity_y: f32, velocity_z: f32) -> Self { + pub fn new(entity_id: &'a VarInt, velocity_x: f32, velocity_y: f32, velocity_z: f32) -> Self { Self { - entitiy_id, + entity_id, velocity_x: (velocity_x.clamp(-3.9, 3.9) * 8000.0) as i16, velocity_y: (velocity_y.clamp(-3.9, 3.9) * 8000.0) as i16, velocity_z: (velocity_z.clamp(-3.9, 3.9) * 8000.0) as i16, diff --git a/pumpkin-protocol/src/client/play/c_hurt_animation.rs b/pumpkin-protocol/src/client/play/c_hurt_animation.rs index 6166acbd3..2b1b04d8f 100644 --- a/pumpkin-protocol/src/client/play/c_hurt_animation.rs +++ b/pumpkin-protocol/src/client/play/c_hurt_animation.rs @@ -6,12 +6,12 @@ use crate::VarInt; #[derive(Serialize)] #[packet(0x24)] pub struct CHurtAnimation<'a> { - entitiy_id: &'a VarInt, + entity_id: &'a VarInt, yaw: f32, } impl<'a> CHurtAnimation<'a> { - pub fn new(entitiy_id: &'a VarInt, yaw: f32) -> Self { - Self { entitiy_id, yaw } + pub fn new(entity_id: &'a VarInt, yaw: f32) -> Self { + Self { entity_id, yaw } } } diff --git a/pumpkin-protocol/src/client/play/c_remove_entities.rs b/pumpkin-protocol/src/client/play/c_remove_entities.rs index 9e89ec260..1b16ad92e 100644 --- a/pumpkin-protocol/src/client/play/c_remove_entities.rs +++ b/pumpkin-protocol/src/client/play/c_remove_entities.rs @@ -7,14 +7,14 @@ use crate::VarInt; #[packet(0x42)] pub struct CRemoveEntities<'a> { count: VarInt, - entitiy_ids: &'a [VarInt], + entity_ids: &'a [VarInt], } impl<'a> CRemoveEntities<'a> { - pub fn new(entitiy_ids: &'a [VarInt]) -> Self { + pub fn new(entity_ids: &'a [VarInt]) -> Self { Self { - count: VarInt(entitiy_ids.len() as i32), - entitiy_ids, + count: VarInt(entity_ids.len() as i32), + entity_ids, } } } diff --git a/pumpkin-protocol/src/client/play/c_sync_player_position.rs b/pumpkin-protocol/src/client/play/c_sync_player_position.rs index 056214281..422160ad2 100644 --- a/pumpkin-protocol/src/client/play/c_sync_player_position.rs +++ b/pumpkin-protocol/src/client/play/c_sync_player_position.rs @@ -5,7 +5,7 @@ use crate::VarInt; #[derive(Serialize)] #[packet(0x40)] -pub struct CSyncPlayerPostion { +pub struct CSyncPlayerPosition { x: f64, y: f64, z: f64, @@ -15,7 +15,7 @@ pub struct CSyncPlayerPostion { teleport_id: VarInt, } -impl CSyncPlayerPostion { +impl CSyncPlayerPosition { pub fn new( x: f64, y: f64, diff --git a/pumpkin-protocol/src/client/play/c_system_chat_message.rs b/pumpkin-protocol/src/client/play/c_system_chat_message.rs index a0f2342d1..2751b40c7 100644 --- a/pumpkin-protocol/src/client/play/c_system_chat_message.rs +++ b/pumpkin-protocol/src/client/play/c_system_chat_message.rs @@ -4,12 +4,12 @@ use serde::Serialize; #[derive(Serialize)] #[packet(0x6C)] -pub struct CSystemChatMessge<'a> { +pub struct CSystemChatMessage<'a> { content: TextComponent<'a>, overlay: bool, } -impl<'a> CSystemChatMessge<'a> { +impl<'a> CSystemChatMessage<'a> { pub fn new(content: TextComponent<'a>, overlay: bool) -> Self { Self { content, overlay } } diff --git a/pumpkin-protocol/src/client/play/c_update_entitiy_pos_rot.rs b/pumpkin-protocol/src/client/play/c_update_entity_pos_rot.rs similarity index 100% rename from pumpkin-protocol/src/client/play/c_update_entitiy_pos_rot.rs rename to pumpkin-protocol/src/client/play/c_update_entity_pos_rot.rs diff --git a/pumpkin-protocol/src/client/play/mod.rs b/pumpkin-protocol/src/client/play/mod.rs index 8d4a6e84b..c64720948 100644 --- a/pumpkin-protocol/src/client/play/mod.rs +++ b/pumpkin-protocol/src/client/play/mod.rs @@ -33,8 +33,8 @@ mod c_subtitle; mod c_sync_player_position; mod c_system_chat_message; mod c_unload_chunk; -mod c_update_entitiy_pos_rot; mod c_update_entity_pos; +mod c_update_entity_pos_rot; mod c_update_entity_rot; mod c_worldevent; mod player_action; @@ -74,8 +74,8 @@ pub use c_subtitle::*; pub use c_sync_player_position::*; pub use c_system_chat_message::*; pub use c_unload_chunk::*; -pub use c_update_entitiy_pos_rot::*; pub use c_update_entity_pos::*; +pub use c_update_entity_pos_rot::*; pub use c_update_entity_rot::*; pub use c_worldevent::*; pub use player_action::*; diff --git a/pumpkin-protocol/src/lib.rs b/pumpkin-protocol/src/lib.rs index f44a0a811..e79388163 100644 --- a/pumpkin-protocol/src/lib.rs +++ b/pumpkin-protocol/src/lib.rs @@ -143,7 +143,7 @@ pub enum PacketError { #[error("packet length is out of bounds")] OutOfBounds, #[error("malformed packet length VarInt")] - MailformedLength, + MalformedLength, } #[derive(Debug, PartialEq)] diff --git a/pumpkin-protocol/src/packet_decoder.rs b/pumpkin-protocol/src/packet_decoder.rs index 3d3e3449a..308716ccb 100644 --- a/pumpkin-protocol/src/packet_decoder.rs +++ b/pumpkin-protocol/src/packet_decoder.rs @@ -28,7 +28,7 @@ impl PacketDecoder { let packet_len = match VarInt::decode_partial(&mut r) { Ok(len) => len, Err(VarIntDecodeError::Incomplete) => return Ok(None), - Err(VarIntDecodeError::TooLarge) => Err(PacketError::MailformedLength)?, + Err(VarIntDecodeError::TooLarge) => Err(PacketError::MalformedLength)?, }; if !(0..=MAX_PACKET_SIZE).contains(&packet_len) { diff --git a/pumpkin-protocol/src/server/play/s_chat_message.rs b/pumpkin-protocol/src/server/play/s_chat_message.rs index e644a5bc3..9332a28f2 100644 --- a/pumpkin-protocol/src/server/play/s_chat_message.rs +++ b/pumpkin-protocol/src/server/play/s_chat_message.rs @@ -13,7 +13,7 @@ pub struct SChatMessage { pub timestamp: i64, pub salt: i64, pub signature: Option, - pub messagee_count: VarInt, + pub message_count: VarInt, pub acknowledged: FixedBitSet, } @@ -25,7 +25,7 @@ impl ServerPacket for SChatMessage { timestamp: bytebuf.get_i64(), salt: bytebuf.get_i64(), signature: bytebuf.get_option(|v| v.copy_to_bytes(256)), - messagee_count: bytebuf.get_var_int(), + message_count: bytebuf.get_var_int(), acknowledged: bytebuf.get_fixed_bitset(20), }) } diff --git a/pumpkin-protocol/src/server/play/s_player_command.rs b/pumpkin-protocol/src/server/play/s_player_command.rs index 235baa6d5..f6365cc02 100644 --- a/pumpkin-protocol/src/server/play/s_player_command.rs +++ b/pumpkin-protocol/src/server/play/s_player_command.rs @@ -5,7 +5,7 @@ use crate::{bytebuf::DeserializerError, ServerPacket, VarInt}; #[packet(0x25)] pub struct SPlayerCommand { - pub entitiy_id: VarInt, + pub entity_id: VarInt, pub action: VarInt, pub jump_boost: VarInt, } @@ -16,8 +16,8 @@ pub enum Action { LeaveBed, StartSprinting, StopSprinting, - StartHourseJump, - StopHourseJump, + StartHorseJump, + StopHorseJump, OpenVehicleInventory, StartFlyingElytra, } @@ -25,7 +25,7 @@ pub enum Action { impl ServerPacket for SPlayerCommand { fn read(bytebuf: &mut crate::bytebuf::ByteBuffer) -> Result { Ok(Self { - entitiy_id: bytebuf.get_var_int(), + entity_id: bytebuf.get_var_int(), action: bytebuf.get_var_int(), jump_boost: bytebuf.get_var_int(), }) diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 97b0b3a4a..1bd1fe81c 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -33,6 +33,8 @@ num-traits = "0.2" num-derive = "0.4" num-bigint = "0.4.6" +ctrlc = "3.4" + # encryption rsa = "0.9.6" rsa-der = "0.3.0" diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index 065cf2c4e..442565b5b 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -269,12 +269,11 @@ impl Client { pub async fn handle_config_acknowledged( &mut self, - server: &mut Server, + _server: &mut Server, _config_acknowledged: SAcknowledgeFinishConfig, ) { dbg!("config acknowledged"); self.connection_state = ConnectionState::Play; - // generate a player - server.spawn_player(self).await; + self.make_player = true; } } diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index a0c852b53..a2edae31c 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -7,7 +7,9 @@ use pumpkin_protocol::client::play::{ use pumpkin_protocol::slot::Slot; use pumpkin_world::item::Item; -impl super::Client { +use crate::entity::player::Player; + +impl Player { pub fn open_container( &mut self, window_type: WindowType, @@ -26,7 +28,7 @@ impl super::Client { .unwrap()) .into(); let title = TextComponent::text(window_title.unwrap_or(window_type.default_title())); - self.send_packet(&COpenScreen::new( + self.client.send_packet(&COpenScreen::new( (window_type.clone() as u8 + 1).into(), menu_protocol_id, title, @@ -40,14 +42,12 @@ impl super::Client { items: Option>>, carried_item: Option<&'a Item>, ) { - let player = self.player.as_ref().unwrap(); - let slots: Vec = { if let Some(mut items) = items { - items.extend(player.inventory.slots()); + items.extend(self.inventory.slots()); items } else { - player.inventory.slots() + self.inventory.slots() } .into_iter() .map(|item| { @@ -69,7 +69,7 @@ impl super::Client { }; let packet = CSetContainerContent::new(window_type as u8 + 1, 0.into(), &slots, &carried_item); - self.send_packet(&packet); + self.client.send_packet(&packet); } pub fn set_container_slot( @@ -78,7 +78,7 @@ impl super::Client { slot: usize, item: Option<&Item>, ) { - self.send_packet(&CSetContainerSlot::new( + self.client.send_packet(&CSetContainerSlot::new( window_type as i8, 0, slot, diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index 71c49990d..ae5b7f650 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -6,33 +6,22 @@ use std::{ }; use crate::{ - entity::player::{ChatMode, GameMode, Hand, Player}, + entity::player::{ChatMode, Hand}, server::Server, }; use authentication::GameProfile; use mio::{event::Event, net::TcpStream, Token}; -use num_traits::ToPrimitive; use pumpkin_core::text::TextComponent; use pumpkin_protocol::{ bytebuf::packet_id::Packet, - client::{ - config::CConfigDisconnect, - login::CLoginDisconnect, - play::{CGameEvent, CPlayDisconnect, CSyncPlayerPostion, CSystemChatMessge}, - }, + client::{config::CConfigDisconnect, login::CLoginDisconnect, play::CPlayDisconnect}, packet_decoder::PacketDecoder, packet_encoder::PacketEncoder, server::{ config::{SAcknowledgeFinishConfig, SClientInformationConfig, SKnownPacks, SPluginMessage}, handshake::SHandShake, login::{SEncryptionResponse, SLoginAcknowledged, SLoginPluginResponse, SLoginStart}, - play::{ - SChatCommand, SChatMessage, SClientInformationPlay, SCloseContainer, SConfirmTeleport, - SInteract, SPlayPingRequest, SPlayerAction, SPlayerCommand, SPlayerPosition, - SPlayerPositionRotation, SPlayerRotation, SSetCreativeSlot, SSetHeldItem, SSwingArm, - SUseItemOn, - }, status::{SStatusPingRequest, SStatusRequest}, }, ClientPacket, ConnectionState, PacketError, RawPacket, ServerPacket, @@ -46,6 +35,7 @@ mod client_packet; mod container; pub mod player_packet; +#[derive(Clone)] pub struct PlayerConfig { pub locale: String, // 16 pub view_distance: i8, @@ -57,9 +47,22 @@ pub struct PlayerConfig { pub server_listing: bool, } -pub struct Client { - pub player: Option, +impl Default for PlayerConfig { + fn default() -> Self { + Self { + locale: "en_us".to_string(), + view_distance: 2, + chat_mode: ChatMode::Enabled, + chat_colors: true, + skin_parts: 0, + main_hand: Hand::Main, + text_filtering: false, + server_listing: false, + } + } +} +pub struct Client { pub gameprofile: Option, pub config: Option, @@ -75,6 +78,8 @@ pub struct Client { enc: PacketEncoder, dec: PacketDecoder, pub client_packets_queue: VecDeque, + + pub make_player: bool, } impl Client { @@ -86,7 +91,6 @@ impl Client { brand: None, token, address, - player: None, connection_state: ConnectionState::HandShake, connection, enc: PacketEncoder::default(), @@ -94,6 +98,7 @@ impl Client { encryption: true, closed: false, client_packets_queue: VecDeque::new(), + make_player: false, } } @@ -122,10 +127,6 @@ impl Client { self.enc.set_compression(compression); } - pub fn is_player(&self) -> bool { - self.player.is_some() - } - /// Send a Clientbound Packet to the Client pub fn send_packet(&mut self, packet: &P) { self.enc @@ -145,37 +146,6 @@ impl Client { Ok(()) } - pub fn teleport(&mut self, x: f64, y: f64, z: f64, yaw: f32, pitch: f32) { - assert!(self.is_player()); - // TODO - let id = 0; - let player = self.player.as_mut().unwrap(); - let entity = &mut player.entity; - entity.x = x; - entity.y = y; - entity.z = z; - entity.lastx = x; - entity.lasty = y; - entity.lastz = z; - entity.yaw = yaw; - entity.pitch = pitch; - player.awaiting_teleport = Some(id.into()); - self.send_packet(&CSyncPlayerPostion::new(x, y, z, yaw, pitch, 0, id.into())); - } - - pub fn update_health(&mut self, health: f32, food: i32, food_saturation: f32) { - let player = self.player.as_mut().unwrap(); - player.health = health; - player.food = food; - player.food_saturation = food_saturation; - } - - pub fn set_gamemode(&mut self, gamemode: GameMode) { - let player = self.player.as_mut().unwrap(); - player.gamemode = gamemode; - self.send_packet(&CGameEvent::new(3, gamemode.to_f32().unwrap())); - } - pub async fn process_packets(&mut self, server: &mut Server) { let mut i = 0; while i < self.client_packets_queue.len() { @@ -185,7 +155,7 @@ impl Client { } } - /// Handles an incoming decoded Packet + /// Handles an incoming decoded not Play state Packet pub async fn handle_packet(&mut self, server: &mut Server, packet: &mut RawPacket) { // TODO: handle each packet's Error instead of calling .unwrap() let bytebuf = &mut packet.bytebuf; @@ -254,74 +224,14 @@ impl Client { packet.id.0 ), }, - pumpkin_protocol::ConnectionState::Play => { - if self.player.is_some() { - self.handle_play_packet(server, packet); - } else { - // should be impossible - self.kick("no player in play state?") - } - } _ => log::error!("Invalid Connection state {:?}", self.connection_state), } } - pub fn handle_play_packet(&mut self, server: &mut Server, packet: &mut RawPacket) { - let bytebuf = &mut packet.bytebuf; - match packet.id.0 { - SConfirmTeleport::PACKET_ID => { - self.handle_confirm_teleport(server, SConfirmTeleport::read(bytebuf).unwrap()) - } - SChatCommand::PACKET_ID => { - self.handle_chat_command(server, SChatCommand::read(bytebuf).unwrap()) - } - SPlayerPosition::PACKET_ID => { - self.handle_position(server, SPlayerPosition::read(bytebuf).unwrap()) - } - SPlayerPositionRotation::PACKET_ID => self - .handle_position_rotation(server, SPlayerPositionRotation::read(bytebuf).unwrap()), - SPlayerRotation::PACKET_ID => { - self.handle_rotation(server, SPlayerRotation::read(bytebuf).unwrap()) - } - SPlayerCommand::PACKET_ID => { - self.handle_player_command(server, SPlayerCommand::read(bytebuf).unwrap()) - } - SSwingArm::PACKET_ID => { - self.handle_swing_arm(server, SSwingArm::read(bytebuf).unwrap()) - } - SChatMessage::PACKET_ID => { - self.handle_chat_message(server, SChatMessage::read(bytebuf).unwrap()) - } - SClientInformationPlay::PACKET_ID => self.handle_client_information_play( - server, - SClientInformationPlay::read(bytebuf).unwrap(), - ), - SInteract::PACKET_ID => self.handle_interact(server, SInteract::read(bytebuf).unwrap()), - SPlayerAction::PACKET_ID => { - self.handle_player_action(server, SPlayerAction::read(bytebuf).unwrap()) - } - SUseItemOn::PACKET_ID => { - self.handle_use_item_on(server, SUseItemOn::read(bytebuf).unwrap()) - } - SSetHeldItem::PACKET_ID => { - self.handle_set_held_item(server, SSetHeldItem::read(bytebuf).unwrap()) - } - SSetCreativeSlot::PACKET_ID => { - self.handle_set_creative_slot(server, SSetCreativeSlot::read(bytebuf).unwrap()) - } - SPlayPingRequest::PACKET_ID => { - self.handle_play_ping_request(server, SPlayPingRequest::read(bytebuf).unwrap()) - } - SCloseContainer::PACKET_ID => { - self.handle_close_container(server, SCloseContainer::read(bytebuf).unwrap()) - } - _ => log::error!("Failed to handle player packet id {:#04x}", packet.id.0), - } - } // Reads the connection until our buffer of len 4096 is full, then decode /// Close connection when an error occurs - pub async fn poll(&mut self, server: &mut Server, event: &Event) { + pub async fn poll(&mut self, event: &Event) { if event.is_readable() { let mut received_data = vec![0; 4096]; let mut bytes_read = 0; @@ -354,7 +264,6 @@ impl Client { Ok(packet) => { if let Some(packet) = packet { self.add_packet(packet); - self.process_packets(server).await; } } Err(err) => self.kick(&err.to_string()), @@ -364,10 +273,6 @@ impl Client { } } - pub fn send_system_message(&mut self, text: TextComponent) { - self.send_packet(&CSystemChatMessge::new(text, false)); - } - /// Kicks the Client with a reason depending on the connection state pub fn kick(&mut self, reason: &str) { dbg!(reason); @@ -382,6 +287,7 @@ impl Client { self.try_send_packet(&CConfigDisconnect::new(reason)) .unwrap_or_else(|_| self.close()); } + // So we can also kick on errors, but generally should use Player::kick ConnectionState::Play => { self.try_send_packet(&CPlayDisconnect::new(TextComponent::text(reason))) .unwrap_or_else(|_| self.close()); diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 5f4c0c61c..867b34475 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -2,7 +2,7 @@ use std::f32::consts::PI; use crate::{ commands::{handle_command, CommandSender}, - entity::player::{ChatMode, GameMode, Hand}, + entity::player::{ChatMode, GameMode, Hand, Player}, server::Server, util::math::wrap_degrees, }; @@ -28,28 +28,28 @@ use pumpkin_protocol::{ use pumpkin_world::block::BlockFace; use pumpkin_world::global_registry; -use super::{Client, PlayerConfig}; +use super::PlayerConfig; fn modulus(a: f32, b: f32) -> f32 { ((a % b) + b) % b } /// Handles all Play Packets send by a real Player -impl Client { +impl Player { pub fn handle_confirm_teleport( &mut self, _server: &mut Server, confirm_teleport: SConfirmTeleport, ) { - let player = self.player.as_mut().unwrap(); - if let Some(id) = player.awaiting_teleport.clone() { + if let Some(id) = self.awaiting_teleport.clone() { if id == confirm_teleport.teleport_id { } else { log::warn!("Teleport id does not match, Weird but okay"); } - player.awaiting_teleport = None; + self.awaiting_teleport = None; } else { - self.kick("Send Teleport confirm, but we did not teleport") + self.client + .kick("Send Teleport confirm, but we did not teleport") } } @@ -63,11 +63,10 @@ impl Client { pub fn handle_position(&mut self, server: &mut Server, position: SPlayerPosition) { if position.x.is_nan() || position.feet_y.is_nan() || position.z.is_nan() { - self.kick("Invalid movement"); + self.kick(TextComponent::text("Invalid movement")); return; } - let player = self.player.as_mut().unwrap(); - let entity = &mut player.entity; + let entity = &mut self.entity; entity.lastx = entity.x; entity.lasty = entity.y; entity.lastz = entity.z; @@ -77,7 +76,7 @@ impl Client { // TODO: teleport when moving > 8 block // send new position to all other players - let on_ground = player.on_ground; + let on_ground = self.on_ground; let entity_id = entity.entity_id; let (x, lastx) = (entity.x, entity.lastx); let (y, lasty) = (entity.y, entity.lasty); @@ -104,15 +103,14 @@ impl Client { || position_rotation.feet_y.is_nan() || position_rotation.z.is_nan() { - self.kick("Invalid movement"); + self.kick(TextComponent::text("Invalid movement")); return; } if !position_rotation.yaw.is_finite() || !position_rotation.pitch.is_finite() { - self.kick("Invalid rotation"); + self.kick(TextComponent::text("Invalid rotation")); return; } - let player = self.player.as_mut().unwrap(); - let entity = &mut player.entity; + let entity = &mut self.entity; entity.lastx = entity.x; entity.lasty = entity.y; @@ -124,7 +122,7 @@ impl Client { entity.pitch = wrap_degrees(position_rotation.pitch).clamp(-90.0, 90.0) % 360.0; // send new position to all other players - let on_ground = player.on_ground; + let on_ground = self.on_ground; let entity_id = entity.entity_id; let (x, lastx) = (entity.x, entity.lastx); let (y, lasty) = (entity.y, entity.lasty); @@ -150,15 +148,14 @@ impl Client { pub fn handle_rotation(&mut self, server: &mut Server, rotation: SPlayerRotation) { if !rotation.yaw.is_finite() || !rotation.pitch.is_finite() { - self.kick("Invalid rotation"); + self.kick(TextComponent::text("Invalid rotation")); return; } - let player = self.player.as_mut().unwrap(); - let entity = &mut player.entity; + let entity = &mut self.entity; entity.yaw = wrap_degrees(rotation.yaw) % 360.0; entity.pitch = wrap_degrees(rotation.pitch).clamp(-90.0, 90.0) % 360.0; // send new position to all other players - let on_ground = player.on_ground; + let on_ground = self.on_ground; let entity_id = entity.entity_id; let yaw = modulus(entity.yaw * 256.0 / 360.0, 256.0); let pitch = modulus(entity.pitch * 256.0 / 360.0, 256.0); @@ -176,26 +173,24 @@ impl Client { } pub fn handle_player_command(&mut self, _server: &mut Server, command: SPlayerCommand) { - let player = self.player.as_mut().unwrap(); - - if command.entitiy_id != player.entity.entity_id.into() { + if command.entity_id != self.entity.entity_id.into() { return; } if let Some(action) = Action::from_i32(command.action.0) { match action { - pumpkin_protocol::server::play::Action::StartSneaking => player.sneaking = true, - pumpkin_protocol::server::play::Action::StopSneaking => player.sneaking = false, + pumpkin_protocol::server::play::Action::StartSneaking => self.sneaking = true, + pumpkin_protocol::server::play::Action::StopSneaking => self.sneaking = false, pumpkin_protocol::server::play::Action::LeaveBed => todo!(), - pumpkin_protocol::server::play::Action::StartSprinting => player.sprinting = true, - pumpkin_protocol::server::play::Action::StopSprinting => player.sprinting = false, - pumpkin_protocol::server::play::Action::StartHourseJump => todo!(), - pumpkin_protocol::server::play::Action::StopHourseJump => todo!(), + pumpkin_protocol::server::play::Action::StartSprinting => self.sprinting = true, + pumpkin_protocol::server::play::Action::StopSprinting => self.sprinting = false, + pumpkin_protocol::server::play::Action::StartHorseJump => todo!(), + pumpkin_protocol::server::play::Action::StopHorseJump => todo!(), pumpkin_protocol::server::play::Action::OpenVehicleInventory => todo!(), pumpkin_protocol::server::play::Action::StartFlyingElytra => {} // TODO } } else { - self.kick("Invalid player command") + self.kick(TextComponent::text("Invalid player command")) } } @@ -204,10 +199,9 @@ impl Client { Hand::Main => Animation::SwingMainArm, Hand::Off => Animation::SwingOffhand, }; - let player = self.player.as_mut().unwrap(); - let id = player.entity_id(); + let id = self.entity_id(); server.broadcast_packet_except( - &[&self.token], + &[&self.client.token], &CEntityAnimation::new(id.into(), animation as u8), ) } @@ -217,12 +211,12 @@ impl Client { let message = chat_message.message; if message.len() > 256 { - self.kick("Oversized message"); + self.kick(TextComponent::text("Oversized message")); return; } // TODO: filter message & validation - let gameprofile = self.gameprofile.as_ref().unwrap(); + let gameprofile = self.client.gameprofile.as_ref().unwrap(); server.broadcast_packet( self, @@ -258,7 +252,7 @@ impl Client { _server: &mut Server, client_information: SClientInformationPlay, ) { - self.config = Some(PlayerConfig { + self.client.config = Some(PlayerConfig { locale: client_information.locale, view_distance: client_information.view_distance, chat_mode: ChatMode::from_i32(client_information.chat_mode.into()).unwrap(), @@ -277,18 +271,16 @@ impl Client { // TODO: do validation and stuff let config = &server.advanced_config.pvp; if config.enabled { - let attacked_client = server.get_by_entityid(self, entity_id.0 as EntityId); - let attacker_player = self.player.as_mut().unwrap(); - attacker_player.sneaking = interact.sneaking; - if let Some(mut client) = attacked_client { - let token = client.token.clone(); - let player = client.player.as_mut().unwrap(); + let attacked_player = server.get_by_entityid(self, entity_id.0 as EntityId); + self.sneaking = interact.sneaking; + if let Some(mut player) = attacked_player { + let token = player.client.token.clone(); let velo = player.velocity; if config.protect_creative && player.gamemode == GameMode::Creative { return; } if config.knockback { - let yaw = attacker_player.entity.yaw; + let yaw = self.entity.yaw; let strength = 1.0; player.knockback( strength * 0.5, @@ -301,25 +293,25 @@ impl Client { player.velocity.y as f32, player.velocity.z as f32, ); - attacker_player.velocity = attacker_player.velocity.multiply(0.6, 1.0, 0.6); + self.velocity = self.velocity.multiply(0.6, 1.0, 0.6); player.velocity = velo; - client.send_packet(packet); + player.client.send_packet(packet); } if config.hurt_animation { // TODO // thats how we prevent borrow errors :c - let packet = &CHurtAnimation::new(&entity_id, attacker_player.entity.yaw); - self.send_packet(packet); - client.send_packet(packet); + let packet = &CHurtAnimation::new(&entity_id, self.entity.yaw); + self.client.send_packet(packet); + player.client.send_packet(packet); server.broadcast_packet_except( - &[self.token.as_ref(), token.as_ref()], + &[self.client.token.as_ref(), token.as_ref()], &CHurtAnimation::new(&entity_id, 10.0), ) } if config.swing {} } else { - self.kick("Interacted with invalid entitiy id") + self.kick(TextComponent::text("Interacted with invalid entity id")) } } } @@ -328,9 +320,8 @@ impl Client { match Status::from_i32(player_action.status.0).unwrap() { Status::StartedDigging => { // TODO: do validation - let player = self.player.as_mut().unwrap(); // TODO: Config - if player.gamemode == GameMode::Creative { + if self.gamemode == GameMode::Creative { let location = player_action.location; // Block break & block break sound // TODO: currently this is always dirt replace it @@ -340,8 +331,7 @@ impl Client { } } Status::CancelledDigging => { - let player = self.player.as_mut().unwrap(); - player.current_block_destroy_stage = 0; + self.current_block_destroy_stage = 0; } Status::FinishedDigging => { // TODO: do validation @@ -352,7 +342,8 @@ impl Client { // AIR server.broadcast_packet(self, &CBlockUpdate::new(location, 0.into())); // TODO: Send this every tick - self.send_packet(&CAcknowledgeBlockChange::new(player_action.sequence)); + self.client + .send_packet(&CAcknowledgeBlockChange::new(player_action.sequence)); } Status::DropItemStack => { dbg!("todo"); @@ -370,16 +361,18 @@ impl Client { } pub fn handle_play_ping_request(&mut self, _server: &mut Server, request: SPlayPingRequest) { - self.send_packet(&CPingResponse::new(request.payload)); + self.client + .send_packet(&CPingResponse::new(request.payload)); } pub fn handle_use_item_on(&mut self, server: &mut Server, use_item_on: SUseItemOn) { - self.send_packet(&CAcknowledgeBlockChange::new(use_item_on.sequence)); + self.client + .send_packet(&CAcknowledgeBlockChange::new(use_item_on.sequence)); let location = use_item_on.location; let face = BlockFace::from_i32(use_item_on.face.0).unwrap(); let location = WorldPosition(location.0 + face.to_offset()); - if let Some(item) = self.player.as_ref().unwrap().inventory.held_item() { + if let Some(item) = self.inventory.held_item() { let minecraft_id = global_registry::find_minecraft_id(global_registry::ITEM_REGISTRY, item.item_id) .expect("All item ids are in the global registry"); @@ -400,21 +393,20 @@ impl Client { pub fn handle_set_held_item(&mut self, _server: &mut Server, held: SSetHeldItem) { let slot = held.slot; if !(0..=8).contains(&slot) { - self.kick("Invalid held slot") + self.kick(TextComponent::text("Invalid held slot")) } - let player = self.player.as_mut().unwrap(); - player.inventory.set_selected(slot as usize); + self.inventory.set_selected(slot as usize); } pub fn handle_set_creative_slot(&mut self, _server: &mut Server, packet: SSetCreativeSlot) { - let player = self.player.as_mut().unwrap(); - if player.gamemode != GameMode::Creative { - self.kick("Invalid action, you can only do that if you are in creative"); + if self.gamemode != GameMode::Creative { + self.kick(TextComponent::text( + "Invalid action, you can only do that if you are in creative", + )); return; } - let inventory = &mut player.inventory; - - inventory.set_slot(packet.slot as usize, packet.clicked_item.to_item(), false); + self.inventory + .set_slot(packet.slot as usize, packet.clicked_item.to_item(), false); } // TODO: diff --git a/pumpkin/src/commands/arg_player.rs b/pumpkin/src/commands/arg_player.rs index a8ee8f460..6a52c5510 100644 --- a/pumpkin/src/commands/arg_player.rs +++ b/pumpkin/src/commands/arg_player.rs @@ -1,4 +1,3 @@ -use crate::client::Client; use crate::commands::dispatcher::InvalidTreeError; use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError; use crate::commands::tree::{ConsumedArgs, RawArgs}; @@ -16,8 +15,8 @@ pub fn consume_arg_player(src: &CommandSender, args: &mut RawArgs) -> Option None, // todo: implement all players target selector _ => { // todo: implement any other player than sender - if let Player(client) = src { - if let Some(profile) = &client.gameprofile { + if let Player(player) = src { + if let Some(profile) = &player.client.gameprofile { if profile.name == s { return Some(s.into()); }; @@ -33,7 +32,7 @@ pub fn parse_arg_player<'a>( src: &'a mut CommandSender, arg_name: &str, consumed_args: &ConsumedArgs, -) -> Result<&'a mut Client, InvalidTreeError> { +) -> Result<&'a mut crate::entity::player::Player, InvalidTreeError> { let s = consumed_args .get(arg_name) .ok_or(InvalidConsumptionError(None))? @@ -46,10 +45,10 @@ pub fn parse_arg_player<'a>( "@a" | "@e" => Err(InvalidConsumptionError(Some(s.into()))), // todo: implement all players target selector _ => { // todo: implement any other player than sender - if let Player(client) = src { - if let Some(profile) = &client.gameprofile { + if let Player(player) = src { + if let Some(profile) = &player.client.gameprofile { if profile.name == s { - return Ok(client); + return Ok(player); }; }; }; diff --git a/pumpkin/src/commands/cmd_stop.rs b/pumpkin/src/commands/cmd_stop.rs index 60f425b76..81bd07ea9 100644 --- a/pumpkin/src/commands/cmd_stop.rs +++ b/pumpkin/src/commands/cmd_stop.rs @@ -1,3 +1,6 @@ +use pumpkin_core::text::color::NamedColor; +use pumpkin_core::text::TextComponent; + use crate::commands::tree::CommandTree; use crate::commands::tree_builder::require; @@ -7,7 +10,10 @@ const DESCRIPTION: &str = "Stop the server."; pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| sender.permission_lvl() >= 4) - .execute(&|_sender, _args| std::process::exit(0)), + require(&|sender| sender.permission_lvl() >= 4).execute(&|sender, _args| { + sender + .send_message(TextComponent::text("Stopping Server").color_named(NamedColor::Red)); + std::process::exit(0) + }), ) } diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index 7ca503fd5..a827303ce 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -2,8 +2,8 @@ use pumpkin_core::text::TextComponent; use std::collections::HashMap; use std::sync::OnceLock; -use crate::client::Client; use crate::commands::dispatcher::CommandDispatcher; +use crate::entity::player::Player; mod arg_player; mod cmd_gamemode; mod cmd_help; @@ -17,7 +17,7 @@ mod tree_format; pub enum CommandSender<'a> { Rcon(&'a mut Vec), Console, - Player(&'a mut Client), + Player(&'a mut Player), } impl<'a> CommandSender<'a> { @@ -45,9 +45,9 @@ impl<'a> CommandSender<'a> { CommandSender::Rcon(_) => true, } } - pub fn as_mut_player(&mut self) -> Option<&mut Client> { + pub fn as_mut_player(&mut self) -> Option<&mut Player> { match self { - CommandSender::Player(client) => Some(client), + CommandSender::Player(player) => Some(player), CommandSender::Console => None, CommandSender::Rcon(_) => None, } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 939c7b8e2..935ba7cd4 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -1,13 +1,27 @@ use std::str::FromStr; use num_derive::{FromPrimitive, ToPrimitive}; +use num_traits::ToPrimitive; +use pumpkin_core::text::TextComponent; use pumpkin_entity::{entity_type::EntityType, Entity, EntityId}; use pumpkin_inventory::player::PlayerInventory; -use pumpkin_protocol::VarInt; +use pumpkin_protocol::{ + bytebuf::packet_id::Packet, + client::play::{CGameEvent, CPlayDisconnect, CSyncPlayerPosition, CSystemChatMessage}, + server::play::{ + SChatCommand, SChatMessage, SClientInformationPlay, SConfirmTeleport, SInteract, + SPlayPingRequest, SPlayerAction, SPlayerCommand, SPlayerPosition, SPlayerPositionRotation, + SPlayerRotation, SSetCreativeSlot, SSetHeldItem, SSwingArm, SUseItemOn, + }, + ConnectionState, RawPacket, ServerPacket, VarInt, +}; use pumpkin_world::vector3::Vector3; use serde::{Deserialize, Serialize}; +use crate::{client::Client, server::Server}; + pub struct Player { + pub client: Client, pub entity: Entity, // current gamemode pub gamemode: GameMode, @@ -34,8 +48,9 @@ pub struct Player { } impl Player { - pub fn new(entity_id: EntityId, gamemode: GameMode) -> Self { + pub fn new(client: Client, entity_id: EntityId, gamemode: GameMode) -> Self { Self { + client, entity: Entity::new(entity_id, EntityType::Player), on_ground: false, awaiting_teleport: None, @@ -77,15 +92,121 @@ impl Player { var7.z / 2.0 - var8.z, ); } + + pub fn teleport(&mut self, x: f64, y: f64, z: f64, yaw: f32, pitch: f32) { + // TODO + let id = 0; + let entity = &mut self.entity; + entity.x = x; + entity.y = y; + entity.z = z; + entity.lastx = x; + entity.lasty = y; + entity.lastz = z; + entity.yaw = yaw; + entity.pitch = pitch; + self.awaiting_teleport = Some(id.into()); + self.client + .send_packet(&CSyncPlayerPosition::new(x, y, z, yaw, pitch, 0, id.into())); + } + + /// Kicks the Client with a reason depending on the connection state + pub fn kick(&mut self, reason: TextComponent) { + assert!(self.client.connection_state == ConnectionState::Play); + dbg!(&reason); + self.client + .try_send_packet(&CPlayDisconnect::new(reason)) + .unwrap_or_else(|_| self.client.close()); + + self.client.close() + } + + pub fn update_health(&mut self, health: f32, food: i32, food_saturation: f32) { + self.health = health; + self.food = food; + self.food_saturation = food_saturation; + } + + pub fn set_gamemode(&mut self, gamemode: GameMode) { + self.gamemode = gamemode; + self.client + .send_packet(&CGameEvent::new(3, gamemode.to_f32().unwrap())); + } + + pub fn send_system_message(&mut self, text: TextComponent) { + self.client + .send_packet(&CSystemChatMessage::new(text, false)); + } +} + +impl Player { + pub fn process_packets(&mut self, server: &mut Server) { + let mut i = 0; + while i < self.client.client_packets_queue.len() { + let mut packet = self.client.client_packets_queue.remove(i).unwrap(); + self.handle_play_packet(server, &mut packet); + i += 1; + } + } + + pub fn handle_play_packet(&mut self, server: &mut Server, packet: &mut RawPacket) { + let bytebuf = &mut packet.bytebuf; + match packet.id.0 { + SConfirmTeleport::PACKET_ID => { + self.handle_confirm_teleport(server, SConfirmTeleport::read(bytebuf).unwrap()) + } + SChatCommand::PACKET_ID => { + self.handle_chat_command(server, SChatCommand::read(bytebuf).unwrap()) + } + SPlayerPosition::PACKET_ID => { + self.handle_position(server, SPlayerPosition::read(bytebuf).unwrap()) + } + SPlayerPositionRotation::PACKET_ID => self + .handle_position_rotation(server, SPlayerPositionRotation::read(bytebuf).unwrap()), + SPlayerRotation::PACKET_ID => { + self.handle_rotation(server, SPlayerRotation::read(bytebuf).unwrap()) + } + SPlayerCommand::PACKET_ID => { + self.handle_player_command(server, SPlayerCommand::read(bytebuf).unwrap()) + } + SSwingArm::PACKET_ID => { + self.handle_swing_arm(server, SSwingArm::read(bytebuf).unwrap()) + } + SChatMessage::PACKET_ID => { + self.handle_chat_message(server, SChatMessage::read(bytebuf).unwrap()) + } + SClientInformationPlay::PACKET_ID => self.handle_client_information_play( + server, + SClientInformationPlay::read(bytebuf).unwrap(), + ), + SInteract::PACKET_ID => self.handle_interact(server, SInteract::read(bytebuf).unwrap()), + SPlayerAction::PACKET_ID => { + self.handle_player_action(server, SPlayerAction::read(bytebuf).unwrap()) + } + SUseItemOn::PACKET_ID => { + self.handle_use_item_on(server, SUseItemOn::read(bytebuf).unwrap()) + } + SSetHeldItem::PACKET_ID => { + self.handle_set_held_item(server, SSetHeldItem::read(bytebuf).unwrap()) + } + SSetCreativeSlot::PACKET_ID => { + self.handle_set_creative_slot(server, SSetCreativeSlot::read(bytebuf).unwrap()) + } + SPlayPingRequest::PACKET_ID => { + self.handle_play_ping_request(server, SPlayPingRequest::read(bytebuf).unwrap()) + } + _ => log::error!("Failed to handle player packet id {:#04x}", packet.id.0), + } + } } -#[derive(FromPrimitive)] +#[derive(FromPrimitive, Clone)] pub enum Hand { Main, Off, } -#[derive(FromPrimitive)] +#[derive(FromPrimitive, Clone)] pub enum ChatMode { Enabled, CommandsOnly, diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 69f839c91..3d0f2b368 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -31,6 +31,9 @@ static ALLOC: dhat::Alloc = dhat::Alloc; #[cfg(not(target_os = "wasi"))] fn main() -> io::Result<()> { + use entity::player::Player; + use pumpkin_core::text::{color::NamedColor, TextComponent}; + #[cfg(feature = "dhat-heap")] let _profiler = dhat::Profiler::new_heap(); #[cfg(feature = "dhat-heap")] @@ -39,6 +42,17 @@ fn main() -> io::Result<()> { .enable_all() .build() .unwrap(); + + ctrlc::set_handler(|| { + log::warn!( + "{}", + TextComponent::text("Stopping Server") + .color_named(NamedColor::Red) + .to_pretty_console() + ); + std::process::exit(0); + }) + .unwrap(); // ensure rayon is built outside of tokio scope rayon::ThreadPoolBuilder::new().build_global().unwrap(); rt.block_on(async { @@ -80,7 +94,8 @@ fn main() -> io::Result<()> { let use_console = advanced_configuration.commands.use_console; let rcon = advanced_configuration.rcon.clone(); - let mut connections: HashMap>> = HashMap::new(); + let mut clients: HashMap = HashMap::new(); + let mut players: HashMap, Rc>> = HashMap::new(); let mut server = Server::new((basic_config, advanced_configuration)); log::info!("Started Server took {}ms", time.elapsed().as_millis()); @@ -94,6 +109,7 @@ fn main() -> io::Result<()> { stdin .read_line(&mut out) .expect("Failed to read console line"); + if !out.is_empty() { handle_command(&mut commands::CommandSender::Console, &out); } @@ -146,30 +162,50 @@ fn main() -> io::Result<()> { Interest::READABLE.add(Interest::WRITABLE), )?; let rc_token = Rc::new(token); - let client = Rc::new(RefCell::new(Client::new( - Rc::clone(&rc_token), - connection, - addr, - ))); - server.add_client(rc_token, Rc::clone(&client)); - connections.insert(token, client); + let client = Client::new(Rc::clone(&rc_token), connection, addr); + clients.insert(token, client); }, token => { - // Maybe received an event for a TCP connection. - let done = if let Some(client) = connections.get_mut(&token) { - let mut client = client.borrow_mut(); - client.poll(&mut server, event).await; - client.closed + // Poll Players + let done = if let Some(player) = players.get_mut(&token) { + let mut player = player.borrow_mut(); + player.client.poll(event).await; + player.process_packets(&mut server); + player.client.closed } else { - // Sporadic events happen, we can safely ignore them. false }; + if done { - if let Some(client) = connections.remove(&token) { - server.remove_client(&token); - let mut client = client.borrow_mut(); - poll.registry().deregister(&mut client.connection)?; + if let Some(player) = players.remove(&token) { + server.remove_player(&token); + let mut player = player.borrow_mut(); + poll.registry().deregister(&mut player.client.connection)?; + } + } + + // Poll current Clients (non players) + // Maybe received an event for a TCP connection. + let (done, make_player) = if let Some(client) = clients.get_mut(&token) { + client.poll(event).await; + client.process_packets(&mut server).await; + (client.closed, client.make_player) + } else { + // Sporadic events happen, we can safely ignore them. + (false, false) + }; + if done || make_player { + if let Some(mut client) = clients.remove(&token) { + if done { + poll.registry().deregister(&mut client.connection)?; + } else if make_player { + let token = client.token.clone(); + let player = server.add_player(token.clone(), client); + players.insert(token, player.clone()); + let mut player = player.borrow_mut(); + server.spawn_player(&mut player).await; + } } } } diff --git a/pumpkin/src/server.rs b/pumpkin/src/server.rs index 0316a34b8..c2aa939d6 100644 --- a/pumpkin/src/server.rs +++ b/pumpkin/src/server.rs @@ -13,7 +13,7 @@ use std::{ use base64::{engine::general_purpose, Engine}; use image::GenericImageView; -use mio::{event::Event, Poll, Token}; +use mio::Token; use num_traits::ToPrimitive; use pumpkin_entity::{entity_type::EntityType, EntityId}; use pumpkin_protocol::{ @@ -60,7 +60,7 @@ pub struct Server { /// Cache the registry so we don't have to parse it every time a player joins pub cached_registry: Vec, - pub current_clients: HashMap, Rc>>, + pub current_players: HashMap, Rc>>, // TODO: replace with HashMap entity_id: AtomicI32, // TODO: place this into every world @@ -115,54 +115,48 @@ impl Server { status_response, status_response_json, public_key_der, - current_clients: HashMap::new(), + current_players: HashMap::new(), base_config: config.0, auth_client, advanced_config: config.1, } } - // Returns Tokens to remove - pub async fn poll(&mut self, client: &mut Client, _poll: &Poll, event: &Event) { - // TODO: Poll players in every world - client.poll(self, event).await - } - - pub fn add_client(&mut self, token: Rc, client: Rc>) { - self.current_clients.insert(token, client); + pub fn add_player(&mut self, token: Rc, client: Client) -> Rc> { + let entity_id = self.new_entity_id(); + let gamemode = match self.base_config.default_gamemode { + GameMode::Undefined => GameMode::Survival, + game_mode => game_mode, + }; + let player = Rc::new(RefCell::new(Player::new(client, entity_id, gamemode))); + self.current_players.insert(token, player.clone()); + player } - pub fn remove_client(&mut self, token: &Token) { - let client = self.current_clients.remove(token).unwrap(); - let client = client.borrow(); + pub fn remove_player(&mut self, token: &Token) { + let player = self.current_players.remove(token).unwrap(); + let player = player.as_ref().borrow(); // despawn the player // todo: put this into the entitiy struct - if client.is_player() { - let id = client.player.as_ref().unwrap().entity_id(); - let uuid = client.gameprofile.as_ref().unwrap().id; - self.broadcast_packet_except( - &[&client.token], - &CRemovePlayerInfo::new(1.into(), &[UUID(uuid)]), - ); - self.broadcast_packet_except(&[&client.token], &CRemoveEntities::new(&[id.into()])) - } + let id = player.entity_id(); + let uuid = player.client.gameprofile.as_ref().unwrap().id; + self.broadcast_packet_except( + &[&player.client.token], + &CRemovePlayerInfo::new(1.into(), &[UUID(uuid)]), + ); + self.broadcast_packet_except(&[&player.client.token], &CRemoveEntities::new(&[id.into()])) } // here is where the magic happens // TODO: do this in a world - pub async fn spawn_player(&mut self, client: &mut Client) { + pub async fn spawn_player(&mut self, player: &mut Player) { // This code follows the vanilla packet order - let entity_id = self.new_entity_id(); - let gamemode = match self.base_config.default_gamemode { - GameMode::Undefined => GameMode::Survival, - game_mode => game_mode, - }; + let entity_id = player.entity_id(); + let gamemode = player.gamemode; log::debug!("spawning player, entity id {}", entity_id); - let player = Player::new(entity_id, gamemode); - client.player = Some(player); // login packet for our new player - client.send_packet(&CLogin::new( + player.client.send_packet(&CLogin::new( entity_id, self.base_config.hardcore, &["minecraft:overworld"], @@ -185,7 +179,9 @@ impl Server { )); dbg!("sending abilities"); // player abilities - client.send_packet(&CPlayerAbilities::new(0x02, 0.1, 0.1)); + player + .client + .send_packet(&CPlayerAbilities::new(0x02, 0.1, 0.1)); // teleport let x = 10.0; @@ -193,12 +189,12 @@ impl Server { let z = 10.0; let yaw = 10.0; let pitch = 10.0; - client.teleport(x, y, z, 10.0, 10.0); - let gameprofile = client.gameprofile.as_ref().unwrap(); + player.teleport(x, y, z, 10.0, 10.0); + let gameprofile = player.client.gameprofile.as_ref().unwrap(); // first send info update to our new player, So he can see his Skin // also send his info to everyone else self.broadcast_packet( - client, + player, &CPlayerInfoUpdate::new( 0x01 | 0x08, &[pumpkin_protocol::client::play::Player { @@ -216,32 +212,36 @@ impl Server { // here we send all the infos of already joined players let mut entries = Vec::new(); - for (_, client) in self.current_clients.iter().filter(|c| c.0 != &client.token) { - let client = client.borrow(); - if client.is_player() { - let gameprofile = client.gameprofile.as_ref().unwrap(); - entries.push(pumpkin_protocol::client::play::Player { - uuid: gameprofile.id, - actions: vec![ - PlayerAction::AddPlayer { - name: gameprofile.name.clone(), - properties: gameprofile.properties.clone(), - }, - PlayerAction::UpdateListed { listed: true }, - ], - }) - } + for (_, playerr) in self + .current_players + .iter() + .filter(|c| c.0 != &player.client.token) + { + let playerr = playerr.as_ref().borrow(); + let gameprofile = &playerr.client.gameprofile.as_ref().unwrap(); + entries.push(pumpkin_protocol::client::play::Player { + uuid: gameprofile.id, + actions: vec![ + PlayerAction::AddPlayer { + name: gameprofile.name.clone(), + properties: gameprofile.properties.clone(), + }, + PlayerAction::UpdateListed { listed: true }, + ], + }) } - client.send_packet(&CPlayerInfoUpdate::new(0x01 | 0x08, &entries)); + player + .client + .send_packet(&CPlayerInfoUpdate::new(0x01 | 0x08, &entries)); // Start waiting for level chunks - client.send_packet(&CGameEvent::new(13, 0.0)); + player.client.send_packet(&CGameEvent::new(13, 0.0)); - let gameprofile = client.gameprofile.as_ref().unwrap(); + let gameprofile = player.client.gameprofile.as_ref().unwrap(); // spawn player for every client self.broadcast_packet_except( - &[&client.token], + &[&player.client.token], // TODO: add velo &CSpawnEntity::new( entity_id.into(), @@ -260,33 +260,31 @@ impl Server { ), ); // spawn players for our client - let token = client.token.clone(); - for (_, existing_client) in self.current_clients.iter().filter(|c| c.0 != &token) { - let existing_client = existing_client.borrow(); - if let Some(player) = &existing_client.player { - let entity = &player.entity; - let gameprofile = existing_client.gameprofile.as_ref().unwrap(); - client.send_packet(&CSpawnEntity::new( - player.entity_id().into(), - UUID(gameprofile.id), - EntityType::Player.to_i32().unwrap().into(), - entity.x, - entity.y, - entity.z, - entity.yaw, - entity.pitch, - entity.pitch, - 0.into(), - 0.0, - 0.0, - 0.0, - )) - } + let token = player.client.token.clone(); + for (_, existing_player) in self.current_players.iter().filter(|c| c.0 != &token) { + let existing_player = existing_player.as_ref().borrow(); + let entity = &existing_player.entity; + let gameprofile = existing_player.client.gameprofile.as_ref().unwrap(); + player.client.send_packet(&CSpawnEntity::new( + player.entity_id().into(), + UUID(gameprofile.id), + EntityType::Player.to_i32().unwrap().into(), + entity.x, + entity.y, + entity.z, + entity.yaw, + entity.pitch, + entity.pitch, + 0.into(), + 0.0, + 0.0, + 0.0, + )) } // entity meta data - if let Some(config) = &client.config { + if let Some(config) = player.client.config.clone() { self.broadcast_packet( - client, + player, &CSetEntityMetadata::new( entity_id.into(), Metadata::new(17, VarInt(0), config.skin_parts), @@ -294,35 +292,41 @@ impl Server { ) } - self.spawn_test_chunk(client, self.base_config.view_distance as u32) + self.spawn_test_chunk(player, self.base_config.view_distance as u32) .await; } /// TODO: This definitly should be in world - pub fn get_by_entityid(&self, from: &Client, id: EntityId) -> Option> { - for (_, client) in self.current_clients.iter().filter(|c| c.0 != &from.token) { + pub fn get_by_entityid(&self, from: &Player, id: EntityId) -> Option> { + for (_, player) in self + .current_players + .iter() + .filter(|c| c.0 != &from.client.token) + { // Check if client is a player - let client = client.borrow_mut(); - if client.is_player() && client.player.as_ref().unwrap().entity_id() == id { - return Some(client); + let player = player.borrow_mut(); + if player.entity_id() == id { + return Some(player); } } None } /// Sends a Packet to all Players - pub fn broadcast_packet

(&self, from: &mut Client, packet: &P) + pub fn broadcast_packet

(&self, from: &mut Player, packet: &P) where P: ClientPacket, { // we can't borrow twice at same time - from.send_packet(packet); - for (_, client) in self.current_clients.iter().filter(|c| c.0 != &from.token) { + from.client.send_packet(packet); + for (_, player) in self + .current_players + .iter() + .filter(|c| c.0 != &from.client.token) + { // Check if client is a player - let mut client = client.borrow_mut(); - if client.is_player() { - client.send_packet(packet); - } + let mut player = player.borrow_mut(); + player.client.send_packet(packet); } } @@ -331,21 +335,19 @@ impl Server { where P: ClientPacket, { - for (_, client) in self - .current_clients + for (_, player) in self + .current_players .iter() .filter(|c| !from.contains(&c.0.as_ref())) { // Check if client is a player - let mut client = client.borrow_mut(); - if client.is_player() { - client.send_packet(packet); - } + let mut player = player.borrow_mut(); + player.client.send_packet(packet); } } // TODO: do this in a world - async fn spawn_test_chunk(&self, client: &mut Client, distance: u32) { + async fn spawn_test_chunk(&self, player: &mut Player, distance: u32) { let inst = std::time::Instant::now(); let (sender, mut chunk_receiver) = mpsc::channel(distance as usize); let world = self.world.clone(); @@ -358,7 +360,7 @@ impl Server { .await; }); - client.send_packet(&CCenterChunk { + player.client.send_packet(&CCenterChunk { chunk_x: 0.into(), chunk_z: 0.into(), }); @@ -382,7 +384,7 @@ impl Server { len / (1024 * 1024) ); } - client.send_packet(&CChunkData(&chunk_data)); + player.client.send_packet(&CChunkData(&chunk_data)); } let t = inst.elapsed(); dbg!("DONE", t);