Merge branch 'Snowiiii:master' into master

This commit is contained in:
we sell insurance
2024-08-24 18:22:25 -05:00
committed by GitHub
34 changed files with 748 additions and 365 deletions

12
.dockerignore Normal file
View File

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

38
Cargo.lock generated
View File

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

13
Dockerfile Normal file
View File

@@ -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"]

View File

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

View File

@@ -1,9 +1,10 @@
use num_derive::ToPrimitive;
use num_derive::{FromPrimitive, ToPrimitive};
pub mod player;
pub mod window_property;
/// https://wiki.vg/Inventory
#[derive(Debug, ToPrimitive, Clone)]
#[derive(Debug, ToPrimitive, FromPrimitive, Clone)]
pub enum WindowType {
// not used
Generic9x1,

View File

@@ -0,0 +1,89 @@
use num_derive::ToPrimitive;
use num_traits::ToPrimitive;
pub trait WindowPropertyTrait {
fn to_id(self) -> i16;
}
impl<T: ToPrimitive> WindowPropertyTrait for T {
fn to_id(self) -> i16 {
self.to_i16().unwrap()
}
}
pub struct WindowProperty<T: WindowPropertyTrait> {
window_property: T,
value: i16,
}
impl<T: WindowPropertyTrait> WindowProperty<T> {
pub fn new(window_property: T, value: i16) -> Self {
Self {
window_property,
value,
}
}
pub fn into_tuple(self) -> (i16, i16) {
(self.window_property.to_id(), self.value)
}
}
#[derive(ToPrimitive)]
pub enum Furnace {
FireIcon,
MaximumFuelBurnTime,
ProgressArrow,
MaximumProgress,
}
pub enum EnchantmentTable {
LevelRequirement { slot: u8 },
EnchantmentSeed,
EnchantmentId { slot: u8 },
EnchantmentLevel { slot: u8 },
}
impl WindowPropertyTrait for EnchantmentTable {
fn to_id(self) -> i16 {
use EnchantmentTable::*;
(match self {
LevelRequirement { slot } => slot,
EnchantmentSeed => 3,
EnchantmentId { slot } => 4 + slot,
EnchantmentLevel { slot } => 7 + slot,
}) as i16
}
}
#[derive(ToPrimitive)]
pub enum Beacon {
PowerLevel,
FirstPotionEffect,
SecondPotionEffect,
}
#[derive(ToPrimitive)]
pub enum Anvil {
RepairCost,
}
#[derive(ToPrimitive)]
pub enum BrewingStand {
BrewTime,
FuelTime,
}
#[derive(ToPrimitive)]
pub enum Stonecutter {
SelectedRecipe,
}
#[derive(ToPrimitive)]
pub enum Loom {
SelectedPattern,
}
#[derive(ToPrimitive)]
pub enum Lectern {
PageNumber,
}

View File

@@ -0,0 +1,14 @@
use pumpkin_macros::packet;
use serde::Serialize;
#[derive(Serialize)]
#[packet(0x12)]
pub struct CCloseContainer {
window_id: u8,
}
impl CCloseContainer {
pub const fn new(window_id: u8) -> Self {
Self { window_id }
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,19 @@
use pumpkin_macros::packet;
use serde::Serialize;
#[derive(Serialize)]
#[packet(0x14)]
pub struct CSetContainerProperty {
window_id: u8,
property: i16,
value: i16,
}
impl CSetContainerProperty {
pub const fn new(window_id: u8, property: i16, value: i16) -> Self {
Self {
window_id,
property,
value,
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,38 @@
use pumpkin_macros::packet;
use serde::Serialize;
use crate::VarInt;
#[derive(Serialize)]
#[packet(0x70)]
pub struct CTeleportEntitiy {
entity_id: VarInt,
x: f64,
y: f64,
z: f64,
yaw: u8,
pitch: u8,
on_ground: bool,
}
impl CTeleportEntitiy {
pub fn new(
entity_id: VarInt,
x: f64,
y: f64,
z: f64,
yaw: u8,
pitch: u8,
on_ground: bool,
) -> Self {
Self {
entity_id,
x,
y,
z,
yaw,
pitch,
on_ground,
}
}
}

View File

@@ -0,0 +1,15 @@
use pumpkin_macros::packet;
use serde::Serialize;
#[derive(Serialize)]
#[packet(0x21)]
pub struct CUnloadChunk {
z: i32,
x: i32,
}
impl CUnloadChunk {
pub fn new(x: i32, z: i32) -> Self {
Self { z, x }
}
}

View File

@@ -5,6 +5,7 @@ mod c_block_update;
mod c_center_chunk;
mod c_change_difficulty;
mod c_chunk_data;
mod c_close_container;
mod c_disguised_chat_message;
mod c_entity_animation;
mod c_entity_metadata;
@@ -23,6 +24,7 @@ mod c_player_info_update;
mod c_player_remove;
mod c_remove_entities;
mod c_set_container_content;
mod c_set_container_property;
mod c_set_container_slot;
mod c_set_held_item;
mod c_set_title;
@@ -30,8 +32,10 @@ mod c_spawn_player;
mod c_subtitle;
mod c_sync_player_position;
mod c_system_chat_message;
mod c_update_entitiy_pos_rot;
mod c_teleport_entity;
mod c_unload_chunk;
mod c_update_entity_pos;
mod c_update_entity_pos_rot;
mod c_update_entity_rot;
mod c_worldevent;
mod player_action;
@@ -43,6 +47,7 @@ pub use c_block_update::*;
pub use c_center_chunk::*;
pub use c_change_difficulty::*;
pub use c_chunk_data::*;
pub use c_close_container::*;
pub use c_disguised_chat_message::*;
pub use c_entity_animation::*;
pub use c_entity_metadata::*;
@@ -61,6 +66,7 @@ pub use c_player_info_update::*;
pub use c_player_remove::*;
pub use c_remove_entities::*;
pub use c_set_container_content::*;
pub use c_set_container_property::*;
pub use c_set_container_slot::*;
pub use c_set_held_item::*;
pub use c_set_title::*;
@@ -68,8 +74,10 @@ pub use c_spawn_player::*;
pub use c_subtitle::*;
pub use c_sync_player_position::*;
pub use c_system_chat_message::*;
pub use c_update_entitiy_pos_rot::*;
pub use c_teleport_entity::*;
pub use c_unload_chunk::*;
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::*;

View File

@@ -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)]

View File

@@ -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) {

View File

@@ -1,6 +1,7 @@
mod s_chat_command;
mod s_chat_message;
mod s_client_information;
mod s_close_container;
mod s_confirm_teleport;
mod s_interact;
mod s_ping_request;
@@ -18,6 +19,7 @@ mod s_use_item;
pub use s_chat_command::*;
pub use s_chat_message::*;
pub use s_client_information::*;
pub use s_close_container::*;
pub use s_confirm_teleport::*;
pub use s_interact::*;
pub use s_ping_request::*;

View File

@@ -13,7 +13,7 @@ pub struct SChatMessage {
pub timestamp: i64,
pub salt: i64,
pub signature: Option<Bytes>,
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),
})
}

View File

@@ -0,0 +1,8 @@
use pumpkin_macros::packet;
use serde::Deserialize;
#[derive(Deserialize)]
#[packet(0x0F)]
pub struct SCloseContainer {
pub window_id: u8,
}

View File

@@ -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<Self, DeserializerError> {
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(),
})

View File

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

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

@@ -1,10 +1,15 @@
use pumpkin_core::text::TextComponent;
use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait};
use pumpkin_inventory::WindowType;
use pumpkin_protocol::client::play::{COpenScreen, CSetContainerContent, CSetContainerSlot};
use pumpkin_protocol::client::play::{
CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot,
};
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 +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,
@@ -37,14 +42,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 +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(
@@ -75,11 +78,27 @@ 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,
&item.into(),
))
}
/// The official Minecraft client is weird, and will always just close *any* window that is opened when this gets sent
pub fn close_container(&mut self, window_type: WindowType) {
self.client
.send_packet(&CCloseContainer::new(window_type as u8))
}
pub fn set_container_property<T: WindowPropertyTrait>(
&mut self,
window_type: WindowType,
window_property: WindowProperty<T>,
) {
let (id, value) = window_property.into_tuple();
self.client
.send_packet(&CSetContainerProperty::new(window_type as u8, id, value));
}
}

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, 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, SConfirmTeleport, SInteract,
SPlayPingRequest, SPlayerAction, SPlayerCommand, SPlayerPosition,
SPlayerPositionRotation, SPlayerRotation, SSetCreativeSlot, SSetHeldItem, SSwingArm,
SUseItemOn,
},
status::{SStatusPingRequest, SStatusRequest},
},
ClientPacket, ConnectionState, PacketError, RawPacket, ServerPacket,
@@ -57,9 +46,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 +77,8 @@ pub struct Client {
enc: PacketEncoder,
dec: PacketDecoder,
pub client_packets_queue: VecDeque<RawPacket>,
pub make_player: bool,
}
impl Client {
@@ -86,7 +90,6 @@ impl Client {
brand: None,
token,
address,
player: None,
connection_state: ConnectionState::HandShake,
connection,
enc: PacketEncoder::default(),
@@ -94,6 +97,7 @@ impl Client {
encryption: true,
closed: false,
client_packets_queue: VecDeque::new(),
make_player: false,
}
}
@@ -122,10 +126,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 +145,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 +154,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 +223,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 {}", 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 +262,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 +271,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);
@@ -379,6 +285,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,13 +2,15 @@ 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,
};
use num_traits::FromPrimitive;
use pumpkin_core::text::TextComponent;
use pumpkin_entity::EntityId;
use pumpkin_inventory::WindowType;
use pumpkin_protocol::server::play::SCloseContainer;
use pumpkin_protocol::{
client::play::{
Animation, CAcknowledgeBlockChange, CBlockUpdate, CEntityAnimation, CEntityVelocity,
@@ -26,28 +28,34 @@ 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 id == confirm_teleport.teleport_id {
if let Some((id, position)) = self.awaiting_teleport.as_ref() {
if id == &confirm_teleport.teleport_id {
// we should set the pos now to that we requested in the teleport packet, Is may fixed issues when the client sended position packets while being teleported
self.entity.x = position.x;
self.entity.y = position.y;
self.entity.z = position.z;
self.awaiting_teleport = None;
} else {
log::warn!("Teleport id does not match, Weird but okay");
self.kick(TextComponent::text("Wrong teleport id"))
}
player.awaiting_teleport = None;
} else {
self.kick("Send Teleport confirm, but we did not teleport")
self.kick(TextComponent::text(
"Send Teleport confirm, but we did not teleport",
))
}
}
@@ -61,11 +69,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 +82,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 +109,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 +128,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 +154,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 +179,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"))
}
}
@@ -202,10 +205,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 +217,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 +258,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 +277,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 +299,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"))
}
}
}
@@ -326,9 +326,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 +337,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 +348,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 +367,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 +399,30 @@ 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;
self.inventory
.set_slot(packet.slot as usize, packet.clicked_item.to_item(), false);
}
inventory.set_slot(packet.slot as usize, packet.clicked_item.to_item(), false);
// TODO:
// This function will in the future be used to keep track of if the client is in a valid state.
// But this is not possible yet
pub fn handle_close_container(&mut self, _server: &mut Server, packet: SCloseContainer) {
// window_id 0 represents both 9x1 Generic AND inventory here
let Some(_window_type) = WindowType::from_u8(packet.window_id) else {
self.kick(TextComponent::text("Invalid window ID"));
return;
};
}
}

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

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

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,
@@ -29,13 +43,15 @@ pub struct Player {
// TODO: prbly should put this into an Living Entitiy or something
pub velocity: Vector3<f64>,
// Current awaiting teleport id, None if did not teleport
pub awaiting_teleport: Option<VarInt>,
pub teleport_id_count: i32,
// Current awaiting teleport id and location, None if did not teleport
pub awaiting_teleport: Option<(VarInt, Vector3<f64>)>,
}
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,
@@ -48,6 +64,7 @@ impl Player {
current_block_destroy_stage: 0,
velocity: Vector3::new(0.0, 0.0, 0.0),
inventory: PlayerInventory::new(),
teleport_id_count: 0,
gamemode,
}
}
@@ -77,6 +94,122 @@ impl Player {
var7.z / 2.0 - var8.z,
);
}
pub fn teleport(&mut self, x: f64, y: f64, z: f64, yaw: f32, pitch: f32) {
// this is the ultra special magic code used to create the teleport id
self.teleport_id_count += 1;
if self.teleport_id_count == i32::MAX {
self.teleport_id_count = 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((self.teleport_id_count.into(), Vector3::new(x, y, z)));
self.client.send_packet(&CSyncPlayerPosition::new(
x,
y,
z,
yaw,
pitch,
0,
self.teleport_id_count.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)]

View File

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

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(
existing_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.as_ref() {
self.broadcast_packet(
client,
player,
&CSetEntityMetadata::new(
entity_id.into(),
Metadata::new(17, VarInt(0), config.skin_parts),
@@ -294,35 +292,39 @@ 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) {
// 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);
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)
{
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) {
// Check if client is a player
let mut client = client.borrow_mut();
if client.is_player() {
client.send_packet(packet);
}
from.client.send_packet(packet);
for (_, player) in self
.current_players
.iter()
.filter(|c| c.0 != &from.client.token)
{
let mut player = player.borrow_mut();
player.client.send_packet(packet);
}
}
@@ -331,21 +333,18 @@ 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 +357,7 @@ impl Server {
.await;
});
client.send_packet(&CCenterChunk {
player.client.send_packet(&CCenterChunk {
chunk_x: 0.into(),
chunk_z: 0.into(),
});
@@ -382,7 +381,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);