mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
Merge branch 'Snowiiii:master' into master
This commit is contained in:
12
.dockerignore
Normal file
12
.dockerignore
Normal 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
38
Cargo.lock
generated
@@ -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
13
Dockerfile
Normal 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"]
|
||||
15
README.md
15
README.md
@@ -96,6 +96,21 @@ Then run:
|
||||
RUSTFLAGS="-C target-cpu=native" cargo run --release
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
Experimental Docker support is available.
|
||||
The image is currently not published anywhere, but you can use the following command to build it:
|
||||
|
||||
```shell
|
||||
docker build . -t pumpkin
|
||||
```
|
||||
|
||||
To run it use the following command:
|
||||
|
||||
```shell
|
||||
docker run --rm -v "./world:/pumpkin/world" pumpkin
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||
|
||||
@@ -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,
|
||||
|
||||
89
pumpkin-inventory/src/window_property.rs
Normal file
89
pumpkin-inventory/src/window_property.rs
Normal 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,
|
||||
}
|
||||
14
pumpkin-protocol/src/client/play/c_close_container.rs
Normal file
14
pumpkin-protocol/src/client/play/c_close_container.rs
Normal 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 }
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
pumpkin-protocol/src/client/play/c_set_container_property.rs
Normal file
19
pumpkin-protocol/src/client/play/c_set_container_property.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
38
pumpkin-protocol/src/client/play/c_teleport_entity.rs
Normal file
38
pumpkin-protocol/src/client/play/c_teleport_entity.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
15
pumpkin-protocol/src/client/play/c_unload_chunk.rs
Normal file
15
pumpkin-protocol/src/client/play/c_unload_chunk.rs
Normal 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 }
|
||||
}
|
||||
}
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
8
pumpkin-protocol/src/server/play/s_close_container.rs
Normal file
8
pumpkin-protocol/src/server/play/s_close_container.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use pumpkin_macros::packet;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[packet(0x0F)]
|
||||
pub struct SCloseContainer {
|
||||
pub window_id: u8,
|
||||
}
|
||||
@@ -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(),
|
||||
})
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user