Refactor Client

This commit is contained in:
Snowiiii
2024-08-24 14:51:43 +02:00
parent ae028da8ca
commit c7f15dee3d
9 changed files with 373 additions and 330 deletions

View File

@@ -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;
}
}

View File

@@ -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<Vec<Option<&'a Item>>>,
carried_item: Option<&'a Item>,
) {
let player = self.player.as_ref().unwrap();
let slots: Vec<Slot> = {
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,

View File

@@ -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<Player>,
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<GameProfile>,
pub config: Option<PlayerConfig>,
@@ -75,6 +78,8 @@ pub struct Client {
enc: PacketEncoder,
dec: PacketDecoder,
pub client_packets_queue: VecDeque<RawPacket>,
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<P: ClientPacket>(&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());

View File

@@ -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);
}
}

View File

@@ -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<Str
"@a" | "@e" => 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);
};
};
};

View File

@@ -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<String>),
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,
}

View File

@@ -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,

View File

@@ -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<Token, Rc<RefCell<Client>>> = HashMap::new();
let mut clients: HashMap<Token, Client> = HashMap::new();
let mut players: HashMap<Rc<Token>, Rc<RefCell<Player>>> = 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;
}
}
}
}

View File

@@ -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<Registry>,
pub current_clients: HashMap<Rc<Token>, Rc<RefCell<Client>>>,
pub current_players: HashMap<Rc<Token>, Rc<RefCell<Player>>>,
// TODO: replace with HashMap <World, Player>
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<Token>, client: Rc<RefCell<Client>>) {
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<Token>, client: Client) -> Rc<RefCell<Player>> {
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<RefMut<Client>> {
for (_, client) in self.current_clients.iter().filter(|c| c.0 != &from.token) {
pub fn get_by_entityid(&self, from: &Player, id: EntityId) -> Option<RefMut<Player>> {
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<P>(&self, from: &mut Client, packet: &P)
pub fn broadcast_packet<P>(&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);