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 b8a242dbd..df188212f 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -4,7 +4,9 @@ use pumpkin_protocol::client::play::{COpenScreen, CSetContainerContent, CSetCont 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, @@ -23,7 +25,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, @@ -37,14 +39,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| { @@ -66,7 +66,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( @@ -75,7 +75,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 7793ec46d..5146a31f2 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, CSyncPlayerPosition, CSystemChatMessage}, - }, + 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, 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(&CSyncPlayerPosition::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,71 +224,13 @@ 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()) - } - _ => 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; @@ -351,7 +263,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()), @@ -361,10 +272,6 @@ impl Client { } } - pub fn send_system_message(&mut self, text: TextComponent) { - self.send_packet(&CSystemChatMessage::new(text, false)); - } - /// Kicks the Client with a reason depending on the connection state pub fn kick(&mut self, reason: &str) { dbg!(reason); @@ -379,6 +286,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 a1b4273ec..993b20f56 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, }; @@ -26,28 +26,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") } } @@ -61,11 +61,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; @@ -75,7 +74,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); @@ -102,15 +101,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; @@ -122,7 +120,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); @@ -148,15 +146,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); @@ -174,26 +171,24 @@ impl Client { } pub fn handle_player_command(&mut self, _server: &mut Server, command: SPlayerCommand) { - let player = self.player.as_mut().unwrap(); - - if command.entity_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::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")) } } @@ -202,10 +197,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), ) } @@ -215,12 +209,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, @@ -256,7 +250,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(), @@ -275,18 +269,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, @@ -299,25 +291,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 entity id") + self.kick(TextComponent::text("Interacted with invalid entity id")) } } } @@ -326,9 +318,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 @@ -338,8 +329,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 @@ -350,7 +340,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"); @@ -368,16 +359,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"); @@ -398,20 +391,19 @@ 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); } } 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/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)); + } } -#[derive(FromPrimitive)] +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, 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 316b2374e..3d0f2b368 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -31,6 +31,7 @@ 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")] @@ -93,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()); @@ -160,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 remove_client(&mut self, token: &Token) { - let client = self.current_clients.remove(token).unwrap(); - let client = client.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()])) - } - } - - // here is where the magic happens - // TODO: do this in a world - pub async fn spawn_player(&mut self, client: &mut Client) { - // This code follows the vanilla packet order + 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_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 + 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, player: &mut Player) { + // This code follows the vanilla packet order + 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);