Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
5d6c38d1b2
34
Cargo.lock
generated
34
Cargo.lock
generated
@ -772,6 +772,16 @@ version = "0.4.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "md-5"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"digest 0.10.7",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.4"
|
version = "2.7.4"
|
||||||
@ -1098,6 +1108,7 @@ name = "pumpkin"
|
|||||||
version = "0.1.0-dev"
|
version = "0.1.0-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
|
"bytes",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"digest 0.11.0-pre.9",
|
"digest 0.11.0-pre.9",
|
||||||
"image",
|
"image",
|
||||||
@ -1107,8 +1118,10 @@ dependencies = [
|
|||||||
"num-derive",
|
"num-derive",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"pumpkin-entity",
|
"pumpkin-entity",
|
||||||
|
"pumpkin-inventory",
|
||||||
"pumpkin-protocol",
|
"pumpkin-protocol",
|
||||||
"pumpkin-registry",
|
"pumpkin-registry",
|
||||||
|
"pumpkin-text",
|
||||||
"pumpkin-world",
|
"pumpkin-world",
|
||||||
"rand",
|
"rand",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@ -1132,6 +1145,14 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pumpkin-inventory"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"num-derive",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pumpkin-macros"
|
name = "pumpkin-macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -1148,13 +1169,13 @@ dependencies = [
|
|||||||
"aes",
|
"aes",
|
||||||
"bytes",
|
"bytes",
|
||||||
"cfb8",
|
"cfb8",
|
||||||
"fastnbt",
|
|
||||||
"flate2",
|
"flate2",
|
||||||
"itertools",
|
"itertools",
|
||||||
"log",
|
"log",
|
||||||
"num-derive",
|
"num-derive",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"pumpkin-macros",
|
"pumpkin-macros",
|
||||||
|
"pumpkin-text",
|
||||||
"pumpkin-world",
|
"pumpkin-world",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -1170,9 +1191,19 @@ dependencies = [
|
|||||||
"fastnbt",
|
"fastnbt",
|
||||||
"fastsnbt",
|
"fastsnbt",
|
||||||
"pumpkin-protocol",
|
"pumpkin-protocol",
|
||||||
|
"pumpkin-text",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pumpkin-text"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"fastnbt",
|
||||||
|
"serde",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pumpkin-world"
|
name = "pumpkin-world"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -1894,6 +1925,7 @@ version = "1.10.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
|
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"md-5",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [ "pumpkin-entity", "pumpkin-macros/", "pumpkin-protocol/", "pumpkin-registry/", "pumpkin-world", "pumpkin/" ]
|
members = [ "pumpkin-entity", "pumpkin-inventory", "pumpkin-macros/", "pumpkin-protocol/", "pumpkin-registry/", "pumpkin-world", "pumpkin/" ]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
strip = true
|
||||||
lto = true
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
tokio = { version = "1.39.2", features = ["net", "macros", "rt-multi-thread", "fs", "io-util"] }
|
tokio = { version = "1.39.2", features = ["net", "macros", "rt-multi-thread", "fs", "io-util"] }
|
||||||
|
|||||||
12
README.md
12
README.md
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||

|

|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||

|

|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -27,13 +27,10 @@ Pumpkin is currently under heavy development.
|
|||||||
### Features (WIP)
|
### Features (WIP)
|
||||||
- [x] Configuration (toml)
|
- [x] Configuration (toml)
|
||||||
- [x] Server Status/Ping
|
- [x] Server Status/Ping
|
||||||
- [x] Custom maximum player amout
|
- [x] Custom maximum player amount
|
||||||
- [x] Custom Icon
|
- [x] Custom Icon
|
||||||
- [x] Custom Status (MOTD)
|
- [x] Custom Status (MOTD)
|
||||||
- Login
|
- [x] Login
|
||||||
- [x] Authentication
|
|
||||||
- [x] Encryption
|
|
||||||
- [x] Packet Compression
|
|
||||||
- Player Configuration
|
- Player Configuration
|
||||||
- [x] Registries (biome types, paintings, dimensions)
|
- [x] Registries (biome types, paintings, dimensions)
|
||||||
- [x] Server Brand
|
- [x] Server Brand
|
||||||
@ -42,6 +39,7 @@ Pumpkin is currently under heavy development.
|
|||||||
- [ ] Cookies
|
- [ ] Cookies
|
||||||
- World
|
- World
|
||||||
- [x] World Joining
|
- [x] World Joining
|
||||||
|
- [x] Player Tab-list
|
||||||
- [x] World Loading
|
- [x] World Loading
|
||||||
- [x] Entity Spawning
|
- [x] Entity Spawning
|
||||||
- [ ] Chunk Loading
|
- [ ] Chunk Loading
|
||||||
@ -57,6 +55,8 @@ Pumpkin is currently under heavy development.
|
|||||||
- [ ] Player Inventory
|
- [ ] Player Inventory
|
||||||
- [ ] Player Attack
|
- [ ] Player Attack
|
||||||
- Server
|
- Server
|
||||||
|
- [x] RCON
|
||||||
|
- [x] Inventories
|
||||||
- [x] Chat
|
- [x] Chat
|
||||||
- [x] Commands
|
- [x] Commands
|
||||||
|
|
||||||
|
|||||||
8
pumpkin-inventory/Cargo.toml
Normal file
8
pumpkin-inventory/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "pumpkin-inventory"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
num-traits = "0.2"
|
||||||
|
num-derive = "0.4"
|
||||||
41
pumpkin-inventory/src/lib.rs
Normal file
41
pumpkin-inventory/src/lib.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
use num_derive::ToPrimitive;
|
||||||
|
|
||||||
|
/// https://wiki.vg/Inventory
|
||||||
|
#[derive(Debug, ToPrimitive)]
|
||||||
|
pub enum WindowType {
|
||||||
|
// not used
|
||||||
|
Generic9x1,
|
||||||
|
// not used
|
||||||
|
Generic9x2,
|
||||||
|
// General-purpose 3-row inventory. Used by Chest, minecart with chest, ender chest, and barrel
|
||||||
|
Generic9x3,
|
||||||
|
// not used
|
||||||
|
Generic9x4,
|
||||||
|
// not used
|
||||||
|
Generic9x5,
|
||||||
|
// Used by large chests
|
||||||
|
Generic9x6,
|
||||||
|
// General-purpose 3-by-3 square inventory, used by Dispenser and Dropper
|
||||||
|
Generic3x3,
|
||||||
|
// General-purpose 3-by-3 square inventory, used by the Crafter
|
||||||
|
Craft3x3,
|
||||||
|
Anvil,
|
||||||
|
Beacon,
|
||||||
|
BlastFurance,
|
||||||
|
BrewingStand,
|
||||||
|
CraftingTable,
|
||||||
|
EnchantmentTable,
|
||||||
|
Furnace,
|
||||||
|
Grindstone,
|
||||||
|
// Hopper or minecart with hopper
|
||||||
|
Hopper,
|
||||||
|
Lectern,
|
||||||
|
Loom,
|
||||||
|
// Villager, Wandering Trader
|
||||||
|
Merchant,
|
||||||
|
ShulkerBox,
|
||||||
|
SmithingTable,
|
||||||
|
Smoker,
|
||||||
|
CartographyTable,
|
||||||
|
Stonecutter,
|
||||||
|
}
|
||||||
@ -4,9 +4,9 @@ version.workspace = true
|
|||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
pumpkin-macros = { path = "../pumpkin-macros" }
|
pumpkin-macros = { path = "../pumpkin-macros" }
|
||||||
pumpkin-world = { path = "../pumpkin-world" }
|
pumpkin-world = { path = "../pumpkin-world" }
|
||||||
|
pumpkin-text = { path = "../pumpkin-text" }
|
||||||
|
|
||||||
bytes = "1.7"
|
bytes = "1.7"
|
||||||
|
|
||||||
@ -23,11 +23,9 @@ log = "0.4"
|
|||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
num-derive = "0.4"
|
num-derive = "0.4"
|
||||||
|
|
||||||
# for text component
|
|
||||||
fastnbt = { git = "https://github.com/owengage/fastnbt.git" }
|
|
||||||
|
|
||||||
# encryption
|
# encryption
|
||||||
aes = "0.8.4"
|
aes = "0.8.4"
|
||||||
cfb8 = "0.8.1"
|
cfb8 = "0.8.1"
|
||||||
take_mut = "0.2.2"
|
take_mut = "0.2.2"
|
||||||
itertools = "0.13.0"
|
itertools = "0.13.0"
|
||||||
|
fastnbt = "2.5.0"
|
||||||
|
|||||||
31
pumpkin-protocol/src/client/config/c_add_resource_pack.rs
Normal file
31
pumpkin-protocol/src/client/config/c_add_resource_pack.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use pumpkin_macros::packet;
|
||||||
|
use pumpkin_text::TextComponent;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[packet(0x09)]
|
||||||
|
pub struct CConfigAddResourcePack {
|
||||||
|
uuid: uuid::Uuid,
|
||||||
|
url: String,
|
||||||
|
hash: String,
|
||||||
|
forced: bool,
|
||||||
|
prompt_message: Option<TextComponent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CConfigAddResourcePack {
|
||||||
|
pub fn new(
|
||||||
|
uuid: uuid::Uuid,
|
||||||
|
url: String,
|
||||||
|
hash: String,
|
||||||
|
forced: bool,
|
||||||
|
prompt_message: Option<TextComponent>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
uuid,
|
||||||
|
url,
|
||||||
|
hash,
|
||||||
|
forced,
|
||||||
|
prompt_message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,9 @@
|
|||||||
use pumpkin_macros::packet;
|
use pumpkin_macros::packet;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::{bytebuf::ByteBuffer, ClientPacket};
|
use crate::{bytebuf::ByteBuffer, ClientPacket};
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
#[packet(0x01)]
|
#[packet(0x01)]
|
||||||
pub struct CPluginMessage<'a> {
|
pub struct CPluginMessage<'a> {
|
||||||
channel: &'a str,
|
channel: &'a str,
|
||||||
@ -13,10 +15,3 @@ impl<'a> CPluginMessage<'a> {
|
|||||||
Self { channel, data }
|
Self { channel, data }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ClientPacket for CPluginMessage<'a> {
|
|
||||||
fn write(&self, bytebuf: &mut ByteBuffer) {
|
|
||||||
bytebuf.put_string(self.channel);
|
|
||||||
bytebuf.put_slice(self.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
mod c_add_resource_pack;
|
||||||
mod c_config_disconnect;
|
mod c_config_disconnect;
|
||||||
mod c_cookie_request;
|
mod c_cookie_request;
|
||||||
mod c_finish_config;
|
mod c_finish_config;
|
||||||
@ -5,6 +6,7 @@ mod c_known_packs;
|
|||||||
mod c_plugin_message;
|
mod c_plugin_message;
|
||||||
mod c_registry_data;
|
mod c_registry_data;
|
||||||
|
|
||||||
|
pub use c_add_resource_pack::*;
|
||||||
pub use c_config_disconnect::*;
|
pub use c_config_disconnect::*;
|
||||||
pub use c_cookie_request::*;
|
pub use c_cookie_request::*;
|
||||||
pub use c_finish_config::*;
|
pub use c_finish_config::*;
|
||||||
|
|||||||
@ -1,31 +1,31 @@
|
|||||||
use pumpkin_macros::packet;
|
use pumpkin_macros::packet;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::{RawBytes, VarInt};
|
use crate::VarInt;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[packet(0x01)]
|
#[packet(0x01)]
|
||||||
pub struct CEncryptionRequest<'a> {
|
pub struct CEncryptionRequest<'a> {
|
||||||
server_id: &'a str, // 20
|
server_id: &'a str, // 20
|
||||||
public_key_length: VarInt,
|
public_key_length: VarInt,
|
||||||
public_key: RawBytes<'a>,
|
public_key: &'a [u8],
|
||||||
verify_token_length: VarInt,
|
verify_token_length: VarInt,
|
||||||
verify_token: RawBytes<'a>,
|
verify_token: &'a [u8],
|
||||||
should_authenticate: bool,
|
should_authenticate: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CEncryptionRequest<'a> {
|
impl<'a> CEncryptionRequest<'a> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
server_id: &'a str,
|
server_id: &'a str,
|
||||||
public_key: RawBytes<'a>,
|
public_key: &'a [u8],
|
||||||
verify_token: RawBytes<'a>,
|
verify_token: &'a [u8],
|
||||||
should_authenticate: bool,
|
should_authenticate: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
server_id,
|
server_id,
|
||||||
public_key_length: public_key.0.len().into(),
|
public_key_length: public_key.len().into(),
|
||||||
public_key,
|
public_key,
|
||||||
verify_token_length: verify_token.0.len().into(),
|
verify_token_length: verify_token.len().into(),
|
||||||
verify_token,
|
verify_token,
|
||||||
should_authenticate,
|
should_authenticate,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use crate::{bytebuf::ByteBuffer, ClientPacket, Property};
|
|||||||
#[packet(0x02)]
|
#[packet(0x02)]
|
||||||
pub struct CLoginSuccess<'a> {
|
pub struct CLoginSuccess<'a> {
|
||||||
pub uuid: uuid::Uuid,
|
pub uuid: uuid::Uuid,
|
||||||
pub username: String, // 16
|
pub username: &'a str, // 16
|
||||||
pub properties: &'a [Property],
|
pub properties: &'a [Property],
|
||||||
pub strict_error_handling: bool,
|
pub strict_error_handling: bool,
|
||||||
}
|
}
|
||||||
@ -13,7 +13,7 @@ pub struct CLoginSuccess<'a> {
|
|||||||
impl<'a> CLoginSuccess<'a> {
|
impl<'a> CLoginSuccess<'a> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
uuid: uuid::Uuid,
|
uuid: uuid::Uuid,
|
||||||
username: String,
|
username: &'a str,
|
||||||
properties: &'a [Property],
|
properties: &'a [Property],
|
||||||
strict_error_handling: bool,
|
strict_error_handling: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|||||||
15
pumpkin-protocol/src/client/play/c_actionbar.rs
Normal file
15
pumpkin-protocol/src/client/play/c_actionbar.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use pumpkin_macros::packet;
|
||||||
|
use pumpkin_text::TextComponent;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[packet(0x4C)]
|
||||||
|
pub struct CActionBar {
|
||||||
|
action_bar: TextComponent,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CActionBar {
|
||||||
|
pub fn new(action_bar: TextComponent) -> Self {
|
||||||
|
Self { action_bar }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
use pumpkin_macros::packet;
|
use pumpkin_macros::packet;
|
||||||
|
use pumpkin_text::TextComponent;
|
||||||
|
|
||||||
use crate::{text::TextComponent, ClientPacket, VarInt};
|
use crate::{ClientPacket, VarInt};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
#[packet(0x1E)]
|
#[packet(0x1E)]
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use serde::Serialize;
|
|||||||
|
|
||||||
use crate::VarInt;
|
use crate::VarInt;
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize)]
|
||||||
#[packet(0x03)]
|
#[packet(0x03)]
|
||||||
pub struct CEntityAnimation {
|
pub struct CEntityAnimation {
|
||||||
entity_id: VarInt,
|
entity_id: VarInt,
|
||||||
|
|||||||
@ -3,23 +3,25 @@ use serde::Serialize;
|
|||||||
|
|
||||||
use crate::VarInt;
|
use crate::VarInt;
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize)]
|
||||||
#[packet(0x58)]
|
#[packet(0x58)]
|
||||||
pub struct CSetEntityMetadata {
|
pub struct CSetEntityMetadata {
|
||||||
entity_id: VarInt,
|
entity_id: VarInt,
|
||||||
metadata: Vec<Metadata>,
|
metadata: Metadata,
|
||||||
|
end: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CSetEntityMetadata {
|
impl CSetEntityMetadata {
|
||||||
pub fn new(entity_id: VarInt, metadata: Vec<Metadata>) -> Self {
|
pub fn new(entity_id: VarInt, metadata: Metadata) -> Self {
|
||||||
Self {
|
Self {
|
||||||
entity_id,
|
entity_id,
|
||||||
metadata,
|
metadata,
|
||||||
|
end: 255,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize)]
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
index: u8,
|
index: u8,
|
||||||
typ: VarInt,
|
typ: VarInt,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use serde::Serialize;
|
|||||||
|
|
||||||
use crate::VarInt;
|
use crate::VarInt;
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize)]
|
||||||
#[packet(0x48)]
|
#[packet(0x48)]
|
||||||
pub struct CHeadRot {
|
pub struct CHeadRot {
|
||||||
entity_id: VarInt,
|
entity_id: VarInt,
|
||||||
|
|||||||
@ -5,11 +5,11 @@ use crate::VarInt;
|
|||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[packet(0x2B)]
|
#[packet(0x2B)]
|
||||||
pub struct CLogin {
|
pub struct CLogin<'a> {
|
||||||
entity_id: i32,
|
entity_id: i32,
|
||||||
is_hardcore: bool,
|
is_hardcore: bool,
|
||||||
dimension_count: VarInt,
|
dimension_count: VarInt,
|
||||||
dimension_names: Vec<String>,
|
dimension_names: &'a [&'a str],
|
||||||
max_players: VarInt,
|
max_players: VarInt,
|
||||||
view_distance: VarInt,
|
view_distance: VarInt,
|
||||||
simulated_distance: VarInt,
|
simulated_distance: VarInt,
|
||||||
@ -17,7 +17,7 @@ pub struct CLogin {
|
|||||||
enabled_respawn_screen: bool,
|
enabled_respawn_screen: bool,
|
||||||
limited_crafting: bool,
|
limited_crafting: bool,
|
||||||
dimension_type: VarInt,
|
dimension_type: VarInt,
|
||||||
dimension_name: String,
|
dimension_name: &'a str,
|
||||||
hashed_seed: i64,
|
hashed_seed: i64,
|
||||||
game_mode: u8,
|
game_mode: u8,
|
||||||
previous_gamemode: i8,
|
previous_gamemode: i8,
|
||||||
@ -28,12 +28,12 @@ pub struct CLogin {
|
|||||||
enforce_secure_chat: bool,
|
enforce_secure_chat: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CLogin {
|
impl<'a> CLogin<'a> {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
entity_id: i32,
|
entity_id: i32,
|
||||||
is_hardcore: bool,
|
is_hardcore: bool,
|
||||||
dimension_names: Vec<String>,
|
dimension_names: &'a [&'a str],
|
||||||
max_players: VarInt,
|
max_players: VarInt,
|
||||||
view_distance: VarInt,
|
view_distance: VarInt,
|
||||||
simulated_distance: VarInt,
|
simulated_distance: VarInt,
|
||||||
@ -41,7 +41,7 @@ impl CLogin {
|
|||||||
enabled_respawn_screen: bool,
|
enabled_respawn_screen: bool,
|
||||||
limited_crafting: bool,
|
limited_crafting: bool,
|
||||||
dimension_type: VarInt,
|
dimension_type: VarInt,
|
||||||
dimension_name: String,
|
dimension_name: &'a str,
|
||||||
hashed_seed: i64,
|
hashed_seed: i64,
|
||||||
game_mode: u8,
|
game_mode: u8,
|
||||||
previous_gamemode: i8,
|
previous_gamemode: i8,
|
||||||
|
|||||||
23
pumpkin-protocol/src/client/play/c_open_screen.rs
Normal file
23
pumpkin-protocol/src/client/play/c_open_screen.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use pumpkin_macros::packet;
|
||||||
|
use pumpkin_text::TextComponent;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::VarInt;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[packet(0x33)]
|
||||||
|
pub struct COpenScreen {
|
||||||
|
window_id: VarInt,
|
||||||
|
window_type: VarInt,
|
||||||
|
window_title: TextComponent,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl COpenScreen {
|
||||||
|
pub fn new(window_id: VarInt, window_type: VarInt, window_title: TextComponent) -> Self {
|
||||||
|
Self {
|
||||||
|
window_id,
|
||||||
|
window_type,
|
||||||
|
window_title,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,7 @@
|
|||||||
use pumpkin_macros::packet;
|
use pumpkin_macros::packet;
|
||||||
|
use pumpkin_text::TextComponent;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::text::TextComponent;
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[packet(0x1D)]
|
#[packet(0x1D)]
|
||||||
pub struct CPlayDisconnect {
|
pub struct CPlayDisconnect {
|
||||||
|
|||||||
@ -1,22 +1,26 @@
|
|||||||
use num_derive::{FromPrimitive, ToPrimitive};
|
use num_derive::{FromPrimitive, ToPrimitive};
|
||||||
use pumpkin_macros::packet;
|
use pumpkin_macros::packet;
|
||||||
|
use pumpkin_text::TextComponent;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::{text::TextComponent, VarInt};
|
use crate::{uuid::UUID, BitSet, VarInt};
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize)]
|
||||||
#[packet(0x39)]
|
#[packet(0x39)]
|
||||||
pub struct CPlayerChatMessage<'a> {
|
pub struct CPlayerChatMessage<'a> {
|
||||||
sender: uuid::Uuid,
|
sender: UUID,
|
||||||
index: VarInt,
|
index: VarInt,
|
||||||
message_signature: Option<&'a [u8]>,
|
message_signature: Option<&'a [u8]>,
|
||||||
message: String,
|
message: String,
|
||||||
timestamp: i64,
|
timestamp: i64,
|
||||||
salt: i64,
|
salt: i64,
|
||||||
|
previous_messages_count: VarInt,
|
||||||
previous_messages: &'a [PreviousMessage<'a>], // max 20
|
previous_messages: &'a [PreviousMessage<'a>], // max 20
|
||||||
unsigned_content: Option<TextComponent>,
|
unsigned_content: Option<TextComponent>,
|
||||||
/// See `FilterType`
|
/// See `FilterType`
|
||||||
filter_type: VarInt,
|
filter_type: VarInt,
|
||||||
|
// TODO: THIS IS A HACK, We currently don't support writing or reading bitsets
|
||||||
|
filter_type_bits: bool,
|
||||||
chat_type: VarInt,
|
chat_type: VarInt,
|
||||||
sender_name: TextComponent,
|
sender_name: TextComponent,
|
||||||
target_name: Option<TextComponent>,
|
target_name: Option<TextComponent>,
|
||||||
@ -25,7 +29,7 @@ pub struct CPlayerChatMessage<'a> {
|
|||||||
impl<'a> CPlayerChatMessage<'a> {
|
impl<'a> CPlayerChatMessage<'a> {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
sender: uuid::Uuid,
|
sender: UUID,
|
||||||
index: VarInt,
|
index: VarInt,
|
||||||
message_signature: Option<&'a [u8]>,
|
message_signature: Option<&'a [u8]>,
|
||||||
message: String,
|
message: String,
|
||||||
@ -45,9 +49,11 @@ impl<'a> CPlayerChatMessage<'a> {
|
|||||||
message,
|
message,
|
||||||
timestamp,
|
timestamp,
|
||||||
salt,
|
salt,
|
||||||
|
previous_messages_count: previous_messages.len().into(),
|
||||||
previous_messages,
|
previous_messages,
|
||||||
unsigned_content,
|
unsigned_content,
|
||||||
filter_type,
|
filter_type,
|
||||||
|
filter_type_bits: false,
|
||||||
chat_type,
|
chat_type,
|
||||||
sender_name,
|
sender_name,
|
||||||
target_name,
|
target_name,
|
||||||
|
|||||||
20
pumpkin-protocol/src/client/play/c_player_remove.rs
Normal file
20
pumpkin-protocol/src/client/play/c_player_remove.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use pumpkin_macros::packet;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{uuid::UUID, VarInt};
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[packet(0x3D)]
|
||||||
|
pub struct CRemovePlayerInfo<'a> {
|
||||||
|
players_count: VarInt,
|
||||||
|
players: &'a [UUID],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CRemovePlayerInfo<'a> {
|
||||||
|
pub fn new(players_count: VarInt, players: &'a [UUID]) -> Self {
|
||||||
|
Self {
|
||||||
|
players_count,
|
||||||
|
players,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,14 +3,18 @@ use serde::Serialize;
|
|||||||
|
|
||||||
use crate::VarInt;
|
use crate::VarInt;
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize)]
|
||||||
#[packet(0x42)]
|
#[packet(0x42)]
|
||||||
pub struct CRemoveEntities {
|
pub struct CRemoveEntities<'a> {
|
||||||
entitiy_ids: Vec<VarInt>,
|
count: VarInt,
|
||||||
|
entitiy_ids: &'a [VarInt],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CRemoveEntities {
|
impl<'a> CRemoveEntities<'a> {
|
||||||
pub fn new(entitiy_ids: Vec<VarInt>) -> Self {
|
pub fn new(entitiy_ids: &'a [VarInt]) -> Self {
|
||||||
Self { entitiy_ids }
|
Self {
|
||||||
|
count: VarInt(entitiy_ids.len() as i32),
|
||||||
|
entitiy_ids,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
pumpkin-protocol/src/client/play/c_set_title.rs
Normal file
15
pumpkin-protocol/src/client/play/c_set_title.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use pumpkin_macros::packet;
|
||||||
|
use pumpkin_text::TextComponent;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[packet(0x65)]
|
||||||
|
pub struct CTitleText {
|
||||||
|
title: TextComponent,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CTitleText {
|
||||||
|
pub fn new(title: TextComponent) -> Self {
|
||||||
|
Self { title }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +1,13 @@
|
|||||||
use pumpkin_macros::packet;
|
use pumpkin_macros::packet;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::{ClientPacket, VarInt};
|
use crate::{uuid::UUID, VarInt};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Serialize)]
|
||||||
#[packet(0x01)]
|
#[packet(0x01)]
|
||||||
pub struct CSpawnEntity {
|
pub struct CSpawnEntity {
|
||||||
entity_id: VarInt,
|
entity_id: VarInt,
|
||||||
entity_uuid: uuid::Uuid,
|
entity_uuid: UUID,
|
||||||
typ: VarInt,
|
typ: VarInt,
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
@ -24,18 +25,18 @@ impl CSpawnEntity {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
entity_id: VarInt,
|
entity_id: VarInt,
|
||||||
entity_uuid: uuid::Uuid,
|
entity_uuid: UUID,
|
||||||
typ: VarInt,
|
typ: VarInt,
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
z: f64,
|
z: f64,
|
||||||
pitch: u8, // angle
|
pitch: f32, // angle
|
||||||
yaw: u8, // angle
|
yaw: f32, // angle
|
||||||
head_yaw: u8, // angle
|
head_yaw: f32, // angle
|
||||||
data: VarInt,
|
data: VarInt,
|
||||||
velocity_x: i16,
|
velocity_x: f32,
|
||||||
velocity_y: i16,
|
velocity_y: f32,
|
||||||
velocity_z: i16,
|
velocity_z: f32,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
entity_id,
|
entity_id,
|
||||||
@ -44,31 +45,13 @@ impl CSpawnEntity {
|
|||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
z,
|
z,
|
||||||
pitch,
|
pitch: (pitch * 256.0 / 360.0).floor() as u8,
|
||||||
yaw,
|
yaw: (yaw * 256.0 / 360.0).floor() as u8,
|
||||||
head_yaw,
|
head_yaw: (head_yaw * 256.0 / 360.0).floor() as u8,
|
||||||
data,
|
data,
|
||||||
velocity_x,
|
velocity_x: (velocity_x.clamp(-3.9, 3.9) * 8000.0) as i16,
|
||||||
velocity_y,
|
velocity_y: (velocity_y.clamp(-3.9, 3.9) * 8000.0) as i16,
|
||||||
velocity_z,
|
velocity_z: (velocity_z.clamp(-3.9, 3.9) * 8000.0) as i16,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientPacket for CSpawnEntity {
|
|
||||||
fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) {
|
|
||||||
bytebuf.put_var_int(&self.entity_id);
|
|
||||||
bytebuf.put_uuid(self.entity_uuid);
|
|
||||||
bytebuf.put_var_int(&self.typ);
|
|
||||||
bytebuf.put_f64(self.x);
|
|
||||||
bytebuf.put_f64(self.y);
|
|
||||||
bytebuf.put_f64(self.z);
|
|
||||||
bytebuf.put_u8(self.pitch);
|
|
||||||
bytebuf.put_u8(self.yaw);
|
|
||||||
bytebuf.put_u8(self.head_yaw);
|
|
||||||
bytebuf.put_var_int(&self.data);
|
|
||||||
bytebuf.put_i16(self.velocity_x);
|
|
||||||
bytebuf.put_i16(self.velocity_y);
|
|
||||||
bytebuf.put_i16(self.velocity_z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
15
pumpkin-protocol/src/client/play/c_subtitle.rs
Normal file
15
pumpkin-protocol/src/client/play/c_subtitle.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use pumpkin_macros::packet;
|
||||||
|
use pumpkin_text::TextComponent;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[packet(0x63)]
|
||||||
|
pub struct CSubtitle {
|
||||||
|
subtitle: TextComponent,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CSubtitle {
|
||||||
|
pub fn new(subtitle: TextComponent) -> Self {
|
||||||
|
Self { subtitle }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +1,8 @@
|
|||||||
use pumpkin_macros::packet;
|
use pumpkin_macros::packet;
|
||||||
|
use pumpkin_text::TextComponent;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::text::TextComponent;
|
#[derive(Serialize)]
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
|
||||||
#[packet(0x6C)]
|
#[packet(0x6C)]
|
||||||
pub struct CSystemChatMessge {
|
pub struct CSystemChatMessge {
|
||||||
content: TextComponent,
|
content: TextComponent,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use serde::Serialize;
|
|||||||
|
|
||||||
use crate::VarInt;
|
use crate::VarInt;
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize)]
|
||||||
#[packet(0x2F)]
|
#[packet(0x2F)]
|
||||||
pub struct CUpdateEntityPosRot {
|
pub struct CUpdateEntityPosRot {
|
||||||
entity_id: VarInt,
|
entity_id: VarInt,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use serde::Serialize;
|
|||||||
|
|
||||||
use crate::VarInt;
|
use crate::VarInt;
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize)]
|
||||||
#[packet(0x2E)]
|
#[packet(0x2E)]
|
||||||
pub struct CUpdateEntityPos {
|
pub struct CUpdateEntityPos {
|
||||||
entity_id: VarInt,
|
entity_id: VarInt,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use serde::Serialize;
|
|||||||
|
|
||||||
use crate::VarInt;
|
use crate::VarInt;
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize)]
|
||||||
#[packet(0x30)]
|
#[packet(0x30)]
|
||||||
pub struct CUpdateEntityRot {
|
pub struct CUpdateEntityRot {
|
||||||
entity_id: VarInt,
|
entity_id: VarInt,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
mod c_center_chunk;
|
mod c_center_chunk;
|
||||||
|
mod c_actionbar;
|
||||||
mod c_change_difficulty;
|
mod c_change_difficulty;
|
||||||
mod c_chunk_data;
|
mod c_chunk_data;
|
||||||
mod c_chunk_data_update_light;
|
mod c_chunk_data_update_light;
|
||||||
@ -8,13 +9,17 @@ mod c_entity_metadata;
|
|||||||
mod c_game_event;
|
mod c_game_event;
|
||||||
mod c_head_rot;
|
mod c_head_rot;
|
||||||
mod c_login;
|
mod c_login;
|
||||||
|
mod c_open_screen;
|
||||||
mod c_play_disconnect;
|
mod c_play_disconnect;
|
||||||
mod c_player_abilities;
|
mod c_player_abilities;
|
||||||
mod c_player_chat_message;
|
mod c_player_chat_message;
|
||||||
mod c_player_info_update;
|
mod c_player_info_update;
|
||||||
|
mod c_player_remove;
|
||||||
mod c_remove_entities;
|
mod c_remove_entities;
|
||||||
mod c_set_held_item;
|
mod c_set_held_item;
|
||||||
|
mod c_set_title;
|
||||||
mod c_spawn_player;
|
mod c_spawn_player;
|
||||||
|
mod c_subtitle;
|
||||||
mod c_sync_player_position;
|
mod c_sync_player_position;
|
||||||
mod c_system_chat_message;
|
mod c_system_chat_message;
|
||||||
mod c_update_entitiy_pos_rot;
|
mod c_update_entitiy_pos_rot;
|
||||||
@ -23,6 +28,7 @@ mod c_update_entity_rot;
|
|||||||
mod player_action;
|
mod player_action;
|
||||||
|
|
||||||
pub use c_center_chunk::*;
|
pub use c_center_chunk::*;
|
||||||
|
pub use c_actionbar::*;
|
||||||
pub use c_change_difficulty::*;
|
pub use c_change_difficulty::*;
|
||||||
pub use c_chunk_data::*;
|
pub use c_chunk_data::*;
|
||||||
pub use c_chunk_data_update_light::*;
|
pub use c_chunk_data_update_light::*;
|
||||||
@ -32,13 +38,17 @@ pub use c_entity_metadata::*;
|
|||||||
pub use c_game_event::*;
|
pub use c_game_event::*;
|
||||||
pub use c_head_rot::*;
|
pub use c_head_rot::*;
|
||||||
pub use c_login::*;
|
pub use c_login::*;
|
||||||
|
pub use c_open_screen::*;
|
||||||
pub use c_play_disconnect::*;
|
pub use c_play_disconnect::*;
|
||||||
pub use c_player_abilities::*;
|
pub use c_player_abilities::*;
|
||||||
pub use c_player_chat_message::*;
|
pub use c_player_chat_message::*;
|
||||||
pub use c_player_info_update::*;
|
pub use c_player_info_update::*;
|
||||||
|
pub use c_player_remove::*;
|
||||||
pub use c_remove_entities::*;
|
pub use c_remove_entities::*;
|
||||||
pub use c_set_held_item::*;
|
pub use c_set_held_item::*;
|
||||||
|
pub use c_set_title::*;
|
||||||
pub use c_spawn_player::*;
|
pub use c_spawn_player::*;
|
||||||
|
pub use c_subtitle::*;
|
||||||
pub use c_sync_player_position::*;
|
pub use c_sync_player_position::*;
|
||||||
pub use c_system_chat_message::*;
|
pub use c_system_chat_message::*;
|
||||||
pub use c_update_entitiy_pos_rot::*;
|
pub use c_update_entitiy_pos_rot::*;
|
||||||
|
|||||||
@ -2,13 +2,13 @@ use std::io::{self, Write};
|
|||||||
|
|
||||||
use bytebuf::{packet_id::Packet, ByteBuffer, DeserializerError};
|
use bytebuf::{packet_id::Packet, ByteBuffer, DeserializerError};
|
||||||
use bytes::Buf;
|
use bytes::Buf;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub mod bytebuf;
|
pub mod bytebuf;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod text;
|
pub mod uuid;
|
||||||
|
|
||||||
pub mod packet_decoder;
|
pub mod packet_decoder;
|
||||||
pub mod packet_encoder;
|
pub mod packet_encoder;
|
||||||
@ -91,16 +91,6 @@ impl VarInt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RawBytes<'a>(pub &'a [u8]);
|
|
||||||
impl<'a> Serialize for RawBytes<'a> {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_bytes(self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<i32> for VarInt {
|
impl From<i32> for VarInt {
|
||||||
fn from(value: i32) -> Self {
|
fn from(value: i32) -> Self {
|
||||||
VarInt(value)
|
VarInt(value)
|
||||||
|
|||||||
@ -22,7 +22,7 @@ pub struct PacketEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PacketEncoder {
|
impl PacketEncoder {
|
||||||
pub fn append_packet<P: ClientPacket>(&mut self, packet: P) -> Result<(), PacketError> {
|
pub fn append_packet<P: ClientPacket>(&mut self, packet: &P) -> Result<(), PacketError> {
|
||||||
let start_len = self.buf.len();
|
let start_len = self.buf.len();
|
||||||
|
|
||||||
let mut writer = (&mut self.buf).writer();
|
let mut writer = (&mut self.buf).writer();
|
||||||
|
|||||||
@ -1,79 +0,0 @@
|
|||||||
use core::str;
|
|
||||||
|
|
||||||
use fastnbt::SerOpts;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
// Fepresents a Text component
|
|
||||||
// Reference: https://wiki.vg/Text_formatting#Text_components
|
|
||||||
#[derive(Clone, PartialEq, Default, Debug, Deserialize)]
|
|
||||||
pub struct TextComponent {
|
|
||||||
pub text: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl serde::Serialize for TextComponent {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_bytes(self.encode().as_slice())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextComponent {
|
|
||||||
pub fn encode(&self) -> Vec<u8> {
|
|
||||||
#[derive(serde::Serialize)]
|
|
||||||
struct TempStruct<'a> {
|
|
||||||
text: &'a String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fastnbt::to_bytes_with_opts(&TempStruct { text: &self.text }, SerOpts::network_nbt())
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for TextComponent {
|
|
||||||
fn from(value: String) -> Self {
|
|
||||||
Self { text: value }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for TextComponent {
|
|
||||||
fn from(value: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
text: value.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Text color
|
|
||||||
#[derive(Default, Debug, Clone, Copy)]
|
|
||||||
pub enum Color {
|
|
||||||
/// The default color for the text will be used, which varies by context
|
|
||||||
/// (in some cases, it's white; in others, it's black; in still others, it
|
|
||||||
/// is a shade of gray that isn't normally used on text).
|
|
||||||
#[default]
|
|
||||||
Reset,
|
|
||||||
/// One of the 16 named Minecraft colors
|
|
||||||
Named(NamedColor),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Named Minecraft color
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
|
||||||
pub enum NamedColor {
|
|
||||||
Black = 0,
|
|
||||||
DarkBlue,
|
|
||||||
DarkGreen,
|
|
||||||
DarkCyan,
|
|
||||||
DarkRed,
|
|
||||||
Purple,
|
|
||||||
Gold,
|
|
||||||
Gray,
|
|
||||||
DarkGray,
|
|
||||||
Blue,
|
|
||||||
BrightGreen,
|
|
||||||
Cyan,
|
|
||||||
Red,
|
|
||||||
Pink,
|
|
||||||
Yellow,
|
|
||||||
White,
|
|
||||||
}
|
|
||||||
13
pumpkin-protocol/src/uuid.rs
Normal file
13
pumpkin-protocol/src/uuid.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct UUID(pub uuid::Uuid);
|
||||||
|
|
||||||
|
impl Serialize for UUID {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_bytes(self.0.as_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,8 @@ edition.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pumpkin-protocol = { path = "../pumpkin-protocol"}
|
pumpkin-protocol = { path = "../pumpkin-protocol"}
|
||||||
|
pumpkin-text = { path = "../pumpkin-text"}
|
||||||
|
|
||||||
# nbt
|
# nbt
|
||||||
fastnbt = { git = "https://github.com/owengage/fastnbt.git" }
|
fastnbt = { git = "https://github.com/owengage/fastnbt.git" }
|
||||||
fastsnbt = "0.2"
|
fastsnbt = "0.2"
|
||||||
|
|||||||
@ -1 +1,33 @@
|
|||||||
|
use pumpkin_text::style::Style;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct ChatType {
|
||||||
|
chat: Decoration,
|
||||||
|
narration: Decoration,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct Decoration {
|
||||||
|
translation_key: String,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
style: Option<Style>,
|
||||||
|
parameters: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ChatType {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
chat: Decoration {
|
||||||
|
style: None,
|
||||||
|
parameters: vec!["sender".into(), "content".into()],
|
||||||
|
translation_key: "chat.type.text".into(),
|
||||||
|
},
|
||||||
|
narration: Decoration {
|
||||||
|
style: None,
|
||||||
|
parameters: vec!["sender".into(), "content".into()],
|
||||||
|
translation_key: "chat.type.text.narrate".into(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
use biomes::Biome;
|
use biomes::Biome;
|
||||||
|
use chat_type::ChatType;
|
||||||
use dimensions::Dimension;
|
use dimensions::Dimension;
|
||||||
use fastnbt::SerOpts;
|
use fastnbt::SerOpts;
|
||||||
use paint::Painting;
|
use paint::Painting;
|
||||||
@ -50,6 +51,16 @@ impl Registry {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let chat_types = Registry {
|
||||||
|
registry_id: "minecraft:chat_type".to_string(),
|
||||||
|
registry_entries: vec![RegistryEntry {
|
||||||
|
entry_id: "minecraft:chat",
|
||||||
|
data: fastnbt::to_bytes_with_opts(&ChatType::default(), SerOpts::network_nbt())
|
||||||
|
.unwrap(),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
let damage_types = Registry {
|
let damage_types = Registry {
|
||||||
registry_id: "minecraft:damage_type".to_string(),
|
registry_id: "minecraft:damage_type".to_string(),
|
||||||
registry_entries: damage_type::entires(),
|
registry_entries: damage_type::entires(),
|
||||||
@ -62,6 +73,13 @@ impl Registry {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
vec![dimensions, damage_types, biomes, wolf_variants, paintings]
|
vec![
|
||||||
|
dimensions,
|
||||||
|
damage_types,
|
||||||
|
biomes,
|
||||||
|
wolf_variants,
|
||||||
|
paintings,
|
||||||
|
chat_types,
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
pumpkin-text/Cargo.toml
Normal file
9
pumpkin-text/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "pumpkin-text"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
fastnbt = { git = "https://github.com/owengage/fastnbt.git" }
|
||||||
|
uuid = "1.10"
|
||||||
19
pumpkin-text/src/click.rs
Normal file
19
pumpkin-text/src/click.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Action to take on click of the text.
|
||||||
|
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "action", content = "value", rename_all = "snake_case")]
|
||||||
|
pub enum ClickEvent {
|
||||||
|
/// Opens an URL
|
||||||
|
OpenUrl(String),
|
||||||
|
/// Works in signs, but only on the root text component
|
||||||
|
RunCommand(String),
|
||||||
|
/// Replaces the contents of the chat box with the text, not necessarily a
|
||||||
|
/// command.
|
||||||
|
SuggestCommand(String),
|
||||||
|
/// Only usable within written books. Changes the page of the book. Indexing
|
||||||
|
/// starts at 1.
|
||||||
|
ChangePage(i32),
|
||||||
|
/// Copies the given text to system clipboard
|
||||||
|
CopyToClipboard(String),
|
||||||
|
}
|
||||||
36
pumpkin-text/src/color.rs
Normal file
36
pumpkin-text/src/color.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Text color
|
||||||
|
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum Color {
|
||||||
|
/// The default color for the text will be used, which varies by context
|
||||||
|
/// (in some cases, it's white; in others, it's black; in still others, it
|
||||||
|
/// is a shade of gray that isn't normally used on text).
|
||||||
|
#[default]
|
||||||
|
Reset,
|
||||||
|
/// One of the 16 named Minecraft colors
|
||||||
|
Named(NamedColor),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Named Minecraft color
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum NamedColor {
|
||||||
|
Black = 0,
|
||||||
|
DarkBlue,
|
||||||
|
DarkGreen,
|
||||||
|
DarkAqua,
|
||||||
|
DarkRed,
|
||||||
|
DarkPurple,
|
||||||
|
Gold,
|
||||||
|
Gray,
|
||||||
|
DarkGray,
|
||||||
|
Blue,
|
||||||
|
Green,
|
||||||
|
Aqua,
|
||||||
|
Red,
|
||||||
|
LightPurple,
|
||||||
|
Yellow,
|
||||||
|
White,
|
||||||
|
}
|
||||||
32
pumpkin-text/src/hover.rs
Normal file
32
pumpkin-text/src/hover.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::Text;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "action", content = "contents", rename_all = "snake_case")]
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
|
pub enum HoverEvent {
|
||||||
|
/// Displays a tooltip with the given text.
|
||||||
|
ShowText(Text),
|
||||||
|
/// Shows an item.
|
||||||
|
ShowItem {
|
||||||
|
/// Resource identifier of the item
|
||||||
|
id: String,
|
||||||
|
/// Number of the items in the stack
|
||||||
|
count: Option<i32>,
|
||||||
|
/// NBT information about the item (sNBT format)
|
||||||
|
tag: String,
|
||||||
|
},
|
||||||
|
/// Shows an entity.
|
||||||
|
ShowEntity {
|
||||||
|
/// The entity's UUID
|
||||||
|
id: uuid::Uuid,
|
||||||
|
/// Resource identifier of the entity
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
kind: Option<String>,
|
||||||
|
/// Optional custom name for the entity
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
name: Option<Text>,
|
||||||
|
},
|
||||||
|
}
|
||||||
167
pumpkin-text/src/lib.rs
Normal file
167
pumpkin-text/src/lib.rs
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
use core::str;
|
||||||
|
|
||||||
|
use click::ClickEvent;
|
||||||
|
use color::Color;
|
||||||
|
use fastnbt::SerOpts;
|
||||||
|
use hover::HoverEvent;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use style::Style;
|
||||||
|
|
||||||
|
pub mod click;
|
||||||
|
pub mod color;
|
||||||
|
pub mod hover;
|
||||||
|
pub mod style;
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct Text(pub Box<TextComponent>);
|
||||||
|
|
||||||
|
// Fepresents a Text component
|
||||||
|
// Reference: https://wiki.vg/Text_formatting#Text_components
|
||||||
|
#[derive(Clone, Default, Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct TextComponent {
|
||||||
|
/// The actual text
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub content: TextContent,
|
||||||
|
/// Style of the text. Bold, Italic, unterline, Color...
|
||||||
|
/// Also has `ClickEvent
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub style: Style,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::Serialize for TextComponent {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_bytes(self.encode().as_slice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextComponent {
|
||||||
|
pub fn color(mut self, color: Color) -> Self {
|
||||||
|
self.style.color = Some(color);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn color_named(mut self, color: color::NamedColor) -> Self {
|
||||||
|
self.style.color = Some(Color::Named(color));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes the text bold
|
||||||
|
pub fn bold(mut self) -> Self {
|
||||||
|
self.style.bold = Some(1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes the text italic
|
||||||
|
pub fn italic(mut self) -> Self {
|
||||||
|
self.style.italic = Some(1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes the text underlined
|
||||||
|
pub fn underlined(mut self) -> Self {
|
||||||
|
self.style.underlined = Some(1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes the text strikethrough
|
||||||
|
pub fn strikethrough(mut self) -> Self {
|
||||||
|
self.style.strikethrough = Some(1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes the text obfuscated
|
||||||
|
pub fn obfuscated(mut self) -> Self {
|
||||||
|
self.style.obfuscated = Some(1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When the text is shift-clicked by a player, this string is inserted in their chat input. It does not overwrite any existing text the player was writing. This only works in chat messages
|
||||||
|
pub fn insertion(mut self, text: String) -> Self {
|
||||||
|
self.style.insertion = Some(text);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows for events to occur when the player clicks on text. Only work in chat.
|
||||||
|
pub fn click_event(mut self, event: ClickEvent) -> Self {
|
||||||
|
self.style.click_event = Some(event);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows for a tooltip to be displayed when the player hovers their mouse over text.
|
||||||
|
pub fn hover_event(mut self, event: HoverEvent) -> Self {
|
||||||
|
self.style.hover_event = Some(event);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode(&self) -> Vec<u8> {
|
||||||
|
// TODO: Somehow fix this ugly mess
|
||||||
|
#[derive(serde::Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct TempStruct<'a> {
|
||||||
|
#[serde(flatten)]
|
||||||
|
text: &'a TextContent,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub style: &'a Style,
|
||||||
|
}
|
||||||
|
let astruct = TempStruct {
|
||||||
|
text: &self.content,
|
||||||
|
style: &self.style,
|
||||||
|
};
|
||||||
|
// dbg!(&serde_json::to_string(&astruct));
|
||||||
|
|
||||||
|
fastnbt::to_bytes_with_opts(&astruct, SerOpts::network_nbt()).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for TextComponent {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
Self {
|
||||||
|
content: TextContent::Text { text: value },
|
||||||
|
style: Style::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for TextComponent {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
content: TextContent::Text {
|
||||||
|
text: value.to_string(),
|
||||||
|
},
|
||||||
|
style: Style::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum TextContent {
|
||||||
|
/// Raw Text
|
||||||
|
Text { text: String },
|
||||||
|
/// Translated text
|
||||||
|
Translate {
|
||||||
|
translate: String,
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
with: Vec<Text>,
|
||||||
|
},
|
||||||
|
/// Displays the name of one or more entities found by a selector.
|
||||||
|
EntityNames {
|
||||||
|
selector: String,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
separator: Option<Text>,
|
||||||
|
},
|
||||||
|
/// A keybind identifier
|
||||||
|
/// https://minecraft.fandom.com/wiki/Controls#Configurable_controls
|
||||||
|
Keybind { keybind: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TextContent {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Text { text: "".into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
100
pumpkin-text/src/style.rs
Normal file
100
pumpkin-text/src/style.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
click::ClickEvent,
|
||||||
|
color::{self, Color},
|
||||||
|
hover::HoverEvent,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
||||||
|
pub struct Style {
|
||||||
|
/// Changes the color to render the content
|
||||||
|
pub color: Option<Color>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub bold: Option<u8>,
|
||||||
|
/// Whether to render the content in italic.
|
||||||
|
/// Keep in mind that booleans are representet as bytes in nbt
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub italic: Option<u8>,
|
||||||
|
/// Whether to render the content in underlined.
|
||||||
|
/// Keep in mind that booleans are representet as bytes in nbt
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub underlined: Option<u8>,
|
||||||
|
/// Whether to render the content in strikethrough.
|
||||||
|
/// Keep in mind that booleans are representet as bytes in nbt
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub strikethrough: Option<u8>,
|
||||||
|
/// Whether to render the content in obfuscated.
|
||||||
|
/// Keep in mind that booleans are representet as bytes in nbt
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub obfuscated: Option<u8>,
|
||||||
|
/// When the text is shift-clicked by a player, this string is inserted in their chat input. It does not overwrite any existing text the player was writing. This only works in chat messages
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub insertion: Option<String>,
|
||||||
|
/// Allows for events to occur when the player clicks on text. Only work in chat.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub click_event: Option<ClickEvent>,
|
||||||
|
/// Allows for a tooltip to be displayed when the player hovers their mouse over text.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub hover_event: Option<HoverEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Style {
|
||||||
|
pub fn color(mut self, color: Color) -> Self {
|
||||||
|
self.color = Some(color);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn color_named(mut self, color: color::NamedColor) -> Self {
|
||||||
|
self.color = Some(Color::Named(color));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes the text bold
|
||||||
|
pub fn bold(mut self) -> Self {
|
||||||
|
self.bold = Some(1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes the text italic
|
||||||
|
pub fn italic(mut self) -> Self {
|
||||||
|
self.italic = Some(1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes the text underlined
|
||||||
|
pub fn underlined(mut self) -> Self {
|
||||||
|
self.underlined = Some(1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes the text strikethrough
|
||||||
|
pub fn strikethrough(mut self) -> Self {
|
||||||
|
self.strikethrough = Some(1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes the text obfuscated
|
||||||
|
pub fn obfuscated(mut self) -> Self {
|
||||||
|
self.obfuscated = Some(1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When the text is shift-clicked by a player, this string is inserted in their chat input. It does not overwrite any existing text the player was writing. This only works in chat messages
|
||||||
|
pub fn insertion(mut self, text: String) -> Self {
|
||||||
|
self.insertion = Some(text);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows for events to occur when the player clicks on text. Only work in chat.
|
||||||
|
pub fn click_event(mut self, event: ClickEvent) -> Self {
|
||||||
|
self.click_event = Some(event);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows for a tooltip to be displayed when the player hovers their mouse over text.
|
||||||
|
pub fn hover_event(mut self, event: HoverEvent) -> Self {
|
||||||
|
self.hover_event = Some(event);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,8 @@ description = "Empowering everyone to host fast and efficient Minecraft servers.
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
pumpkin-text = { path = "../pumpkin-text" }
|
||||||
|
pumpkin-inventory = { path = "../pumpkin-inventory"}
|
||||||
pumpkin-world = { path = "../pumpkin-world"}
|
pumpkin-world = { path = "../pumpkin-world"}
|
||||||
pumpkin-entity = { path = "../pumpkin-entity"}
|
pumpkin-entity = { path = "../pumpkin-entity"}
|
||||||
pumpkin-protocol = { path = "../pumpkin-protocol"}
|
pumpkin-protocol = { path = "../pumpkin-protocol"}
|
||||||
@ -15,6 +17,8 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
toml = "0.8.19"
|
toml = "0.8.19"
|
||||||
|
|
||||||
|
bytes = "1.7"
|
||||||
|
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
@ -45,6 +49,6 @@ log = "0.4"
|
|||||||
mio = { version = "1.0.1", features = ["os-poll", "net"]}
|
mio = { version = "1.0.1", features = ["os-poll", "net"]}
|
||||||
crossbeam-channel = "0.5.13"
|
crossbeam-channel = "0.5.13"
|
||||||
|
|
||||||
uuid = { version = "1.10", features = ["serde"]}
|
uuid = { version = "1.10", features = ["serde", "v3"]}
|
||||||
|
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
@ -52,14 +52,16 @@ pub async fn authenticate(
|
|||||||
ip: &IpAddr,
|
ip: &IpAddr,
|
||||||
server: &mut Server,
|
server: &mut Server,
|
||||||
) -> Result<GameProfile, AuthError> {
|
) -> Result<GameProfile, AuthError> {
|
||||||
assert!(server.advanced_config.authentication.use_authentication);
|
assert!(server.advanced_config.authentication.enabled);
|
||||||
assert!(server.auth_client.is_some());
|
assert!(server.auth_client.is_some());
|
||||||
let address = if server
|
let address = if server
|
||||||
.advanced_config
|
.advanced_config
|
||||||
.authentication
|
.authentication
|
||||||
.prevent_proxy_connections
|
.prevent_proxy_connections
|
||||||
{
|
{
|
||||||
format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}&ip={ip}")
|
let test = format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}&ip={ip}");
|
||||||
|
dbg!(&test);
|
||||||
|
test
|
||||||
} else {
|
} else {
|
||||||
format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}")
|
format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}")
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
client::{
|
client::{
|
||||||
config::{CFinishConfig, CKnownPacks, CRegistryData},
|
config::{CConfigAddResourcePack, CFinishConfig, CKnownPacks, CRegistryData},
|
||||||
login::{CEncryptionRequest, CLoginSuccess, CSetCompression},
|
login::{CEncryptionRequest, CLoginSuccess, CSetCompression},
|
||||||
status::{CPingResponse, CStatusResponse},
|
status::{CPingResponse, CStatusResponse},
|
||||||
},
|
},
|
||||||
@ -11,9 +13,9 @@ use pumpkin_protocol::{
|
|||||||
login::{SEncryptionResponse, SLoginAcknowledged, SLoginPluginResponse, SLoginStart},
|
login::{SEncryptionResponse, SLoginAcknowledged, SLoginPluginResponse, SLoginStart},
|
||||||
status::{SPingRequest, SStatusRequest},
|
status::{SPingRequest, SStatusRequest},
|
||||||
},
|
},
|
||||||
ConnectionState, KnownPack, RawBytes,
|
ConnectionState, KnownPack,
|
||||||
};
|
};
|
||||||
use pumpkin_registry::Registry;
|
use pumpkin_text::TextComponent;
|
||||||
use rsa::Pkcs1v15Encrypt;
|
use rsa::Pkcs1v15Encrypt;
|
||||||
use sha1::{Digest, Sha1};
|
use sha1::{Digest, Sha1};
|
||||||
|
|
||||||
@ -38,12 +40,12 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_status_request(&mut self, server: &mut Server, _status_request: SStatusRequest) {
|
pub fn handle_status_request(&mut self, server: &mut Server, _status_request: SStatusRequest) {
|
||||||
self.send_packet(CStatusResponse::new(&server.status_response_json));
|
self.send_packet(&CStatusResponse::new(&server.status_response_json));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_ping_request(&mut self, _server: &mut Server, ping_request: SPingRequest) {
|
pub fn handle_ping_request(&mut self, _server: &mut Server, ping_request: SPingRequest) {
|
||||||
dbg!("ping");
|
dbg!("ping");
|
||||||
self.send_packet(CPingResponse::new(ping_request.payload));
|
self.send_packet(&CPingResponse::new(ping_request.payload));
|
||||||
self.close();
|
self.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,11 +66,11 @@ impl Client {
|
|||||||
let public_key_der = &server.public_key_der;
|
let public_key_der = &server.public_key_der;
|
||||||
let packet = CEncryptionRequest::new(
|
let packet = CEncryptionRequest::new(
|
||||||
"",
|
"",
|
||||||
RawBytes(public_key_der),
|
public_key_der,
|
||||||
RawBytes(&verify_token),
|
&verify_token,
|
||||||
server.base_config.online_mode, // TODO
|
server.base_config.online_mode, // TODO
|
||||||
);
|
);
|
||||||
self.send_packet(packet);
|
self.send_packet(&packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_encryption_response(
|
pub async fn handle_encryption_response(
|
||||||
@ -131,6 +133,7 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for ele in self.gameprofile.as_ref().unwrap().properties.clone() {
|
for ele in self.gameprofile.as_ref().unwrap().properties.clone() {
|
||||||
|
// todo, use this
|
||||||
unpack_textures(ele, &server.advanced_config.authentication.textures);
|
unpack_textures(ele, &server.advanced_config.authentication.textures);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,13 +144,13 @@ impl Client {
|
|||||||
.packet_compression
|
.packet_compression
|
||||||
.compression_threshold;
|
.compression_threshold;
|
||||||
let level = server.advanced_config.packet_compression.compression_level;
|
let level = server.advanced_config.packet_compression.compression_level;
|
||||||
self.send_packet(CSetCompression::new(threshold.into()));
|
self.send_packet(&CSetCompression::new(threshold.into()));
|
||||||
self.set_compression(Some((threshold, level)));
|
self.set_compression(Some((threshold, level)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(profile) = self.gameprofile.as_ref().cloned() {
|
if let Some(profile) = self.gameprofile.as_ref().cloned() {
|
||||||
let packet = CLoginSuccess::new(profile.id, profile.name, &profile.properties, false);
|
let packet = CLoginSuccess::new(profile.id, &profile.name, &profile.properties, false);
|
||||||
self.send_packet(packet);
|
self.send_packet(&packet);
|
||||||
} else {
|
} else {
|
||||||
self.kick("game profile is none");
|
self.kick("game profile is none");
|
||||||
}
|
}
|
||||||
@ -167,8 +170,25 @@ impl Client {
|
|||||||
) {
|
) {
|
||||||
self.connection_state = ConnectionState::Config;
|
self.connection_state = ConnectionState::Config;
|
||||||
server.send_brand(self);
|
server.send_brand(self);
|
||||||
|
|
||||||
|
let resource_config = &server.advanced_config.resource_pack;
|
||||||
|
if resource_config.enabled {
|
||||||
|
let prompt_message = if resource_config.prompt_message.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(TextComponent::from(resource_config.prompt_message.clone()))
|
||||||
|
};
|
||||||
|
self.send_packet(&CConfigAddResourcePack::new(
|
||||||
|
uuid::Uuid::from_str(&resource_config.resource_pack_url).unwrap(),
|
||||||
|
resource_config.resource_pack_url.clone(),
|
||||||
|
resource_config.resource_pack_sha1.clone(),
|
||||||
|
resource_config.force,
|
||||||
|
prompt_message,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// known data packs
|
// known data packs
|
||||||
self.send_packet(CKnownPacks::new(&[KnownPack {
|
self.send_packet(&CKnownPacks::new(&[KnownPack {
|
||||||
namespace: "minecraft",
|
namespace: "minecraft",
|
||||||
id: "core",
|
id: "core",
|
||||||
version: "1.21",
|
version: "1.21",
|
||||||
@ -180,6 +200,7 @@ impl Client {
|
|||||||
_server: &mut Server,
|
_server: &mut Server,
|
||||||
client_information: SClientInformation,
|
client_information: SClientInformation,
|
||||||
) {
|
) {
|
||||||
|
dbg!("got client settings");
|
||||||
self.config = Some(PlayerConfig {
|
self.config = Some(PlayerConfig {
|
||||||
locale: client_information.locale,
|
locale: client_information.locale,
|
||||||
view_distance: client_information.view_distance,
|
view_distance: client_information.view_distance,
|
||||||
@ -204,9 +225,9 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_known_packs(&mut self, _server: &mut Server, _config_acknowledged: SKnownPacks) {
|
pub fn handle_known_packs(&mut self, server: &mut Server, _config_acknowledged: SKnownPacks) {
|
||||||
for registry in Registry::get_static() {
|
for registry in &server.cached_registry {
|
||||||
self.send_packet(CRegistryData::new(
|
self.send_packet(&CRegistryData::new(
|
||||||
®istry.registry_id,
|
®istry.registry_id,
|
||||||
®istry.registry_entries,
|
®istry.registry_entries,
|
||||||
));
|
));
|
||||||
@ -214,7 +235,7 @@ impl Client {
|
|||||||
|
|
||||||
// We are done with configuring
|
// We are done with configuring
|
||||||
dbg!("finish config");
|
dbg!("finish config");
|
||||||
self.send_packet(CFinishConfig::new());
|
self.send_packet(&CFinishConfig::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_config_acknowledged(
|
pub async fn handle_config_acknowledged(
|
||||||
|
|||||||
@ -32,9 +32,9 @@ use pumpkin_protocol::{
|
|||||||
},
|
},
|
||||||
status::{SPingRequest, SStatusRequest},
|
status::{SPingRequest, SStatusRequest},
|
||||||
},
|
},
|
||||||
text::TextComponent,
|
|
||||||
ClientPacket, ConnectionState, PacketError, RawPacket, ServerPacket,
|
ClientPacket, ConnectionState, PacketError, RawPacket, ServerPacket,
|
||||||
};
|
};
|
||||||
|
use pumpkin_text::TextComponent;
|
||||||
|
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@ -122,7 +122,7 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Send a Clientbound Packet to the Client
|
/// Send a Clientbound Packet to the Client
|
||||||
pub fn send_packet<P: ClientPacket>(&mut self, packet: P) {
|
pub fn send_packet<P: ClientPacket>(&mut self, packet: &P) {
|
||||||
self.enc
|
self.enc
|
||||||
.append_packet(packet)
|
.append_packet(packet)
|
||||||
.unwrap_or_else(|e| self.kick(&e.to_string()));
|
.unwrap_or_else(|e| self.kick(&e.to_string()));
|
||||||
@ -132,7 +132,7 @@ impl Client {
|
|||||||
.unwrap_or_else(|e| self.kick(&e.to_string()));
|
.unwrap_or_else(|e| self.kick(&e.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_send_packet<P: ClientPacket>(&mut self, packet: P) -> Result<(), PacketError> {
|
pub fn try_send_packet<P: ClientPacket>(&mut self, packet: &P) -> Result<(), PacketError> {
|
||||||
self.enc.append_packet(packet)?;
|
self.enc.append_packet(packet)?;
|
||||||
self.connection
|
self.connection
|
||||||
.write_all(&self.enc.take())
|
.write_all(&self.enc.take())
|
||||||
@ -155,11 +155,11 @@ impl Client {
|
|||||||
entity.yaw = yaw;
|
entity.yaw = yaw;
|
||||||
entity.pitch = pitch;
|
entity.pitch = pitch;
|
||||||
player.awaiting_teleport = Some(id.into());
|
player.awaiting_teleport = Some(id.into());
|
||||||
self.send_packet(CSyncPlayerPostion::new(x, y, z, yaw, pitch, 0, id.into()));
|
self.send_packet(&CSyncPlayerPostion::new(x, y, z, yaw, pitch, 0, id.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_gamemode(&mut self, gamemode: GameMode) {
|
pub fn set_gamemode(&mut self, gamemode: GameMode) {
|
||||||
self.send_packet(CGameEvent::new(3, gamemode.to_f32().unwrap()));
|
self.send_packet(&CGameEvent::new(3, gamemode.to_f32().unwrap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn process_packets(&mut self, server: &mut Server) {
|
pub async fn process_packets(&mut self, server: &mut Server) {
|
||||||
@ -294,9 +294,7 @@ impl Client {
|
|||||||
}
|
}
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
bytes_read += n;
|
bytes_read += n;
|
||||||
if bytes_read == received_data.len() {
|
received_data.extend(&vec![0; n]);
|
||||||
received_data.resize(received_data.len() + 1024, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Would block "errors" are the OS's way of saying that the
|
// Would block "errors" are the OS's way of saying that the
|
||||||
// connection is not actually ready to perform this I/O operation.
|
// connection is not actually ready to perform this I/O operation.
|
||||||
@ -325,7 +323,7 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_system_message(&mut self, text: TextComponent) {
|
pub fn send_system_message(&mut self, text: TextComponent) {
|
||||||
self.send_packet(CSystemChatMessge::new(text, false));
|
self.send_packet(&CSystemChatMessge::new(text, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Kicks the Client with a reason depending on the connection state
|
/// Kicks the Client with a reason depending on the connection state
|
||||||
@ -333,19 +331,17 @@ impl Client {
|
|||||||
dbg!(reason);
|
dbg!(reason);
|
||||||
match self.connection_state {
|
match self.connection_state {
|
||||||
ConnectionState::Login => {
|
ConnectionState::Login => {
|
||||||
self.try_send_packet(CLoginDisconnect::new(
|
self.try_send_packet(&CLoginDisconnect::new(
|
||||||
&serde_json::to_string_pretty(&reason).unwrap(),
|
&serde_json::to_string_pretty(&reason).unwrap(),
|
||||||
))
|
))
|
||||||
.unwrap_or_else(|_| self.close());
|
.unwrap_or_else(|_| self.close());
|
||||||
}
|
}
|
||||||
ConnectionState::Config => {
|
ConnectionState::Config => {
|
||||||
self.try_send_packet(CConfigDisconnect::new(reason))
|
self.try_send_packet(&CConfigDisconnect::new(reason))
|
||||||
.unwrap_or_else(|_| self.close());
|
.unwrap_or_else(|_| self.close());
|
||||||
}
|
}
|
||||||
ConnectionState::Play => {
|
ConnectionState::Play => {
|
||||||
self.try_send_packet(CPlayDisconnect::new(TextComponent {
|
self.try_send_packet(&CPlayDisconnect::new(TextComponent::from(reason)))
|
||||||
text: reason.to_string(),
|
|
||||||
}))
|
|
||||||
.unwrap_or_else(|_| self.close());
|
.unwrap_or_else(|_| self.close());
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|||||||
@ -1,20 +1,23 @@
|
|||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
|
use pumpkin_inventory::WindowType;
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
client::play::{
|
client::play::{
|
||||||
Animation, CEntityAnimation, CHeadRot, CSystemChatMessge, CUpdateEntityPos,
|
Animation, CEntityAnimation, CHeadRot, COpenScreen, CUpdateEntityPos, CUpdateEntityPosRot,
|
||||||
CUpdateEntityPosRot, CUpdateEntityRot,
|
CUpdateEntityRot,
|
||||||
},
|
},
|
||||||
server::play::{
|
server::play::{
|
||||||
SChatCommand, SChatMessage, SConfirmTeleport, SPlayerCommand, SPlayerPosition,
|
SChatCommand, SChatMessage, SConfirmTeleport, SPlayerCommand, SPlayerPosition,
|
||||||
SPlayerPositionRotation, SPlayerRotation, SSwingArm,
|
SPlayerPositionRotation, SPlayerRotation, SSwingArm,
|
||||||
},
|
},
|
||||||
text::TextComponent,
|
VarInt,
|
||||||
};
|
};
|
||||||
|
use pumpkin_text::TextComponent;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{handle_command, CommandSender},
|
commands::{handle_command, CommandSender},
|
||||||
entity::player::Hand,
|
entity::player::Hand,
|
||||||
server::Server,
|
server::Server,
|
||||||
|
util::math::wrap_degrees,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Client;
|
use super::Client;
|
||||||
@ -38,18 +41,27 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clamp_horizontal(pos: f64) -> f64 {
|
||||||
|
pos.clamp(-3.0E7, 3.0E7)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clamp_vertical(pos: f64) -> f64 {
|
||||||
|
pos.clamp(-2.0E7, 2.0E7)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handle_position(&mut self, server: &mut Server, position: SPlayerPosition) {
|
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() {
|
if position.x.is_nan() || position.feet_y.is_nan() || position.z.is_nan() {
|
||||||
self.kick("Invalid movement");
|
self.kick("Invalid movement");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
let player = self.player.as_mut().unwrap();
|
let player = self.player.as_mut().unwrap();
|
||||||
let entity = &mut player.entity;
|
let entity = &mut player.entity;
|
||||||
entity.lastx = entity.x;
|
entity.lastx = entity.x;
|
||||||
entity.lasty = entity.y;
|
entity.lasty = entity.y;
|
||||||
entity.lastz = entity.z;
|
entity.lastz = entity.z;
|
||||||
entity.x = position.x;
|
entity.x = Self::clamp_horizontal(position.x);
|
||||||
entity.y = position.feet_y;
|
entity.y = Self::clamp_vertical(position.feet_y);
|
||||||
entity.z = position.z;
|
entity.z = Self::clamp_horizontal(position.z);
|
||||||
// TODO: teleport when moving > 8 block
|
// TODO: teleport when moving > 8 block
|
||||||
|
|
||||||
// send new position to all other players
|
// send new position to all other players
|
||||||
@ -61,7 +73,7 @@ impl Client {
|
|||||||
|
|
||||||
server.broadcast_packet(
|
server.broadcast_packet(
|
||||||
self,
|
self,
|
||||||
CUpdateEntityPos::new(
|
&CUpdateEntityPos::new(
|
||||||
entity_id.into(),
|
entity_id.into(),
|
||||||
(x * 4096.0 - lastx * 4096.0) as i16,
|
(x * 4096.0 - lastx * 4096.0) as i16,
|
||||||
(y * 4096.0 - lasty * 4096.0) as i16,
|
(y * 4096.0 - lasty * 4096.0) as i16,
|
||||||
@ -81,18 +93,23 @@ impl Client {
|
|||||||
|| position_rotation.z.is_nan()
|
|| position_rotation.z.is_nan()
|
||||||
{
|
{
|
||||||
self.kick("Invalid movement");
|
self.kick("Invalid movement");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if !position_rotation.yaw.is_finite() || !position_rotation.pitch.is_finite() {
|
if !position_rotation.yaw.is_finite() || !position_rotation.pitch.is_finite() {
|
||||||
self.kick("Invalid rotation");
|
self.kick("Invalid rotation");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
let player = self.player.as_mut().unwrap();
|
let player = self.player.as_mut().unwrap();
|
||||||
let entity = &mut player.entity;
|
let entity = &mut player.entity;
|
||||||
|
|
||||||
entity.x = position_rotation.x;
|
entity.lastx = entity.x;
|
||||||
entity.y = position_rotation.feet_y;
|
entity.lasty = entity.y;
|
||||||
entity.z = position_rotation.z;
|
entity.lastz = entity.z;
|
||||||
entity.yaw = position_rotation.yaw % 360.0;
|
entity.x = Self::clamp_horizontal(position_rotation.x);
|
||||||
entity.pitch = position_rotation.pitch.clamp(-90.0, 90.0) % 360.0;
|
entity.y = Self::clamp_vertical(position_rotation.feet_y);
|
||||||
|
entity.z = Self::clamp_horizontal(position_rotation.z);
|
||||||
|
entity.yaw = wrap_degrees(position_rotation.yaw);
|
||||||
|
entity.pitch = wrap_degrees(position_rotation.pitch);
|
||||||
|
|
||||||
// send new position to all other players
|
// send new position to all other players
|
||||||
let on_ground = player.on_ground;
|
let on_ground = player.on_ground;
|
||||||
@ -106,7 +123,7 @@ impl Client {
|
|||||||
|
|
||||||
server.broadcast_packet(
|
server.broadcast_packet(
|
||||||
self,
|
self,
|
||||||
CUpdateEntityPosRot::new(
|
&CUpdateEntityPosRot::new(
|
||||||
entity_id.into(),
|
entity_id.into(),
|
||||||
(x * 4096.0 - lastx * 4096.0) as i16,
|
(x * 4096.0 - lastx * 4096.0) as i16,
|
||||||
(y * 4096.0 - lasty * 4096.0) as i16,
|
(y * 4096.0 - lasty * 4096.0) as i16,
|
||||||
@ -116,32 +133,34 @@ impl Client {
|
|||||||
on_ground,
|
on_ground,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
server.broadcast_packet(self, CHeadRot::new(entity_id.into(), head_yaw as u8));
|
server.broadcast_packet(self, &CHeadRot::new(entity_id.into(), head_yaw as u8));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_rotation(&mut self, server: &mut Server, rotation: SPlayerRotation) {
|
pub fn handle_rotation(&mut self, server: &mut Server, rotation: SPlayerRotation) {
|
||||||
if !rotation.yaw.is_finite() || !rotation.pitch.is_finite() {
|
if !rotation.yaw.is_finite() || !rotation.pitch.is_finite() {
|
||||||
self.kick("Invalid rotation");
|
self.kick("Invalid rotation");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
let player = self.player.as_mut().unwrap();
|
let player = self.player.as_mut().unwrap();
|
||||||
let entity = &mut player.entity;
|
let entity = &mut player.entity;
|
||||||
entity.yaw = rotation.yaw % 360.0;
|
entity.yaw = wrap_degrees(rotation.yaw);
|
||||||
entity.pitch = rotation.pitch.clamp(-90.0, 90.0) % 360.0;
|
entity.pitch = wrap_degrees(rotation.pitch);
|
||||||
// send new position to all other players
|
// send new position to all other players
|
||||||
let on_ground = player.on_ground;
|
let on_ground = player.on_ground;
|
||||||
let entity_id = entity.entity_id;
|
let entity_id = entity.entity_id;
|
||||||
let yaw = entity.yaw;
|
let yaw = (entity.yaw * 256.0 / 360.0).floor();
|
||||||
let pitch = entity.pitch;
|
let pitch = (entity.pitch * 256.0 / 360.0).floor();
|
||||||
|
let head_yaw = (entity.head_yaw * 256.0 / 360.0).floor();
|
||||||
|
|
||||||
server.broadcast_packet(
|
server.broadcast_packet(
|
||||||
self,
|
self,
|
||||||
CUpdateEntityRot::new(entity_id.into(), yaw as u8, pitch as u8, on_ground),
|
&CUpdateEntityRot::new(entity_id.into(), yaw as u8, pitch as u8, on_ground),
|
||||||
);
|
);
|
||||||
server.broadcast_packet(self, CHeadRot::new(entity_id.into(), yaw as u8));
|
server.broadcast_packet(self, &CHeadRot::new(entity_id.into(), head_yaw as u8));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_chat_command(&mut self, _server: &mut Server, command: SChatCommand) {
|
pub fn handle_chat_command(&mut self, _server: &mut Server, command: SChatCommand) {
|
||||||
handle_command(&mut CommandSender::Player(self), command.command);
|
handle_command(&mut CommandSender::Player(self), &command.command);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_player_command(&mut self, _server: &mut Server, command: SPlayerCommand) {
|
pub fn handle_player_command(&mut self, _server: &mut Server, command: SPlayerCommand) {
|
||||||
@ -170,26 +189,31 @@ impl Client {
|
|||||||
};
|
};
|
||||||
let player = self.player.as_mut().unwrap();
|
let player = self.player.as_mut().unwrap();
|
||||||
let id = player.entity_id();
|
let id = player.entity_id();
|
||||||
server.broadcast_packet_expect(self, CEntityAnimation::new(id.into(), animation as u8))
|
server.broadcast_packet_expect(self, &CEntityAnimation::new(id.into(), animation as u8))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_chat_message(&mut self, server: &mut Server, chat_message: SChatMessage) {
|
pub fn handle_chat_message(&mut self, server: &mut Server, chat_message: SChatMessage) {
|
||||||
let message = chat_message.message;
|
let message = chat_message.message;
|
||||||
|
self.send_packet(&COpenScreen::new(
|
||||||
|
VarInt(0),
|
||||||
|
VarInt(WindowType::CraftingTable as i32),
|
||||||
|
TextComponent::from("Test Crafter"),
|
||||||
|
));
|
||||||
// TODO: filter message & validation
|
// TODO: filter message & validation
|
||||||
let gameprofile = self.gameprofile.as_ref().unwrap();
|
let gameprofile = self.gameprofile.as_ref().unwrap();
|
||||||
dbg!("got message");
|
dbg!("got message");
|
||||||
// yeah a "raw system message", the ugly way to do that, but it works
|
// yeah a "raw system message", the ugly way to do that, but it works
|
||||||
server.broadcast_packet(
|
// server.broadcast_packet(
|
||||||
self,
|
// self,
|
||||||
CSystemChatMessge::new(
|
// CSystemChatMessge::new(
|
||||||
TextComponent::from(format!("{}: {}", gameprofile.name, message)),
|
// TextComponent::from(format!("{}: {}", gameprofile.name, message)),
|
||||||
false,
|
// false,
|
||||||
),
|
// ),
|
||||||
)
|
// );
|
||||||
/* server.broadcast_packet(
|
/* server.broadcast_packet(
|
||||||
self,
|
self,
|
||||||
CPlayerChatMessage::new(
|
CPlayerChatMessage::new(
|
||||||
gameprofile.id,
|
pumpkin_protocol::uuid::UUID(gameprofile.id),
|
||||||
0.into(),
|
0.into(),
|
||||||
None,
|
None,
|
||||||
message.clone(),
|
message.clone(),
|
||||||
@ -202,7 +226,9 @@ impl Client {
|
|||||||
TextComponent::from(gameprofile.name.clone()),
|
TextComponent::from(gameprofile.name.clone()),
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
) */
|
)
|
||||||
|
|
||||||
|
*/
|
||||||
/* server.broadcast_packet(
|
/* server.broadcast_packet(
|
||||||
self,
|
self,
|
||||||
CDisguisedChatMessage::new(
|
CDisguisedChatMessage::new(
|
||||||
|
|||||||
@ -2,12 +2,6 @@ use super::Command;
|
|||||||
|
|
||||||
pub struct GamemodeCommand {}
|
pub struct GamemodeCommand {}
|
||||||
|
|
||||||
impl GamemodeCommand {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Command<'a> for GamemodeCommand {
|
impl<'a> Command<'a> for GamemodeCommand {
|
||||||
const NAME: &'a str = "gamemode";
|
const NAME: &'a str = "gamemode";
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use gamemode::GamemodeCommand;
|
use gamemode::GamemodeCommand;
|
||||||
use pumpkin::PumpkinCommand;
|
use pumpkin::PumpkinCommand;
|
||||||
use pumpkin_protocol::text::TextComponent;
|
use pumpkin_text::TextComponent;
|
||||||
|
|
||||||
use crate::client::Client;
|
use crate::client::Client;
|
||||||
|
|
||||||
@ -23,6 +23,7 @@ pub trait Command<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub enum CommandSender<'a> {
|
pub enum CommandSender<'a> {
|
||||||
|
Rcon(&'a mut Vec<String>),
|
||||||
Console,
|
Console,
|
||||||
Player(&'a mut Client),
|
Player(&'a mut Client),
|
||||||
}
|
}
|
||||||
@ -31,8 +32,9 @@ impl<'a> CommandSender<'a> {
|
|||||||
pub fn send_message(&mut self, text: TextComponent) {
|
pub fn send_message(&mut self, text: TextComponent) {
|
||||||
match self {
|
match self {
|
||||||
// TODO: add color and stuff to console
|
// TODO: add color and stuff to console
|
||||||
CommandSender::Console => log::info!("{}", text.text),
|
CommandSender::Console => log::info!("{:?}", text.content),
|
||||||
CommandSender::Player(c) => c.send_system_message(text),
|
CommandSender::Player(c) => c.send_system_message(text),
|
||||||
|
CommandSender::Rcon(s) => s.push(format!("{:?}", text.content)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,6 +42,7 @@ impl<'a> CommandSender<'a> {
|
|||||||
match self {
|
match self {
|
||||||
CommandSender::Console => false,
|
CommandSender::Console => false,
|
||||||
CommandSender::Player(_) => true,
|
CommandSender::Player(_) => true,
|
||||||
|
CommandSender::Rcon(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,16 +50,18 @@ impl<'a> CommandSender<'a> {
|
|||||||
match self {
|
match self {
|
||||||
CommandSender::Console => true,
|
CommandSender::Console => true,
|
||||||
CommandSender::Player(_) => false,
|
CommandSender::Player(_) => false,
|
||||||
|
CommandSender::Rcon(_) => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn as_mut_player(&mut self) -> Option<&mut Client> {
|
pub fn as_mut_player(&mut self) -> Option<&mut Client> {
|
||||||
match self {
|
match self {
|
||||||
CommandSender::Player(client) => Some(client),
|
CommandSender::Player(client) => Some(client),
|
||||||
CommandSender::Console => None,
|
CommandSender::Console => None,
|
||||||
|
CommandSender::Rcon(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn handle_command(sender: &mut CommandSender, command: String) {
|
pub fn handle_command(sender: &mut CommandSender, command: &str) {
|
||||||
let command = command.to_lowercase();
|
let command = command.to_lowercase();
|
||||||
// an ugly mess i know
|
// an ugly mess i know
|
||||||
if command.starts_with(PumpkinCommand::NAME) {
|
if command.starts_with(PumpkinCommand::NAME) {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
use pumpkin_protocol::{text::TextComponent, CURRENT_MC_PROTOCOL};
|
use pumpkin_protocol::CURRENT_MC_PROTOCOL;
|
||||||
|
use pumpkin_text::{color::NamedColor, TextComponent};
|
||||||
|
|
||||||
use crate::server::CURRENT_MC_VERSION;
|
use crate::server::CURRENT_MC_VERSION;
|
||||||
|
|
||||||
@ -6,12 +7,6 @@ use super::Command;
|
|||||||
|
|
||||||
pub struct PumpkinCommand {}
|
pub struct PumpkinCommand {}
|
||||||
|
|
||||||
impl PumpkinCommand {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Command<'a> for PumpkinCommand {
|
impl<'a> Command<'a> for PumpkinCommand {
|
||||||
const NAME: &'a str = "pumpkin";
|
const NAME: &'a str = "pumpkin";
|
||||||
|
|
||||||
@ -20,6 +15,6 @@ impl<'a> Command<'a> for PumpkinCommand {
|
|||||||
fn on_execute(sender: &mut super::CommandSender<'a>, _command: String) {
|
fn on_execute(sender: &mut super::CommandSender<'a>, _command: String) {
|
||||||
let version = env!("CARGO_PKG_VERSION");
|
let version = env!("CARGO_PKG_VERSION");
|
||||||
let description = env!("CARGO_PKG_DESCRIPTION");
|
let description = env!("CARGO_PKG_DESCRIPTION");
|
||||||
sender.send_message(TextComponent::from(format!("Pumpkin {version}, {description} (Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL})")))
|
sender.send_message(TextComponent::from(format!("Pumpkin {version}, {description} (Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL})")).color_named(NamedColor::Green).bold())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use crate::client::authentication::ProfileAction;
|
|||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct Authentication {
|
pub struct Authentication {
|
||||||
/// Whether to use Mojang authentication.
|
/// Whether to use Mojang authentication.
|
||||||
pub use_authentication: bool,
|
pub enabled: bool,
|
||||||
|
|
||||||
/// Prevent proxy connections.
|
/// Prevent proxy connections.
|
||||||
pub prevent_proxy_connections: bool,
|
pub prevent_proxy_connections: bool,
|
||||||
@ -84,8 +84,8 @@ impl Default for TextureTypes {
|
|||||||
impl Default for Authentication {
|
impl Default for Authentication {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
use_authentication: true,
|
enabled: true,
|
||||||
prevent_proxy_connections: true,
|
prevent_proxy_connections: false,
|
||||||
player_profile: Default::default(),
|
player_profile: Default::default(),
|
||||||
textures: Default::default(),
|
textures: Default::default(),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use auth_config::Authentication;
|
use auth_config::Authentication;
|
||||||
|
use resource_pack::ResourcePack;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{entity::player::GameMode, server::Difficulty};
|
use crate::{entity::player::GameMode, server::Difficulty};
|
||||||
|
|
||||||
pub mod auth_config;
|
pub mod auth_config;
|
||||||
|
pub mod resource_pack;
|
||||||
|
|
||||||
/// Current Config version of the Base Config
|
/// Current Config version of the Base Config
|
||||||
const CURRENT_BASE_VERSION: &str = "1.0.0";
|
const CURRENT_BASE_VERSION: &str = "1.0.0";
|
||||||
@ -18,6 +20,27 @@ pub struct AdvancedConfiguration {
|
|||||||
pub commands: Commands,
|
pub commands: Commands,
|
||||||
pub authentication: Authentication,
|
pub authentication: Authentication,
|
||||||
pub packet_compression: Compression,
|
pub packet_compression: Compression,
|
||||||
|
pub resource_pack: ResourcePack,
|
||||||
|
pub rcon: RCONConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
|
pub struct RCONConfig {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub ip: String,
|
||||||
|
pub port: u16,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RCONConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enabled: false,
|
||||||
|
ip: "0.0.0.0".to_string(),
|
||||||
|
port: 25575,
|
||||||
|
password: "".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
@ -63,6 +86,8 @@ impl Default for AdvancedConfiguration {
|
|||||||
authentication: Authentication::default(),
|
authentication: Authentication::default(),
|
||||||
commands: Commands::default(),
|
commands: Commands::default(),
|
||||||
packet_compression: Compression::default(),
|
packet_compression: Compression::default(),
|
||||||
|
resource_pack: ResourcePack::default(),
|
||||||
|
rcon: RCONConfig::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,10 +108,6 @@ pub struct BasicConfiguration {
|
|||||||
pub view_distance: u8,
|
pub view_distance: u8,
|
||||||
/// The maximum simulated view distance.
|
/// The maximum simulated view distance.
|
||||||
pub simulation_distance: u8,
|
pub simulation_distance: u8,
|
||||||
/// The path to the resource pack.
|
|
||||||
pub resource_pack: String,
|
|
||||||
/// The SHA1 hash of the resource pack.
|
|
||||||
pub resource_pack_sha1: String,
|
|
||||||
/// The default game difficulty.
|
/// The default game difficulty.
|
||||||
pub default_difficulty: Difficulty,
|
pub default_difficulty: Difficulty,
|
||||||
/// Whether the Nether dimension is enabled.
|
/// Whether the Nether dimension is enabled.
|
||||||
@ -107,14 +128,12 @@ impl Default for BasicConfiguration {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
config_version: CURRENT_BASE_VERSION.to_string(),
|
config_version: CURRENT_BASE_VERSION.to_string(),
|
||||||
server_address: "127.0.0.1".to_string(),
|
server_address: "0.0.0.0".to_string(),
|
||||||
server_port: 25565,
|
server_port: 25565,
|
||||||
seed: "".to_string(),
|
seed: "".to_string(),
|
||||||
max_players: 100000,
|
max_players: 100000,
|
||||||
view_distance: 10,
|
view_distance: 10,
|
||||||
simulation_distance: 10,
|
simulation_distance: 10,
|
||||||
resource_pack: "".to_string(),
|
|
||||||
resource_pack_sha1: "".to_string(),
|
|
||||||
default_difficulty: Difficulty::Normal,
|
default_difficulty: Difficulty::Normal,
|
||||||
allow_nether: true,
|
allow_nether: true,
|
||||||
hardcore: false,
|
hardcore: false,
|
||||||
@ -130,21 +149,30 @@ impl AdvancedConfiguration {
|
|||||||
pub fn load<P: AsRef<Path>>(path: P) -> AdvancedConfiguration {
|
pub fn load<P: AsRef<Path>>(path: P) -> AdvancedConfiguration {
|
||||||
if path.as_ref().exists() {
|
if path.as_ref().exists() {
|
||||||
let toml = std::fs::read_to_string(path).expect("Couldn't read configuration");
|
let toml = std::fs::read_to_string(path).expect("Couldn't read configuration");
|
||||||
toml::from_str(toml.as_str()).expect("Couldn't parse, Proberbly old config")
|
let config: AdvancedConfiguration =
|
||||||
|
toml::from_str(toml.as_str()).expect("Couldn't parse features.toml, Proberbly old config, Replacing with a new one or just delete it");
|
||||||
|
config.validate();
|
||||||
|
config
|
||||||
} else {
|
} else {
|
||||||
let config = AdvancedConfiguration::default();
|
let config = AdvancedConfiguration::default();
|
||||||
let toml = toml::to_string(&config).expect("Couldn't create toml!");
|
let toml = toml::to_string(&config).expect("Couldn't create toml!");
|
||||||
std::fs::write(path, toml).expect("Couldn't save configuration");
|
std::fs::write(path, toml).expect("Couldn't save configuration");
|
||||||
|
config.validate();
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn validate(&self) {
|
||||||
|
self.resource_pack.validate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BasicConfiguration {
|
impl BasicConfiguration {
|
||||||
pub fn load<P: AsRef<Path>>(path: P) -> BasicConfiguration {
|
pub fn load<P: AsRef<Path>>(path: P) -> BasicConfiguration {
|
||||||
if path.as_ref().exists() {
|
if path.as_ref().exists() {
|
||||||
let toml = std::fs::read_to_string(path).expect("Couldn't read configuration");
|
let toml = std::fs::read_to_string(path).expect("Couldn't read configuration");
|
||||||
toml::from_str(toml.as_str()).expect("Couldn't parse")
|
let config: BasicConfiguration = toml::from_str(toml.as_str()).expect("Couldn't parse configuration.toml, Proberbly old config, Replacing with a new one or just delete it");
|
||||||
|
config.validate();
|
||||||
|
config
|
||||||
} else {
|
} else {
|
||||||
let config = BasicConfiguration::default();
|
let config = BasicConfiguration::default();
|
||||||
let toml = toml::to_string(&config).expect("Couldn't create toml!");
|
let toml = toml::to_string(&config).expect("Couldn't create toml!");
|
||||||
@ -170,10 +198,5 @@ impl BasicConfiguration {
|
|||||||
"When Online Mode is enabled, Encryption must be enabled"
|
"When Online Mode is enabled, Encryption must be enabled"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
assert_eq!(
|
|
||||||
!self.resource_pack.is_empty(),
|
|
||||||
!self.resource_pack_sha1.is_empty(),
|
|
||||||
"Resource Pack path or Sha1 hash is missing"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
pumpkin/src/config/resource_pack.rs
Normal file
40
pumpkin/src/config/resource_pack.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
pub struct ResourcePack {
|
||||||
|
pub enabled: bool,
|
||||||
|
/// The path to the resource pack.
|
||||||
|
pub resource_pack_url: String,
|
||||||
|
/// The SHA1 hash (40) of the resource pack.
|
||||||
|
pub resource_pack_sha1: String,
|
||||||
|
/// Custom propmt Text component, Leave blank for none
|
||||||
|
pub prompt_message: String,
|
||||||
|
/// Will force the Player to accept the resource pack
|
||||||
|
pub force: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResourcePack {
|
||||||
|
pub fn validate(&self) {
|
||||||
|
assert_eq!(
|
||||||
|
!self.resource_pack_url.is_empty(),
|
||||||
|
!self.resource_pack_sha1.is_empty(),
|
||||||
|
"Resource Pack path or Sha1 hash is missing"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
self.resource_pack_sha1.len() <= 40,
|
||||||
|
"Resource pack sha1 hash is too long (max. 40)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ResourcePack {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enabled: false,
|
||||||
|
resource_pack_url: "".into(),
|
||||||
|
resource_pack_sha1: "".into(),
|
||||||
|
force: false,
|
||||||
|
prompt_message: "".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,6 +18,7 @@ pub mod client;
|
|||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod entity;
|
pub mod entity;
|
||||||
|
pub mod rcon;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
@ -27,6 +28,8 @@ async fn main() -> io::Result<()> {
|
|||||||
const SERVER: Token = Token(0);
|
const SERVER: Token = Token(0);
|
||||||
use std::{cell::RefCell, time::Instant};
|
use std::{cell::RefCell, time::Instant};
|
||||||
|
|
||||||
|
use rcon::RCONServer;
|
||||||
|
|
||||||
let time = Instant::now();
|
let time = Instant::now();
|
||||||
let basic_config = BasicConfiguration::load("configuration.toml");
|
let basic_config = BasicConfiguration::load("configuration.toml");
|
||||||
|
|
||||||
@ -58,6 +61,7 @@ async fn main() -> io::Result<()> {
|
|||||||
let mut unique_token = Token(SERVER.0 + 1);
|
let mut unique_token = Token(SERVER.0 + 1);
|
||||||
|
|
||||||
let use_console = advanced_configuration.commands.use_console;
|
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 connections: HashMap<Token, Rc<RefCell<Client>>> = HashMap::new();
|
||||||
|
|
||||||
@ -73,10 +77,15 @@ async fn main() -> io::Result<()> {
|
|||||||
stdin
|
stdin
|
||||||
.read_line(&mut out)
|
.read_line(&mut out)
|
||||||
.expect("Failed to read console line");
|
.expect("Failed to read console line");
|
||||||
handle_command(&mut commands::CommandSender::Console, out);
|
handle_command(&mut commands::CommandSender::Console, &out);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if rcon.enabled {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
RCONServer::new(&rcon).await.unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
loop {
|
loop {
|
||||||
if let Err(err) = poll.poll(&mut events, None) {
|
if let Err(err) = poll.poll(&mut events, None) {
|
||||||
if interrupted(&err) {
|
if interrupted(&err) {
|
||||||
|
|||||||
220
pumpkin/src/rcon/mod.rs
Normal file
220
pumpkin/src/rcon/mod.rs
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
io::{self, Read},
|
||||||
|
};
|
||||||
|
|
||||||
|
use mio::{
|
||||||
|
net::{TcpListener, TcpStream},
|
||||||
|
Events, Interest, Poll, Token,
|
||||||
|
};
|
||||||
|
use packet::{Packet, PacketError, PacketType};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::{commands::handle_command, config::RCONConfig};
|
||||||
|
|
||||||
|
mod packet;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum RCONError {
|
||||||
|
#[error("authentication failed")]
|
||||||
|
Auth,
|
||||||
|
#[error("command exceeds the maximum length")]
|
||||||
|
CommandTooLong,
|
||||||
|
#[error("{}", _0)]
|
||||||
|
Io(io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
const SERVER: Token = Token(0);
|
||||||
|
|
||||||
|
pub struct RCONServer {}
|
||||||
|
|
||||||
|
impl RCONServer {
|
||||||
|
pub async fn new(config: &RCONConfig) -> Result<Self, io::Error> {
|
||||||
|
assert!(config.enabled, "RCON is not enabled");
|
||||||
|
let addr = format!("{}:{}", config.ip, config.port)
|
||||||
|
.parse()
|
||||||
|
.expect("Failed to parse RCON address");
|
||||||
|
let mut poll = Poll::new().unwrap();
|
||||||
|
let mut listener = TcpListener::bind(addr).unwrap();
|
||||||
|
|
||||||
|
poll.registry()
|
||||||
|
.register(&mut listener, SERVER, Interest::READABLE)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut unique_token = Token(SERVER.0 + 1);
|
||||||
|
|
||||||
|
let mut events = Events::with_capacity(128);
|
||||||
|
|
||||||
|
let mut connections: HashMap<Token, RCONClient> = HashMap::new();
|
||||||
|
|
||||||
|
let password = config.password.clone();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
poll.poll(&mut events, None).unwrap();
|
||||||
|
|
||||||
|
for event in events.iter() {
|
||||||
|
match event.token() {
|
||||||
|
SERVER => loop {
|
||||||
|
// Received an event for the TCP server socket, which
|
||||||
|
// indicates we can accept an connection.
|
||||||
|
let (mut connection, address) = match listener.accept() {
|
||||||
|
Ok((connection, address)) => (connection, address),
|
||||||
|
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||||
|
// If we get a `WouldBlock` error we know our
|
||||||
|
// listener has no more incoming connections queued,
|
||||||
|
// so we can return to polling and wait for some
|
||||||
|
// more.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// If it was any other kind of error, something went
|
||||||
|
// wrong and we terminate with an error.
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
log::info!("Accepted connection from: {}", address);
|
||||||
|
|
||||||
|
let token = Self::next(&mut unique_token);
|
||||||
|
poll.registry()
|
||||||
|
.register(
|
||||||
|
&mut connection,
|
||||||
|
token,
|
||||||
|
Interest::READABLE.add(Interest::WRITABLE),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
connections.insert(token, RCONClient::new(connection));
|
||||||
|
},
|
||||||
|
|
||||||
|
token => {
|
||||||
|
let done = if let Some(client) = connections.get_mut(&token) {
|
||||||
|
client.handle(&password).await
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
if done {
|
||||||
|
if let Some(mut client) = connections.remove(&token) {
|
||||||
|
poll.registry().deregister(&mut client.connection)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(current: &mut Token) -> Token {
|
||||||
|
let next = current.0;
|
||||||
|
current.0 += 1;
|
||||||
|
Token(next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RCONClient {
|
||||||
|
connection: TcpStream,
|
||||||
|
logged_in: bool,
|
||||||
|
incoming: Vec<u8>,
|
||||||
|
closed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RCONClient {
|
||||||
|
pub fn new(connection: TcpStream) -> Self {
|
||||||
|
Self {
|
||||||
|
connection,
|
||||||
|
logged_in: false,
|
||||||
|
incoming: Vec::new(),
|
||||||
|
closed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle(&mut self, password: &str) -> bool {
|
||||||
|
if !self.closed {
|
||||||
|
loop {
|
||||||
|
match self.read_bytes() {
|
||||||
|
// Stream closed, so we can't reply, so we just close everything.
|
||||||
|
Ok(true) => return true,
|
||||||
|
Ok(false) => {}
|
||||||
|
Err(e) if e.kind() == io::ErrorKind::WouldBlock => break,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("could not read packet: {e}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we get a close here, we might have a reply, which we still want to write.
|
||||||
|
match self.poll(password).await {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("rcon error: {e}");
|
||||||
|
self.closed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.closed
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn poll(&mut self, password: &str) -> Result<(), PacketError> {
|
||||||
|
loop {
|
||||||
|
let packet = match self.receive_packet().await? {
|
||||||
|
Some(p) => p,
|
||||||
|
None => return Ok(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
match packet.get_type() {
|
||||||
|
PacketType::Auth => {
|
||||||
|
let body = packet.get_body();
|
||||||
|
if !body.is_empty() && packet.get_body() == password {
|
||||||
|
self.send(&mut Packet::new(
|
||||||
|
packet.get_id(),
|
||||||
|
PacketType::AuthResponse,
|
||||||
|
"".into(),
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
log::info!("RCON Client logged in successfully");
|
||||||
|
self.logged_in = true;
|
||||||
|
} else {
|
||||||
|
log::warn!("RCON Client has tried wrong password");
|
||||||
|
self.send(&mut Packet::new(-1, PacketType::AuthResponse, "".into()))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
return Err(PacketError::WrongPassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PacketType::ExecCommand => {
|
||||||
|
if self.logged_in {
|
||||||
|
let mut output = Vec::new();
|
||||||
|
handle_command(
|
||||||
|
&mut crate::commands::CommandSender::Rcon(&mut output),
|
||||||
|
packet.get_body(),
|
||||||
|
);
|
||||||
|
for line in output {
|
||||||
|
self.send(&mut Packet::new(packet.get_id(), PacketType::Output, line))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PacketType::Output => todo!(),
|
||||||
|
PacketType::AuthResponse => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_bytes(&mut self) -> io::Result<bool> {
|
||||||
|
let mut buf = [0; 1460];
|
||||||
|
let n = self.connection.read(&mut buf)?;
|
||||||
|
if n == 0 {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
self.incoming.extend_from_slice(&buf[..n]);
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send(&mut self, packet: &mut Packet) -> io::Result<()> {
|
||||||
|
packet.send_packet(&mut self.connection).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn receive_packet(&mut self) -> Result<Option<Packet>, PacketError> {
|
||||||
|
Packet::deserialize(&mut self.incoming).await
|
||||||
|
}
|
||||||
|
}
|
||||||
113
pumpkin/src/rcon/packet.rs
Normal file
113
pumpkin/src/rcon/packet.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
use std::io::{self, BufRead, Cursor, Write};
|
||||||
|
|
||||||
|
use bytes::{BufMut, BytesMut};
|
||||||
|
use mio::net::TcpStream;
|
||||||
|
use thiserror::Error;
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum PacketType {
|
||||||
|
Auth,
|
||||||
|
AuthResponse,
|
||||||
|
ExecCommand,
|
||||||
|
Output,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum PacketError {
|
||||||
|
#[error("invalid length")]
|
||||||
|
InvalidLength,
|
||||||
|
#[error("expected terminating NUL byte")]
|
||||||
|
NoNullTermination,
|
||||||
|
#[error("wrong password")]
|
||||||
|
WrongPassword,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketType {
|
||||||
|
fn to_i32(self) -> i32 {
|
||||||
|
match self {
|
||||||
|
PacketType::Auth => 3,
|
||||||
|
PacketType::AuthResponse => 2,
|
||||||
|
PacketType::ExecCommand => 2,
|
||||||
|
PacketType::Output => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_i32(n: i32) -> PacketType {
|
||||||
|
match n {
|
||||||
|
3 => PacketType::Auth,
|
||||||
|
2 => PacketType::ExecCommand,
|
||||||
|
_ => PacketType::Output,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Packet {
|
||||||
|
id: i32,
|
||||||
|
ptype: PacketType,
|
||||||
|
body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Packet {
|
||||||
|
pub fn new(id: i32, ptype: PacketType, body: String) -> Packet {
|
||||||
|
Packet { id, ptype, body }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_packet(&mut self, connection: &mut TcpStream) -> io::Result<()> {
|
||||||
|
// let len = outgoing.len() as u64;
|
||||||
|
let mut buf = BytesMut::new();
|
||||||
|
// 10 is for 4 bytes ty, 4 bytes id, and 2 terminating nul bytes.
|
||||||
|
buf.put_i32_le(10 + self.get_body().len() as i32);
|
||||||
|
buf.put_i32_le(self.id);
|
||||||
|
buf.put_i32_le(self.get_type().to_i32());
|
||||||
|
let bytes = self.get_body().as_bytes();
|
||||||
|
buf.put_slice(bytes);
|
||||||
|
buf.put_u8(0);
|
||||||
|
buf.put_u8(0);
|
||||||
|
connection.write(&buf).unwrap();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn deserialize(incoming: &mut Vec<u8>) -> Result<Option<Packet>, PacketError> {
|
||||||
|
if incoming.len() < 4 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let mut buf = Cursor::new(&incoming);
|
||||||
|
let len = buf.read_i32_le().await.unwrap() + 4;
|
||||||
|
if !(0..=1460).contains(&len) {
|
||||||
|
return Err(PacketError::InvalidLength);
|
||||||
|
}
|
||||||
|
let id = buf.read_i32_le().await.unwrap();
|
||||||
|
let ty = buf.read_i32_le().await.unwrap();
|
||||||
|
let mut payload = vec![];
|
||||||
|
let _ = buf.read_until(b'\0', &mut payload).unwrap();
|
||||||
|
payload.pop();
|
||||||
|
if buf.read_u8().await.unwrap() != 0 {
|
||||||
|
return Err(PacketError::NoNullTermination);
|
||||||
|
}
|
||||||
|
if buf.position() != len as u64 {
|
||||||
|
return Err(PacketError::InvalidLength);
|
||||||
|
}
|
||||||
|
incoming.drain(0..len as usize);
|
||||||
|
|
||||||
|
let packet = Packet {
|
||||||
|
id,
|
||||||
|
ptype: PacketType::from_i32(ty),
|
||||||
|
body: String::from_utf8(payload).unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(packet))
|
||||||
|
}
|
||||||
|
pub fn get_body(&self) -> &str {
|
||||||
|
&self.body
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_type(&self) -> PacketType {
|
||||||
|
self.ptype
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_id(&self) -> i32 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ use std::{
|
|||||||
io::Cursor,
|
io::Cursor,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::atomic::{AtomicI32, Ordering},
|
sync::atomic::{AtomicI32, Ordering},
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use base64::{engine::general_purpose, Engine};
|
use base64::{engine::general_purpose, Engine};
|
||||||
@ -21,6 +22,9 @@ use pumpkin_protocol::{
|
|||||||
}, BitSet, ClientPacket, Players, Sample, StatusResponse, VarInt, Version, CURRENT_MC_PROTOCOL
|
}, BitSet, ClientPacket, Players, Sample, StatusResponse, VarInt, Version, CURRENT_MC_PROTOCOL
|
||||||
};
|
};
|
||||||
use pumpkin_world::dimension::Dimension;
|
use pumpkin_world::dimension::Dimension;
|
||||||
|
|
||||||
|
use pumpkin_registry::Registry;
|
||||||
|
use pumpkin_world::chunk::TestChunk;
|
||||||
use rsa::{traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey};
|
use rsa::{traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -48,6 +52,9 @@ pub struct Server {
|
|||||||
/// Cache the Server brand buffer so we don't have to rebuild them every time a player joins
|
/// Cache the Server brand buffer so we don't have to rebuild them every time a player joins
|
||||||
pub cached_server_brand: Vec<u8>,
|
pub cached_server_brand: Vec<u8>,
|
||||||
|
|
||||||
|
/// 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_clients: HashMap<Rc<Token>, Rc<RefCell<Client>>>,
|
||||||
|
|
||||||
// TODO: replace with HashMap <World, Player>
|
// TODO: replace with HashMap <World, Player>
|
||||||
@ -76,12 +83,18 @@ impl Server {
|
|||||||
)
|
)
|
||||||
.into_boxed_slice();
|
.into_boxed_slice();
|
||||||
let auth_client = if config.0.online_mode {
|
let auth_client = if config.0.online_mode {
|
||||||
Some(reqwest::Client::new())
|
Some(
|
||||||
|
reqwest::Client::builder()
|
||||||
|
.timeout(Duration::from_millis(5000))
|
||||||
|
.build()
|
||||||
|
.expect("Failed to to make reqwest client"),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
cached_registry: Registry::get_static(),
|
||||||
// 0 is invalid
|
// 0 is invalid
|
||||||
entity_id: 2.into(),
|
entity_id: 2.into(),
|
||||||
// world: World::load(""),
|
// world: World::load(""),
|
||||||
@ -116,7 +129,12 @@ impl Server {
|
|||||||
// todo: put this into the entitiy struct
|
// todo: put this into the entitiy struct
|
||||||
if client.is_player() {
|
if client.is_player() {
|
||||||
let id = client.player.as_ref().unwrap().entity_id();
|
let id = client.player.as_ref().unwrap().entity_id();
|
||||||
self.broadcast_packet(&mut client, CRemoveEntities::new(vec![id.into()]))
|
let uuid = client.gameprofile.as_ref().unwrap().id;
|
||||||
|
self.broadcast_packet_expect(
|
||||||
|
&mut client,
|
||||||
|
&CRemovePlayerInfo::new(1.into(), &[UUID(uuid)]),
|
||||||
|
);
|
||||||
|
self.broadcast_packet_expect(&mut client, &CRemoveEntities::new(&[id.into()]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,10 +148,10 @@ impl Server {
|
|||||||
client.player = Some(player);
|
client.player = Some(player);
|
||||||
|
|
||||||
// login packet for our new player
|
// login packet for our new player
|
||||||
client.send_packet(CLogin::new(
|
client.send_packet(&CLogin::new(
|
||||||
entity_id,
|
entity_id,
|
||||||
self.base_config.hardcore,
|
self.base_config.hardcore,
|
||||||
vec!["minecraft:overworld".into()],
|
&["minecraft:overworld"],
|
||||||
self.base_config.max_players.into(),
|
self.base_config.max_players.into(),
|
||||||
self.base_config.view_distance.into(), // TODO: view distance
|
self.base_config.view_distance.into(), // TODO: view distance
|
||||||
self.base_config.simulation_distance.into(), // TODO: sim view dinstance
|
self.base_config.simulation_distance.into(), // TODO: sim view dinstance
|
||||||
@ -141,7 +159,7 @@ impl Server {
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
0.into(),
|
0.into(),
|
||||||
"minecraft:overworld".into(),
|
"minecraft:overworld",
|
||||||
0, // seed
|
0, // seed
|
||||||
match self.base_config.default_gamemode {
|
match self.base_config.default_gamemode {
|
||||||
GameMode::Undefined => GameMode::Survival,
|
GameMode::Undefined => GameMode::Survival,
|
||||||
@ -158,26 +176,31 @@ impl Server {
|
|||||||
));
|
));
|
||||||
dbg!("sending abilities");
|
dbg!("sending abilities");
|
||||||
// player abilities
|
// player abilities
|
||||||
client.send_packet(CPlayerAbilities::new(0x02, 00.2, 0.1));
|
client.send_packet(&CPlayerAbilities::new(0x02, 00.2, 0.1));
|
||||||
|
|
||||||
// teleport
|
// teleport
|
||||||
let x = 10.0;
|
let x = 10.0;
|
||||||
let y = 120.0;
|
let y = 120.0;
|
||||||
let z = 10.0;
|
let z = 10.0;
|
||||||
|
let yaw = 10.0;
|
||||||
|
let pitch = 10.0;
|
||||||
client.teleport(x, y, z, 10.0, 10.0);
|
client.teleport(x, y, z, 10.0, 10.0);
|
||||||
let gameprofile = client.gameprofile.as_ref().unwrap();
|
let gameprofile = client.gameprofile.as_ref().unwrap();
|
||||||
// first send info update to our new player, So he can see his Skin
|
// first send info update to our new player, So he can see his Skin
|
||||||
// also send his info to everyone else
|
// also send his info to everyone else
|
||||||
self.broadcast_packet(
|
self.broadcast_packet(
|
||||||
client,
|
client,
|
||||||
CPlayerInfoUpdate::new(
|
&CPlayerInfoUpdate::new(
|
||||||
0x01,
|
0x01 | 0x08,
|
||||||
&[pumpkin_protocol::client::play::Player {
|
&[pumpkin_protocol::client::play::Player {
|
||||||
uuid: gameprofile.id,
|
uuid: gameprofile.id,
|
||||||
actions: vec![PlayerAction::AddPlayer {
|
actions: vec![
|
||||||
|
PlayerAction::AddPlayer {
|
||||||
name: gameprofile.name.clone(),
|
name: gameprofile.name.clone(),
|
||||||
properties: gameprofile.properties.clone(),
|
properties: gameprofile.properties.clone(),
|
||||||
}],
|
},
|
||||||
|
PlayerAction::UpdateListed { listed: true },
|
||||||
|
],
|
||||||
}],
|
}],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -190,37 +213,41 @@ impl Server {
|
|||||||
let gameprofile = client.gameprofile.as_ref().unwrap();
|
let gameprofile = client.gameprofile.as_ref().unwrap();
|
||||||
entries.push(pumpkin_protocol::client::play::Player {
|
entries.push(pumpkin_protocol::client::play::Player {
|
||||||
uuid: gameprofile.id,
|
uuid: gameprofile.id,
|
||||||
actions: vec![PlayerAction::AddPlayer {
|
actions: vec![
|
||||||
|
PlayerAction::AddPlayer {
|
||||||
name: gameprofile.name.clone(),
|
name: gameprofile.name.clone(),
|
||||||
properties: gameprofile.properties.clone(),
|
properties: gameprofile.properties.clone(),
|
||||||
}],
|
},
|
||||||
|
PlayerAction::UpdateListed { listed: true },
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
client.send_packet(CPlayerInfoUpdate::new(0x01, &entries));
|
client.send_packet(&CPlayerInfoUpdate::new(0x01 | 0x08, &entries));
|
||||||
|
|
||||||
// Start waiting for level chunks
|
// Start waiting for level chunks
|
||||||
client.send_packet(CGameEvent::new(13, 0.0));
|
client.send_packet(&CGameEvent::new(13, 0.0));
|
||||||
|
|
||||||
let gameprofile = client.gameprofile.as_ref().unwrap();
|
let gameprofile = client.gameprofile.as_ref().unwrap();
|
||||||
|
|
||||||
// spawn player for every client
|
// spawn player for every client
|
||||||
self.broadcast_packet_expect(
|
self.broadcast_packet_expect(
|
||||||
client,
|
client,
|
||||||
CSpawnEntity::new(
|
// TODO: add velo
|
||||||
|
&CSpawnEntity::new(
|
||||||
entity_id.into(),
|
entity_id.into(),
|
||||||
gameprofile.id,
|
UUID(gameprofile.id),
|
||||||
EntityType::Player.to_i32().unwrap().into(),
|
EntityType::Player.to_i32().unwrap().into(),
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
z,
|
z,
|
||||||
0,
|
pitch,
|
||||||
0,
|
yaw,
|
||||||
0,
|
yaw,
|
||||||
0.into(),
|
0.into(),
|
||||||
0,
|
0.0,
|
||||||
0,
|
0.0,
|
||||||
0,
|
0.0,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
// spawn players for our client
|
// spawn players for our client
|
||||||
@ -230,64 +257,64 @@ impl Server {
|
|||||||
if let Some(player) = &existing_client.player {
|
if let Some(player) = &existing_client.player {
|
||||||
let entity = &player.entity;
|
let entity = &player.entity;
|
||||||
let gameprofile = existing_client.gameprofile.as_ref().unwrap();
|
let gameprofile = existing_client.gameprofile.as_ref().unwrap();
|
||||||
client.send_packet(CSpawnEntity::new(
|
client.send_packet(&CSpawnEntity::new(
|
||||||
player.entity_id().into(),
|
player.entity_id().into(),
|
||||||
gameprofile.id,
|
UUID(gameprofile.id),
|
||||||
EntityType::Player.to_i32().unwrap().into(),
|
EntityType::Player.to_i32().unwrap().into(),
|
||||||
entity.x,
|
entity.x,
|
||||||
entity.y,
|
entity.y,
|
||||||
entity.z,
|
entity.z,
|
||||||
entity.yaw as u8,
|
entity.yaw,
|
||||||
entity.pitch as u8,
|
entity.pitch,
|
||||||
0,
|
entity.pitch,
|
||||||
0.into(),
|
0.into(),
|
||||||
0,
|
0.0,
|
||||||
0,
|
0.0,
|
||||||
0,
|
0.0,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// entity meta data
|
// entity meta data
|
||||||
/* if let Some(config) = &client.config {
|
if let Some(config) = &client.config {
|
||||||
self.broadcast_packet(
|
self.broadcast_packet(
|
||||||
client,
|
client,
|
||||||
CSetEntityMetadata::new(entity_id.into(), vec![Metadata::new(18, VarInt(0), 0)]),
|
&CSetEntityMetadata::new(
|
||||||
|
entity_id.into(),
|
||||||
|
Metadata::new(17, VarInt(0), config.skin_parts),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
Server::spawn_test_chunk(client).await;
|
Server::spawn_test_chunk(client).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a Packet to all Players
|
/// Sends a Packet to all Players
|
||||||
pub fn broadcast_packet<P>(&mut self, from: &mut Client, packet: P)
|
pub fn broadcast_packet<P>(&mut self, from: &mut Client, packet: &P)
|
||||||
where
|
where
|
||||||
P: ClientPacket,
|
P: ClientPacket,
|
||||||
P: Clone,
|
|
||||||
{
|
{
|
||||||
// we can't borrow twice at same time
|
// we can't borrow twice at same time
|
||||||
from.send_packet(packet.clone());
|
from.send_packet(packet);
|
||||||
for (_, client) in self.current_clients.iter().filter(|c| c.0 != &from.token) {
|
for (_, client) in self.current_clients.iter().filter(|c| c.0 != &from.token) {
|
||||||
// Check if client is a player
|
// Check if client is a player
|
||||||
let mut client = client.borrow_mut();
|
let mut client = client.borrow_mut();
|
||||||
if client.is_player() {
|
if client.is_player() {
|
||||||
// we need to clone, Because we send a new packet to every client
|
// we need to clone, Because we send a new packet to every client
|
||||||
client.send_packet(packet.clone());
|
client.send_packet(packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn broadcast_packet_expect<P>(&mut self, from: &mut Client, packet: P)
|
pub fn broadcast_packet_expect<P>(&mut self, from: &mut Client, packet: &P)
|
||||||
where
|
where
|
||||||
P: ClientPacket,
|
P: ClientPacket,
|
||||||
P: Clone,
|
|
||||||
{
|
{
|
||||||
for (_, client) in self.current_clients.iter().filter(|c| c.0 != &from.token) {
|
for (_, client) in self.current_clients.iter().filter(|c| c.0 != &from.token) {
|
||||||
// Check if client is a player
|
// Check if client is a player
|
||||||
let mut client = client.borrow_mut();
|
let mut client = client.borrow_mut();
|
||||||
if client.is_player() {
|
if client.is_player() {
|
||||||
// we need to clone, Because we send a new packet to every client
|
// we need to clone, Because we send a new packet to every client
|
||||||
client.send_packet(packet.clone());
|
client.send_packet(packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -363,7 +390,7 @@ impl Server {
|
|||||||
|
|
||||||
pub fn send_brand(&self, client: &mut Client) {
|
pub fn send_brand(&self, client: &mut Client) {
|
||||||
// send server brand
|
// send server brand
|
||||||
client.send_packet(CPluginMessage::new(
|
client.send_packet(&CPluginMessage::new(
|
||||||
"minecraft:brand",
|
"minecraft:brand",
|
||||||
&self.cached_server_brand,
|
&self.cached_server_brand,
|
||||||
));
|
));
|
||||||
|
|||||||
12
pumpkin/src/util/math.rs
Normal file
12
pumpkin/src/util/math.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
pub fn wrap_degrees(var: f32) -> f32 {
|
||||||
|
let mut var1 = var % 360.0;
|
||||||
|
if var1 >= 180.0 {
|
||||||
|
var1 -= 360.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if var1 < -180.0 {
|
||||||
|
var1 += 360.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var1
|
||||||
|
}
|
||||||
@ -1 +1 @@
|
|||||||
|
pub mod math;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user