Merge branch 'master' into docker

This commit is contained in:
Erb3
2024-08-24 12:55:28 +02:00
committed by GitHub
41 changed files with 817 additions and 442 deletions

View File

@@ -8,19 +8,74 @@ env:
CARGO_TERM_COLOR: always
jobs:
build_and_test:
name: Rust project - latest
format:
name: Check formatting
runs-on: ubuntu-latest
strategy:
matrix:
toolchain:
- stable
- stable
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- run: cargo build --verbose
- run: cargo test --verbose
- run: cargo clippy --all-targets --all-features --no-default-features -- -D warnings
- run: cargo fmt --check
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- run: cargo fmt --check
clippy:
name: Run lints
runs-on: ubuntu-latest
strategy:
matrix:
toolchain:
- stable
steps:
- uses: actions/checkout@v4
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- run: cargo clippy --all-targets --all-features --no-default-features -- -D warnings
build_and_test:
name: Build project and test
runs-on: ubuntu-latest
strategy:
matrix:
toolchain:
- stable
steps:
- uses: actions/checkout@v4
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- run: cargo build --verbose
- run: cargo test --verbose
build_release:
name: Build project in release
runs-on: ubuntu-latest
strategy:
matrix:
toolchain:
- stable
steps:
- uses: actions/checkout@v4
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- run: cargo build --verbose --release
clippy_release:
name: Run lints in release mode
runs-on: ubuntu-latest
strategy:
matrix:
toolchain:
- stable
steps:
- uses: actions/checkout@v4
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- run: cargo clippy --release --all-targets --all-features --no-default-features -- -D warnings

295
Cargo.lock generated
View File

@@ -154,6 +154,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "cipher"
version = "0.4.4"
@@ -192,9 +198,9 @@ dependencies = [
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
@@ -267,6 +273,16 @@ dependencies = [
"hybrid-array",
]
[[package]]
name = "ctrlc"
version = "3.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
dependencies = [
"nix",
"windows-sys 0.59.0",
]
[[package]]
name = "der"
version = "0.7.9"
@@ -297,7 +313,7 @@ dependencies = [
"lazy_static",
"mintex",
"parking_lot",
"rustc-hash",
"rustc-hash 1.1.0",
"serde",
"serde_json",
"thousands",
@@ -331,31 +347,12 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "encoding_rs"
version = "0.8.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
dependencies = [
"cfg-if",
]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "fastnbt"
version = "2.5.0"
@@ -367,12 +364,6 @@ dependencies = [
"serde_bytes",
]
[[package]]
name = "fastrand"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
[[package]]
name = "fastsnbt"
version = "0.2.0"
@@ -411,21 +402,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@@ -675,22 +651,7 @@ dependencies = [
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
"webpki-roots",
]
[[package]]
@@ -805,12 +766,6 @@ version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "lock_api"
version = "0.4.12"
@@ -885,20 +840,15 @@ dependencies = [
]
[[package]]
name = "native-tls"
version = "0.2.12"
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.6.0",
"cfg-if",
"cfg_aliases",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
@@ -1009,50 +959,6 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "openssl"
version = "0.10.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1"
dependencies = [
"bitflags 2.6.0",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "parking_lot"
version = "0.12.3"
@@ -1144,12 +1050,6 @@ dependencies = [
"spki",
]
[[package]]
name = "pkg-config"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "png"
version = "0.17.13"
@@ -1194,6 +1094,7 @@ dependencies = [
"base64",
"bytes",
"crossbeam-channel",
"ctrlc",
"dhat",
"digest 0.11.0-pre.9",
"hmac",
@@ -1229,6 +1130,7 @@ dependencies = [
name = "pumpkin-core"
version = "0.1.0"
dependencies = [
"colored",
"fastnbt",
"serde",
"uuid",
@@ -1313,6 +1215,54 @@ dependencies = [
"tokio",
]
[[package]]
name = "quinn"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156"
dependencies = [
"bytes",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash 2.0.0",
"rustls",
"socket2",
"thiserror",
"tokio",
"tracing",
]
[[package]]
name = "quinn-proto"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd"
dependencies = [
"bytes",
"rand",
"ring",
"rustc-hash 2.0.0",
"rustls",
"slab",
"thiserror",
"tinyvec",
"tracing",
]
[[package]]
name = "quinn-udp"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285"
dependencies = [
"libc",
"once_cell",
"socket2",
"tracing",
"windows-sys 0.52.0",
]
[[package]]
name = "quote"
version = "1.0.36"
@@ -1389,7 +1339,6 @@ checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37"
dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
@@ -1398,29 +1347,31 @@ dependencies = [
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls",
"rustls-pemfile",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"system-configuration",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
"winreg",
]
@@ -1481,17 +1432,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.38.34"
name = "rustc-hash"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags 2.6.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
]
checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
[[package]]
name = "rustls"
@@ -1500,6 +1444,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044"
dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
@@ -1539,44 +1484,12 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "schannel"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.6.0",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "serde"
version = "1.0.205"
@@ -1792,19 +1705,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
[[package]]
name = "tempfile"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
dependencies = [
"cfg-if",
"fastrand",
"once_cell",
"rustix",
"windows-sys 0.59.0",
]
[[package]]
name = "thiserror"
version = "1.0.63"
@@ -1906,16 +1806,6 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.0"
@@ -2080,12 +1970,6 @@ dependencies = [
"serde",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.5"
@@ -2183,6 +2067,15 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki-roots"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "windows-sys"
version = "0.48.0"

View File

@@ -7,3 +7,4 @@ edition.workspace = true
serde = { version = "1.0", features = ["derive"] }
fastnbt = { git = "https://github.com/owengage/fastnbt.git" }
uuid.workspace = true
colored = "2"

View File

@@ -1,3 +1,4 @@
use colored::{ColoredString, Colorize};
use serde::{Deserialize, Serialize};
/// Text color
@@ -13,6 +14,32 @@ pub enum Color {
Named(NamedColor),
}
impl Color {
pub fn console_color(&self, text: &str) -> ColoredString {
match self {
Color::Reset => text.clear(),
Color::Named(color) => match color {
NamedColor::Black => text.black(),
NamedColor::DarkBlue => text.blue(),
NamedColor::DarkGreen => text.green(),
NamedColor::DarkAqua => text.cyan(),
NamedColor::DarkRed => text.red(),
NamedColor::DarkPurple => text.purple(),
NamedColor::Gold => text.yellow(),
NamedColor::Gray => text.bright_black(),
NamedColor::DarkGray => text.bright_black(), // ?
NamedColor::Blue => text.bright_blue(),
NamedColor::Green => text.bright_green(),
NamedColor::Aqua => text.cyan(),
NamedColor::Red => text.red(),
NamedColor::LightPurple => text.bright_purple(),
NamedColor::Yellow => text.bright_yellow(),
NamedColor::White => text.white(),
},
}
}
}
/// Named Minecraft color
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]

View File

@@ -3,6 +3,7 @@ use std::borrow::Cow;
use click::ClickEvent;
use color::Color;
use colored::Colorize;
use fastnbt::SerOpts;
use hover::HoverEvent;
use serde::{Deserialize, Serialize};
@@ -38,6 +39,36 @@ impl<'a> TextComponent<'a> {
style: Style::default(),
}
}
pub fn to_pretty_console(self) -> String {
let style = self.style;
let color = style.color;
let mut text = match self.content {
TextContent::Text { text } => text.into_owned(),
TextContent::Translate { translate, with: _ } => translate.into_owned(),
TextContent::EntityNames {
selector,
separator: _,
} => selector.into_owned(),
TextContent::Keybind { keybind } => keybind.into_owned(),
};
if let Some(color) = color {
text = color.console_color(&text).to_string();
}
if style.bold.is_some() {
text = text.bold().to_string();
}
if style.italic.is_some() {
text = text.italic().to_string();
}
if style.underlined.is_some() {
text = text.underline().to_string();
}
if style.strikethrough.is_some() {
text = text.strikethrough().to_string();
}
text
}
}
impl<'a> serde::Serialize for TextComponent<'a> {

View File

@@ -3,7 +3,7 @@ use num_derive::ToPrimitive;
pub mod player;
/// https://wiki.vg/Inventory
#[derive(Debug, ToPrimitive)]
#[derive(Debug, ToPrimitive, Clone)]
pub enum WindowType {
// not used
Generic9x1,
@@ -41,3 +41,13 @@ pub enum WindowType {
CartographyTable,
Stonecutter,
}
impl WindowType {
pub const fn default_title(&self) -> &'static str {
// TODO: Add titles here:
/*match self {
_ => "WINDOW TITLE",
}*/
"WINDOW TITLE"
}
}

View File

@@ -51,7 +51,7 @@ impl PlayerInventory {
1..=4 => self.crafting[slot - 1] = item,
5..=8 => {
match item {
None => self.armor[slot - 4] = None,
None => self.armor[slot - 5] = None,
Some(item) => {
// TODO: Replace asserts with error handling
match slot - 5 {
@@ -95,4 +95,13 @@ impl PlayerInventory {
debug_assert!((0..9).contains(&self.selected));
self.items[self.selected + 36 - 9].as_ref()
}
pub fn slots(&self) -> Vec<Option<&Item>> {
let mut slots = vec![self.crafting_output.as_ref()];
slots.extend(self.crafting.iter().map(|c| c.as_ref()));
slots.extend(self.armor.iter().map(|c| c.as_ref()));
slots.extend(self.items.iter().map(|c| c.as_ref()));
slots.push(self.offhand.as_ref());
slots
}
}

View File

@@ -1,4 +1,4 @@
use crate::{BitSet, VarInt, VarLongType};
use crate::{BitSet, FixedBitSet, VarInt, VarLongType};
use bytes::{Buf, BufMut, BytesMut};
use core::str;
use std::io::{self, Error, ErrorKind};
@@ -107,6 +107,10 @@ impl ByteBuffer {
uuid::Uuid::from_slice(&bytes).expect("Failed to parse UUID")
}
pub fn get_fixed_bitset(&mut self, bits: usize) -> FixedBitSet {
self.copy_to_bytes(bits.div_ceil(8))
}
pub fn put_bool(&mut self, v: bool) {
if v {
self.buffer.put_u8(1);

View File

@@ -48,6 +48,14 @@ impl ser::Error for SerializerError {
}
}
// General notes on the serializer:
//
// Primitives are written as-is
// Strings automatically pre-pend a varint
// Enums are written as a varint of the index
// Structs are ignored
// Iterables' values are written in order, but NO information (e.g. size) about the
// iterable itself is written (list sizes should be a seperate field)
impl<'a> ser::Serializer for &'a mut Serializer {
type Ok = ();
type Error = SerializerError;
@@ -114,14 +122,15 @@ impl<'a> ser::Serializer for &'a mut Serializer {
fn serialize_newtype_variant<T>(
self,
_name: &'static str,
_variant_index: u32,
variant_index: u32,
_variant: &'static str,
_value: &T,
value: &T,
) -> Result<Self::Ok, Self::Error>
where
T: ?Sized + Serialize,
{
unimplemented!()
self.output.put_var_int(&variant_index.into());
value.serialize(self)
}
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
self.output.put_bool(false);
@@ -160,7 +169,7 @@ impl<'a> ser::Serializer for &'a mut Serializer {
unimplemented!()
}
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Self::Error> {
unimplemented!()
Ok(self)
}
fn serialize_tuple_struct(
self,
@@ -172,11 +181,13 @@ impl<'a> ser::Serializer for &'a mut Serializer {
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
variant_index: u32,
_variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant, Self::Error> {
unimplemented!()
// Serialize ENUM index as varint
self.output.put_var_int(&variant_index.into());
Ok(self)
}
fn serialize_u128(self, _v: u128) -> Result<Self::Ok, Self::Error> {
unimplemented!()
@@ -206,10 +217,12 @@ impl<'a> ser::Serializer for &'a mut Serializer {
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant_index: u32,
_variant: &'static str,
) -> Result<Self::Ok, Self::Error> {
todo!()
// For ENUMs, only write enum index as varint
self.output.put_var_int(&variant_index.into());
Ok(())
}
}
@@ -237,15 +250,15 @@ impl<'a> ser::SerializeTuple for &'a mut Serializer {
type Ok = ();
type Error = SerializerError;
fn serialize_element<T>(&mut self, _value: &T) -> Result<(), Self::Error>
fn serialize_element<T>(&mut self, value: &T) -> Result<(), Self::Error>
where
T: ?Sized + Serialize,
{
todo!()
value.serialize(&mut **self)
}
fn end(self) -> Result<(), Self::Error> {
todo!()
Ok(())
}
}

View File

@@ -6,16 +6,16 @@ use crate::VarInt;
#[derive(Serialize)]
#[packet(0x5A)]
pub struct CEntityVelocity<'a> {
entitiy_id: &'a VarInt,
entity_id: &'a VarInt,
velocity_x: i16,
velocity_y: i16,
velocity_z: i16,
}
impl<'a> CEntityVelocity<'a> {
pub fn new(entitiy_id: &'a VarInt, velocity_x: f32, velocity_y: f32, velocity_z: f32) -> Self {
pub fn new(entity_id: &'a VarInt, velocity_x: f32, velocity_y: f32, velocity_z: f32) -> Self {
Self {
entitiy_id,
entity_id,
velocity_x: (velocity_x.clamp(-3.9, 3.9) * 8000.0) as i16,
velocity_y: (velocity_y.clamp(-3.9, 3.9) * 8000.0) as i16,
velocity_z: (velocity_z.clamp(-3.9, 3.9) * 8000.0) as i16,

View File

@@ -6,12 +6,12 @@ use crate::VarInt;
#[derive(Serialize)]
#[packet(0x24)]
pub struct CHurtAnimation<'a> {
entitiy_id: &'a VarInt,
entity_id: &'a VarInt,
yaw: f32,
}
impl<'a> CHurtAnimation<'a> {
pub fn new(entitiy_id: &'a VarInt, yaw: f32) -> Self {
Self { entitiy_id, yaw }
pub fn new(entity_id: &'a VarInt, yaw: f32) -> Self {
Self { entity_id, yaw }
}
}

View File

@@ -1,11 +1,9 @@
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use pumpkin_core::text::TextComponent;
use pumpkin_macros::packet;
use serde::Serialize;
use crate::{bytebuf::ByteBuffer, uuid::UUID, BitSet, ClientPacket, VarInt};
use crate::{uuid::UUID, BitSet, VarInt};
#[derive(Serialize)]
#[packet(0x39)]
pub struct CPlayerChatMessage<'a> {
sender: UUID,
@@ -17,12 +15,7 @@ pub struct CPlayerChatMessage<'a> {
previous_messages_count: VarInt,
previous_messages: &'a [PreviousMessage<'a>], // max 20
unsigned_content: Option<TextComponent<'a>>,
/// See `FilterType`
filter_type: VarInt,
// TODO: Implement
#[allow(dead_code)]
filter_type_bits: Option<BitSet<'a>>,
filter_type: FilterType<'a>,
chat_type: VarInt,
sender_name: TextComponent<'a>,
target_name: Option<TextComponent<'a>>,
@@ -39,8 +32,7 @@ impl<'a> CPlayerChatMessage<'a> {
salt: i64,
previous_messages: &'a [PreviousMessage<'a>],
unsigned_content: Option<TextComponent<'a>>,
filter_type: VarInt,
filter_type_bits: Option<BitSet<'a>>,
filter_type: FilterType<'a>,
chat_type: VarInt,
sender_name: TextComponent<'a>,
target_name: Option<TextComponent<'a>>,
@@ -56,7 +48,6 @@ impl<'a> CPlayerChatMessage<'a> {
previous_messages,
unsigned_content,
filter_type,
filter_type_bits,
chat_type,
sender_name,
target_name,
@@ -64,64 +55,19 @@ impl<'a> CPlayerChatMessage<'a> {
}
}
impl<'a> ClientPacket for CPlayerChatMessage<'a> {
fn write(&self, bytebuf: &mut ByteBuffer) {
bytebuf.put_uuid(self.sender.0);
bytebuf.put_var_int(&self.index);
bytebuf.put_option(&self.message_signature, |p, v| p.put_slice(v));
bytebuf.put_string(self.message);
bytebuf.put_i64(self.timestamp);
bytebuf.put_i64(self.salt);
if self.previous_messages_count.0 > 20 {
// TODO: Assert this is <=20
}
bytebuf.put_var_int(&self.previous_messages_count);
for previous_message in self.previous_messages {
bytebuf.put_var_int(&previous_message.message_id);
if let Some(prev_sig) = previous_message.signature {
// TODO: validate whether this should be None or not
bytebuf.put_slice(prev_sig);
}
}
bytebuf.put_option(&self.unsigned_content, |p, v| {
p.put_slice(v.encode().as_slice())
});
bytebuf.put_var_int(&self.filter_type);
match FilterType::from_i32(self.filter_type.0) {
Some(FilterType::PassThrough) => (),
Some(FilterType::FullyFiltered) => {
// TODO: Implement
}
Some(FilterType::PartiallyFiltered) => {
// TODO: Implement
}
None => {
// TODO: Implement
}
}
bytebuf.put_var_int(&self.chat_type);
bytebuf.put_slice(self.sender_name.encode().as_slice());
bytebuf.put_option(&self.target_name, |p, v| p.put_slice(v.encode().as_slice()));
}
}
#[derive(Serialize)]
pub struct PreviousMessage<'a> {
message_id: VarInt,
signature: Option<&'a [u8]>,
}
#[derive(FromPrimitive, ToPrimitive)]
pub enum FilterType {
#[derive(Serialize)]
#[repr(i32)]
pub enum FilterType<'a> {
/// Message is not filtered at all
PassThrough,
PassThrough = 0,
/// Message is fully filtered
FullyFiltered,
FullyFiltered = 1,
/// Only some characters in the message are filtered
PartiallyFiltered,
PartiallyFiltered(BitSet<'a>) = 2,
}

View File

@@ -7,14 +7,14 @@ use crate::VarInt;
#[packet(0x42)]
pub struct CRemoveEntities<'a> {
count: VarInt,
entitiy_ids: &'a [VarInt],
entity_ids: &'a [VarInt],
}
impl<'a> CRemoveEntities<'a> {
pub fn new(entitiy_ids: &'a [VarInt]) -> Self {
pub fn new(entity_ids: &'a [VarInt]) -> Self {
Self {
count: VarInt(entitiy_ids.len() as i32),
entitiy_ids,
count: VarInt(entity_ids.len() as i32),
entity_ids,
}
}
}

View File

@@ -0,0 +1,26 @@
use crate::slot::Slot;
use crate::VarInt;
use pumpkin_macros::packet;
use serde::Serialize;
#[derive(Serialize)]
#[packet(0x13)]
pub struct CSetContainerContent<'a> {
window_id: u8,
state_id: VarInt,
count: VarInt,
slot_data: &'a [Slot],
carried_item: &'a Slot,
}
impl<'a> CSetContainerContent<'a> {
pub fn new(window_id: u8, state_id: VarInt, slots: &'a [Slot], carried_item: &'a Slot) -> Self {
Self {
window_id,
state_id,
count: slots.len().into(),
slot_data: slots,
carried_item,
}
}
}

View File

@@ -0,0 +1,23 @@
use crate::slot::Slot;
use crate::VarInt;
use pumpkin_macros::packet;
use serde::Serialize;
#[derive(Serialize)]
#[packet(0x15)]
pub struct CSetContainerSlot<'a> {
window_id: i8,
state_id: VarInt,
slot: i16,
slot_data: &'a Slot,
}
impl<'a> CSetContainerSlot<'a> {
pub fn new(window_id: i8, state_id: i32, slot: usize, slot_data: &'a Slot) -> Self {
Self {
window_id,
state_id: state_id.into(),
slot: slot.try_into().unwrap(),
slot_data,
}
}
}

View File

@@ -5,7 +5,7 @@ use crate::VarInt;
#[derive(Serialize)]
#[packet(0x40)]
pub struct CSyncPlayerPostion {
pub struct CSyncPlayerPosition {
x: f64,
y: f64,
z: f64,
@@ -15,7 +15,7 @@ pub struct CSyncPlayerPostion {
teleport_id: VarInt,
}
impl CSyncPlayerPostion {
impl CSyncPlayerPosition {
pub fn new(
x: f64,
y: f64,

View File

@@ -4,12 +4,12 @@ use serde::Serialize;
#[derive(Serialize)]
#[packet(0x6C)]
pub struct CSystemChatMessge<'a> {
pub struct CSystemChatMessage<'a> {
content: TextComponent<'a>,
overlay: bool,
}
impl<'a> CSystemChatMessge<'a> {
impl<'a> CSystemChatMessage<'a> {
pub fn new(content: TextComponent<'a>, overlay: bool) -> Self {
Self { content, overlay }
}

View File

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

View File

@@ -22,14 +22,17 @@ mod c_player_chat_message;
mod c_player_info_update;
mod c_player_remove;
mod c_remove_entities;
mod c_set_container_content;
mod c_set_container_slot;
mod c_set_held_item;
mod c_set_title;
mod c_spawn_player;
mod c_subtitle;
mod c_sync_player_position;
mod c_system_chat_message;
mod c_update_entitiy_pos_rot;
mod c_unload_chunk;
mod c_update_entity_pos;
mod c_update_entity_pos_rot;
mod c_update_entity_rot;
mod c_worldevent;
mod player_action;
@@ -58,14 +61,17 @@ pub use c_player_chat_message::*;
pub use c_player_info_update::*;
pub use c_player_remove::*;
pub use c_remove_entities::*;
pub use c_set_container_content::*;
pub use c_set_container_slot::*;
pub use c_set_held_item::*;
pub use c_set_title::*;
pub use c_spawn_player::*;
pub use c_subtitle::*;
pub use c_sync_player_position::*;
pub use c_system_chat_message::*;
pub use c_update_entitiy_pos_rot::*;
pub use c_unload_chunk::*;
pub use c_update_entity_pos::*;
pub use c_update_entity_pos_rot::*;
pub use c_update_entity_rot::*;
pub use c_worldevent::*;
pub use player_action::*;

View File

@@ -21,6 +21,7 @@ pub const MAX_PACKET_SIZE: i32 = 2097152;
pub type Identifier = String;
pub type VarIntType = i32;
pub type VarLongType = i64;
pub type FixedBitSet = bytes::Bytes;
pub struct BitSet<'a>(pub VarInt, pub &'a [i64]);
@@ -142,7 +143,7 @@ pub enum PacketError {
#[error("packet length is out of bounds")]
OutOfBounds,
#[error("malformed packet length VarInt")]
MailformedLength,
MalformedLength,
}
#[derive(Debug, PartialEq)]

View File

@@ -28,7 +28,7 @@ impl PacketDecoder {
let packet_len = match VarInt::decode_partial(&mut r) {
Ok(len) => len,
Err(VarIntDecodeError::Incomplete) => return Ok(None),
Err(VarIntDecodeError::TooLarge) => Err(PacketError::MailformedLength)?,
Err(VarIntDecodeError::TooLarge) => Err(PacketError::MalformedLength)?,
};
if !(0..=MAX_PACKET_SIZE).contains(&packet_len) {

View File

@@ -3,7 +3,7 @@ use pumpkin_macros::packet;
use crate::{
bytebuf::{ByteBuffer, DeserializerError},
ServerPacket, VarInt,
FixedBitSet, ServerPacket, VarInt,
};
// derive(Deserialize)]
@@ -13,9 +13,8 @@ pub struct SChatMessage {
pub timestamp: i64,
pub salt: i64,
pub signature: Option<Bytes>,
pub messagee_count: VarInt,
// TODO: Properly implement BitSet decoding
// acknowledged: BitSet,
pub message_count: VarInt,
pub acknowledged: FixedBitSet,
}
// TODO
@@ -26,7 +25,8 @@ impl ServerPacket for SChatMessage {
timestamp: bytebuf.get_i64(),
salt: bytebuf.get_i64(),
signature: bytebuf.get_option(|v| v.copy_to_bytes(256)),
messagee_count: bytebuf.get_var_int(),
message_count: bytebuf.get_var_int(),
acknowledged: bytebuf.get_fixed_bitset(20),
})
}
}

View File

@@ -5,7 +5,7 @@ use crate::{bytebuf::DeserializerError, ServerPacket, VarInt};
#[packet(0x25)]
pub struct SPlayerCommand {
pub entitiy_id: VarInt,
pub entity_id: VarInt,
pub action: VarInt,
pub jump_boost: VarInt,
}
@@ -16,8 +16,8 @@ pub enum Action {
LeaveBed,
StartSprinting,
StopSprinting,
StartHourseJump,
StopHourseJump,
StartHorseJump,
StopHorseJump,
OpenVehicleInventory,
StartFlyingElytra,
}
@@ -25,7 +25,7 @@ pub enum Action {
impl ServerPacket for SPlayerCommand {
fn read(bytebuf: &mut crate::bytebuf::ByteBuffer) -> Result<Self, DeserializerError> {
Ok(Self {
entitiy_id: bytebuf.get_var_int(),
entity_id: bytebuf.get_var_int(),
action: bytebuf.get_var_int(),
jump_boost: bytebuf.get_var_int(),
})

View File

@@ -1,8 +1,9 @@
use crate::VarInt;
use pumpkin_world::item::Item;
use serde::ser::SerializeSeq;
use serde::{
de::{self, SeqAccess},
Deserialize,
Deserialize, Serialize, Serializer,
};
#[derive(Debug, Clone)]
@@ -76,6 +77,58 @@ impl<'de> Deserialize<'de> for Slot {
}
}
impl Serialize for Slot {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if self.item_count == 0.into() {
let mut s = serializer.serialize_seq(Some(1))?;
s.serialize_element(&self.item_count)?;
s.end()
} else {
match (&self.num_components_to_add, &self.num_components_to_remove) {
(Some(to_add), Some(to_remove)) => {
let mut s = serializer.serialize_seq(Some(6))?;
s.serialize_element(&self.item_count)?;
s.serialize_element(self.item_id.as_ref().unwrap())?;
s.serialize_element(to_add)?;
s.serialize_element(to_remove)?;
s.serialize_element(self.components_to_add.as_ref().unwrap())?;
s.serialize_element(self.components_to_remove.as_ref().unwrap())?;
s.end()
}
(None, Some(to_remove)) => {
let mut s = serializer.serialize_seq(Some(5))?;
s.serialize_element(&self.item_count)?;
s.serialize_element(self.item_id.as_ref().unwrap())?;
s.serialize_element(&VarInt(0))?;
s.serialize_element(to_remove)?;
s.serialize_element(self.components_to_remove.as_ref().unwrap())?;
s.end()
}
(Some(to_add), None) => {
let mut s = serializer.serialize_seq(Some(5))?;
s.serialize_element(&self.item_count)?;
s.serialize_element(self.item_id.as_ref().unwrap())?;
s.serialize_element(to_add)?;
s.serialize_element(&VarInt(0))?;
s.serialize_element(self.components_to_add.as_ref().unwrap())?;
s.end()
}
(None, None) => {
let mut s = serializer.serialize_seq(Some(4))?;
s.serialize_element(&self.item_count)?;
s.serialize_element(&self.item_id.as_ref().unwrap())?;
s.serialize_element(&VarInt(0))?;
s.serialize_element(&VarInt(0))?;
s.end()
}
}
}
}
}
impl Slot {
pub fn to_item(self) -> Option<Item> {
let item_id = self.item_id?.0.try_into().unwrap();
@@ -84,12 +137,35 @@ impl Slot {
item_count: self.item_count.0.try_into().unwrap(),
})
}
}
impl From<Slot> for Item {
fn from(slot: Slot) -> Self {
Item {
item_count: slot.item_count.0.try_into().unwrap(),
item_id: slot.item_id.unwrap().0.try_into().unwrap(),
pub const fn empty() -> Self {
Slot {
item_count: VarInt(0),
item_id: None,
num_components_to_add: None,
num_components_to_remove: None,
components_to_add: None,
components_to_remove: None,
}
}
}
impl From<&Item> for Slot {
fn from(item: &Item) -> Self {
Slot {
item_count: item.item_count.into(),
item_id: Some(item.item_id.into()),
// TODO: add these
num_components_to_add: None,
num_components_to_remove: None,
components_to_add: None,
components_to_remove: None,
}
}
}
impl From<Option<&Item>> for Slot {
fn from(item: Option<&Item>) -> Self {
item.map(Slot::from).unwrap_or(Slot::empty())
}
}

View File

@@ -33,12 +33,14 @@ num-traits = "0.2"
num-derive = "0.4"
num-bigint = "0.4.6"
ctrlc = "3.4"
# encryption
rsa = "0.9.6"
rsa-der = "0.3.0"
# authentication
reqwest = { version = "0.12.5", features = ["json"]}
reqwest = { version = "0.12.5", default-features=false, features = ["json", "rustls-tls", "http2", "macos-system-configuration"]}
sha1 = "0.10.6"
digest = "=0.11.0-pre.9"

View File

@@ -0,0 +1,85 @@
use pumpkin_core::text::TextComponent;
use pumpkin_inventory::WindowType;
use pumpkin_protocol::client::play::{COpenScreen, CSetContainerContent, CSetContainerSlot};
use pumpkin_protocol::slot::Slot;
use pumpkin_world::item::Item;
impl super::Client {
pub fn open_container(
&mut self,
window_type: WindowType,
minecraft_menu_id: &str,
window_title: Option<&str>,
items: Option<Vec<Option<&Item>>>,
carried_item: Option<&Item>,
) {
let menu_protocol_id = (*pumpkin_world::global_registry::REGISTRY
.get("minecraft:menu")
.unwrap()
.entries
.get(minecraft_menu_id)
.expect("Should be a valid menu id")
.get("protocol_id")
.unwrap())
.into();
let title = TextComponent::text(window_title.unwrap_or(window_type.default_title()));
self.send_packet(&COpenScreen::new(
(window_type.clone() as u8 + 1).into(),
menu_protocol_id,
title,
));
self.set_container_content(window_type, items, carried_item);
}
pub fn set_container_content<'a>(
&mut self,
window_type: WindowType,
items: Option<Vec<Option<&'a Item>>>,
carried_item: Option<&'a Item>,
) {
let player = self.player.as_ref().unwrap();
let slots: Vec<Slot> = {
if let Some(mut items) = items {
items.extend(player.inventory.slots());
items
} else {
player.inventory.slots()
}
.into_iter()
.map(|item| {
if let Some(item) = item {
Slot::from(item)
} else {
Slot::empty()
}
})
.collect()
};
let carried_item = {
if let Some(item) = carried_item {
item.into()
} else {
Slot::empty()
}
};
let packet =
CSetContainerContent::new(window_type as u8 + 1, 0.into(), &slots, &carried_item);
self.send_packet(&packet);
}
pub fn set_container_slot(
&mut self,
window_type: WindowType,
slot: usize,
item: Option<&Item>,
) {
self.send_packet(&CSetContainerSlot::new(
window_type as i8,
0,
slot,
&item.into(),
))
}
}

View File

@@ -19,7 +19,7 @@ use pumpkin_protocol::{
client::{
config::CConfigDisconnect,
login::CLoginDisconnect,
play::{CGameEvent, CPlayDisconnect, CSyncPlayerPostion, CSystemChatMessge},
play::{CGameEvent, CPlayDisconnect, CSyncPlayerPosition, CSystemChatMessage},
},
packet_decoder::PacketDecoder,
packet_encoder::PacketEncoder,
@@ -43,6 +43,7 @@ use thiserror::Error;
pub mod authentication;
mod client_packet;
mod container;
pub mod player_packet;
pub struct PlayerConfig {
@@ -159,7 +160,7 @@ impl Client {
entity.yaw = yaw;
entity.pitch = pitch;
player.awaiting_teleport = Some(id.into());
self.send_packet(&CSyncPlayerPostion::new(x, y, z, yaw, pitch, 0, id.into()));
self.send_packet(&CSyncPlayerPosition::new(x, y, z, yaw, pitch, 0, id.into()));
}
pub fn update_health(&mut self, health: f32, food: i32, food_saturation: f32) {
@@ -311,7 +312,7 @@ impl Client {
SPlayPingRequest::PACKET_ID => {
self.handle_play_ping_request(server, SPlayPingRequest::read(bytebuf).unwrap())
}
_ => log::error!("Failed to handle player packet id {}", packet.id.0),
_ => log::error!("Failed to handle player packet id {:#04x}", packet.id.0),
}
}
@@ -361,7 +362,7 @@ impl Client {
}
pub fn send_system_message(&mut self, text: TextComponent) {
self.send_packet(&CSystemChatMessge::new(text, false));
self.send_packet(&CSystemChatMessage::new(text, false));
}
/// Kicks the Client with a reason depending on the connection state

View File

@@ -176,7 +176,7 @@ impl Client {
pub fn handle_player_command(&mut self, _server: &mut Server, command: SPlayerCommand) {
let player = self.player.as_mut().unwrap();
if command.entitiy_id != player.entity.entity_id.into() {
if command.entity_id != player.entity.entity_id.into() {
return;
}
@@ -187,8 +187,8 @@ impl Client {
pumpkin_protocol::server::play::Action::LeaveBed => todo!(),
pumpkin_protocol::server::play::Action::StartSprinting => player.sprinting = true,
pumpkin_protocol::server::play::Action::StopSprinting => player.sprinting = false,
pumpkin_protocol::server::play::Action::StartHourseJump => todo!(),
pumpkin_protocol::server::play::Action::StopHourseJump => todo!(),
pumpkin_protocol::server::play::Action::StartHorseJump => todo!(),
pumpkin_protocol::server::play::Action::StopHorseJump => todo!(),
pumpkin_protocol::server::play::Action::OpenVehicleInventory => todo!(),
pumpkin_protocol::server::play::Action::StartFlyingElytra => {} // TODO
}
@@ -204,7 +204,7 @@ impl Client {
};
let player = self.player.as_mut().unwrap();
let id = player.entity_id();
server.broadcast_packet_expect(
server.broadcast_packet_except(
&[&self.token],
&CEntityAnimation::new(id.into(), animation as u8),
)
@@ -212,7 +212,13 @@ impl Client {
pub fn handle_chat_message(&mut self, server: &mut Server, chat_message: SChatMessage) {
dbg!("got message");
let message = chat_message.message;
if message.len() > 256 {
self.kick("Oversized message");
return;
}
// TODO: filter message & validation
let gameprofile = self.gameprofile.as_ref().unwrap();
@@ -227,8 +233,7 @@ impl Client {
chat_message.salt,
&[],
Some(TextComponent::text(&message)),
pumpkin_protocol::VarInt(FilterType::PassThrough as i32),
None,
FilterType::PassThrough,
1.into(),
TextComponent::text(&gameprofile.name.clone()),
None,
@@ -305,14 +310,14 @@ impl Client {
let packet = &CHurtAnimation::new(&entity_id, attacker_player.entity.yaw);
self.send_packet(packet);
client.send_packet(packet);
server.broadcast_packet_expect(
server.broadcast_packet_except(
&[self.token.as_ref(), token.as_ref()],
&CHurtAnimation::new(&entity_id, 10.0),
)
}
if config.swing {}
} else {
self.kick("Interacted with invalid entitiy id")
self.kick("Interacted with invalid entity id")
}
}
}
@@ -400,7 +405,12 @@ impl Client {
}
pub fn handle_set_creative_slot(&mut self, _server: &mut Server, packet: SSetCreativeSlot) {
let inventory = &mut self.player.as_mut().unwrap().inventory;
let player = self.player.as_mut().unwrap();
if player.gamemode != GameMode::Creative {
self.kick("Invalid action, you can only do that if you are in creative");
return;
}
let inventory = &mut player.inventory;
inventory.set_slot(packet.slot as usize, packet.clicked_item.to_item(), false);
}

View File

@@ -15,7 +15,7 @@ use crate::commands::CommandSender;
use crate::commands::CommandSender::Player;
use crate::entity::player::GameMode;
pub(crate) const NAME: &str = "gamemode";
const NAMES: [&str; 1] = ["gamemode"];
const DESCRIPTION: &str = "Change a player's gamemode.";
@@ -57,7 +57,7 @@ pub fn parse_arg_gamemode(consumed_args: &ConsumedArgs) -> Result<GameMode, Inva
}
pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(DESCRIPTION).with_child(
CommandTree::new(NAMES, DESCRIPTION).with_child(
require(&|sender| sender.permission_lvl() >= 2).with_child(
argument(ARG_GAMEMODE, consume_arg_gamemode)
.with_child(

View File

@@ -1,12 +1,11 @@
use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError;
use crate::commands::dispatcher::{CommandDispatcher, InvalidTreeError};
use crate::commands::tree::{CommandTree, ConsumedArgs, RawArgs};
use crate::commands::tree::{Command, CommandTree, ConsumedArgs, RawArgs};
use crate::commands::tree_builder::argument;
use crate::commands::{dispatcher_init, CommandSender, DISPATCHER};
use pumpkin_core::text::TextComponent;
pub(crate) const NAME: &str = "help";
pub(crate) const ALIAS: &str = "?";
const NAMES: [&str; 3] = ["help", "h", "?"];
const DESCRIPTION: &str = "Print a help message.";
@@ -17,41 +16,35 @@ fn consume_arg_command(_src: &CommandSender, args: &mut RawArgs) -> Option<Strin
let dispatcher = DISPATCHER.get_or_init(dispatcher_init);
if dispatcher.commands.contains_key(s) {
Some(s.into())
} else {
None
}
dispatcher.get_tree(s).ok().map(|tree| tree.names[0].into())
}
fn parse_arg_command<'a>(
consumed_args: &'a ConsumedArgs,
dispatcher: &'a CommandDispatcher,
) -> Result<(&'a str, &'a CommandTree<'a>), InvalidTreeError> {
) -> Result<&'a CommandTree<'a>, InvalidTreeError> {
let command_name = consumed_args
.get(ARG_COMMAND)
.ok_or(InvalidConsumptionError(None))?;
if let Some(tree) = dispatcher.commands.get::<&str>(&command_name.as_str()) {
Ok((command_name, tree))
} else {
Err(InvalidConsumptionError(Some(command_name.into())))
}
dispatcher
.get_tree(command_name)
.map_err(|_| InvalidConsumptionError(Some(command_name.into())))
}
pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(DESCRIPTION)
CommandTree::new(NAMES, DESCRIPTION)
.with_child(
argument(ARG_COMMAND, consume_arg_command).execute(&|sender, args| {
let dispatcher = DISPATCHER.get_or_init(dispatcher_init);
let (name, tree) = parse_arg_command(args, dispatcher)?;
let tree = parse_arg_command(args, dispatcher)?;
sender.send_message(TextComponent::text(&format!(
"{} - {} Usage:{}",
name,
"{} - {} Usage: {}",
tree.names.join("/"),
tree.description,
tree.paths_formatted(name)
tree
)));
Ok(())
@@ -60,12 +53,19 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
.execute(&|sender, _args| {
let dispatcher = DISPATCHER.get_or_init(dispatcher_init);
for (name, tree) in &dispatcher.commands {
let mut keys: Vec<&str> = dispatcher.commands.keys().copied().collect();
keys.sort();
for key in keys {
let Command::Tree(tree) = &dispatcher.commands[key] else {
continue;
};
sender.send_message(TextComponent::text(&format!(
"{} - {} Usage:{}",
name,
"{} - {} Usage: {}",
tree.names.join("/"),
tree.description,
tree.paths_formatted(name)
tree
)));
}

View File

@@ -4,12 +4,12 @@ use pumpkin_protocol::CURRENT_MC_PROTOCOL;
use crate::commands::tree::CommandTree;
pub(crate) const NAME: &str = "pumpkin";
const NAMES: [&str; 1] = ["pumpkin"];
const DESCRIPTION: &str = "Display information about Pumpkin.";
pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(DESCRIPTION).execute(&|sender, _| {
CommandTree::new(NAMES, DESCRIPTION).execute(&|sender, _| {
let version = env!("CARGO_PKG_VERSION");
let description = env!("CARGO_PKG_DESCRIPTION");

View File

@@ -1,13 +1,19 @@
use pumpkin_core::text::color::NamedColor;
use pumpkin_core::text::TextComponent;
use crate::commands::tree::CommandTree;
use crate::commands::tree_builder::require;
pub(crate) const NAME: &str = "stop";
const NAMES: [&str; 1] = ["stop"];
const DESCRIPTION: &str = "Stop the server.";
pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(DESCRIPTION).with_child(
require(&|sender| sender.permission_lvl() >= 4)
.execute(&|_sender, _args| std::process::exit(0)),
CommandTree::new(NAMES, DESCRIPTION).with_child(
require(&|sender| sender.permission_lvl() >= 4).execute(&|sender, _args| {
sender
.send_message(TextComponent::text("Stopping Server").color_named(NamedColor::Red));
std::process::exit(0)
}),
)
}

View File

@@ -1,7 +1,7 @@
use crate::commands::dispatcher::InvalidTreeError::{
InvalidConsumptionError, InvalidRequirementError,
};
use crate::commands::tree::{CommandTree, ConsumedArgs, NodeType, RawArgs};
use crate::commands::tree::{Command, CommandTree, ConsumedArgs, NodeType, RawArgs};
use crate::commands::CommandSender;
use std::collections::HashMap;
@@ -17,7 +17,7 @@ pub(crate) enum InvalidTreeError {
}
pub(crate) struct CommandDispatcher<'a> {
pub(crate) commands: HashMap<&'a str, CommandTree<'a>>,
pub(crate) commands: HashMap<&'a str, Command<'a>>,
}
/// Stores registered [CommandTree]s and dispatches commands to them.
@@ -28,7 +28,7 @@ impl<'a> CommandDispatcher<'a> {
let key = parts.next().ok_or("Empty Command")?;
let raw_args: Vec<&str> = parts.rev().collect();
let tree = self.commands.get(key).ok_or("Command not found")?;
let tree = self.get_tree(key)?;
// try paths until fitting path is found
for path in tree.iter_paths() {
@@ -49,10 +49,22 @@ impl<'a> CommandDispatcher<'a> {
}
}
Err(format!(
"Invalid Syntax. Usage:{}",
tree.paths_formatted(key)
))
Err(format!("Invalid Syntax. Usage: {}", tree))
}
pub(crate) fn get_tree(&'a self, key: &str) -> Result<&'a CommandTree<'a>, String> {
let command = self.commands.get(key).ok_or("Command not found")?;
match command {
Command::Tree(tree) => Ok(tree),
Command::Alias(target) => {
let Some(Command::Tree(tree)) = &self.commands.get(target) else {
println!("Error while parsing command alias \"{key}\": pointing to \"{target}\" which is not a valid tree");
return Err("Internal Error (See logs for details)".into());
};
Ok(tree)
}
}
}
fn try_is_fitting_path(
@@ -99,4 +111,17 @@ impl<'a> CommandDispatcher<'a> {
Ok(false)
}
/// Register a command with the dispatcher.
pub(crate) fn register(&mut self, tree: CommandTree<'a>) {
let mut names = tree.names.iter();
let primary_name = names.next().expect("at least one name must be provided");
for &name in names {
self.commands.insert(name, Command::Alias(primary_name));
}
self.commands.insert(primary_name, Command::Tree(tree));
}
}

View File

@@ -12,6 +12,7 @@ mod cmd_stop;
mod dispatcher;
mod tree;
mod tree_builder;
mod tree_format;
pub enum CommandSender<'a> {
Rcon(&'a mut Vec<String>),
@@ -23,9 +24,9 @@ impl<'a> CommandSender<'a> {
pub fn send_message(&mut self, text: TextComponent) {
match self {
// TODO: add color and stuff to console
CommandSender::Console => log::info!("{:?}", text.content),
CommandSender::Console => log::info!("{}", text.to_pretty_console()),
CommandSender::Player(c) => c.send_system_message(text),
CommandSender::Rcon(s) => s.push(format!("{:?}", text.content)),
CommandSender::Rcon(s) => s.push(text.to_pretty_console()),
}
}
@@ -70,15 +71,16 @@ static DISPATCHER: OnceLock<CommandDispatcher> = OnceLock::new();
/// create [CommandDispatcher] instance for [DISPATCHER]
fn dispatcher_init<'a>() -> CommandDispatcher<'a> {
let mut map = HashMap::new();
let mut dispatcher = CommandDispatcher {
commands: HashMap::new(),
};
map.insert(cmd_pumpkin::NAME, cmd_pumpkin::init_command_tree());
map.insert(cmd_gamemode::NAME, cmd_gamemode::init_command_tree());
map.insert(cmd_stop::NAME, cmd_stop::init_command_tree());
map.insert(cmd_help::NAME, cmd_help::init_command_tree());
map.insert(cmd_help::ALIAS, cmd_help::init_command_tree());
dispatcher.register(cmd_pumpkin::init_command_tree());
dispatcher.register(cmd_gamemode::init_command_tree());
dispatcher.register(cmd_stop::init_command_tree());
dispatcher.register(cmd_help::init_command_tree());
CommandDispatcher { commands: map }
dispatcher
}
pub fn handle_command(sender: &mut CommandSender, cmd: &str) {

View File

@@ -2,6 +2,7 @@ use std::collections::{HashMap, VecDeque};
use crate::commands::dispatcher::InvalidTreeError;
use crate::commands::CommandSender;
/// see [crate::commands::tree_builder::argument]
pub(crate) type RawArgs<'a> = Vec<&'a str>;
@@ -31,9 +32,15 @@ pub(crate) enum NodeType<'a> {
},
}
pub(crate) enum Command<'a> {
Tree(CommandTree<'a>),
Alias(&'a str),
}
pub(crate) struct CommandTree<'a> {
pub(crate) nodes: Vec<Node<'a>>,
pub(crate) children: Vec<usize>,
pub(crate) names: Vec<&'a str>,
pub(crate) description: &'a str,
}
@@ -51,57 +58,6 @@ impl<'a> CommandTree<'a> {
todo,
}
}
/// format possible paths as [String], using ```name``` as the command name
///
/// todo: merge into single line
pub(crate) fn paths_formatted(&'a self, name: &str) -> String {
let paths: Vec<Vec<&NodeType>> = self
.iter_paths()
.map(|path| path.iter().map(|&i| &self.nodes[i].node_type).collect())
.collect();
let len = paths
.iter()
.map(|path| {
path.iter()
.map(|node| match node {
NodeType::ExecuteLeaf { .. } => 0,
NodeType::Literal { string } => string.len() + 1,
NodeType::Argument { name, .. } => name.len() + 3,
NodeType::Require { .. } => 0,
})
.sum::<usize>()
+ name.len()
+ 2
})
.sum::<usize>();
let mut s = String::with_capacity(len);
for path in paths.iter() {
s.push(if paths.len() > 1 { '\n' } else { ' ' });
s.push('/');
s.push_str(name);
for node in path {
match node {
NodeType::Literal { string } => {
s.push(' ');
s.push_str(string);
}
NodeType::Argument { name, .. } => {
s.push(' ');
s.push('<');
s.push_str(name);
s.push('>');
}
_ => {}
}
}
}
s
}
}
struct TraverseAllPathsIter<'a> {

View File

@@ -11,10 +11,23 @@ impl<'a> CommandTree<'a> {
self
}
pub fn new(description: &'a str) -> Self {
/// provide at least one name
pub fn new<const NAME_COUNT: usize>(
names: [&'a str; NAME_COUNT],
description: &'a str,
) -> Self {
assert!(NAME_COUNT > 0);
let mut names_vec = Vec::with_capacity(NAME_COUNT);
for name in names {
names_vec.push(name);
}
Self {
nodes: Vec::new(),
children: Vec::new(),
names: names_vec,
description,
}
}

View File

@@ -0,0 +1,125 @@
use crate::commands::tree::{CommandTree, Node, NodeType};
use std::collections::VecDeque;
use std::fmt::{Display, Formatter, Write};
trait IsVisible {
/// whether node should be printed in help command/usage hint
fn is_visible(&self) -> bool;
}
impl<'a> IsVisible for Node<'a> {
fn is_visible(&self) -> bool {
match self.node_type {
NodeType::ExecuteLeaf { .. } => false,
NodeType::Literal { .. } => true,
NodeType::Argument { .. } => true,
NodeType::Require { .. } => false,
}
}
}
impl<'a> Display for Node<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.node_type {
NodeType::Literal { string } => {
f.write_str(string)?;
}
NodeType::Argument { name, .. } => {
f.write_char('<')?;
f.write_str(name)?;
f.write_char('>')?;
}
_ => {}
};
Ok(())
}
}
fn flatten_require_nodes(nodes: &[Node], children: &[usize]) -> Vec<usize> {
let mut new_children = Vec::with_capacity(children.len());
for &i in children {
let node = &nodes[i];
match &node.node_type {
NodeType::Require { .. } => {
new_children.extend(flatten_require_nodes(nodes, node.children.as_slice()))
}
_ => new_children.push(i),
}
}
new_children
}
impl<'a> Display for CommandTree<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_char('/')?;
f.write_str(self.names[0])?;
let mut todo = VecDeque::<&[usize]>::with_capacity(self.children.len());
todo.push_back(&self.children);
loop {
let Some(children) = todo.pop_front() else {
break;
};
let flattened_children = flatten_require_nodes(&self.nodes, children);
let visible_children = flattened_children
.iter()
.copied()
.filter(|&i| self.nodes[i].is_visible())
.collect::<Vec<_>>();
if visible_children.is_empty() {
break;
};
f.write_char(' ')?;
let is_optional = flattened_children
.iter()
.map(|&i| &self.nodes[i].node_type)
.any(|node| matches!(node, NodeType::ExecuteLeaf { .. }));
if is_optional {
f.write_char('[')?;
}
match visible_children.as_slice() {
[] => unreachable!(),
[i] => {
let node = &self.nodes[*i];
node.fmt(f)?;
todo.push_back(&node.children);
}
_ => {
// todo: handle cases where one of these nodes has visible children
f.write_char('(')?;
let mut iter = visible_children.iter().map(|&i| &self.nodes[i]);
if let Some(node) = iter.next() {
node.fmt(f)?;
}
for node in iter {
f.write_char('|')?;
node.fmt(f)?;
}
f.write_char(')')?;
}
}
if is_optional {
f.write_char(']')?;
}
}
Ok(())
}
}

View File

@@ -31,6 +31,8 @@ static ALLOC: dhat::Alloc = dhat::Alloc;
#[cfg(not(target_os = "wasi"))]
fn main() -> io::Result<()> {
use pumpkin_core::text::{color::NamedColor, TextComponent};
#[cfg(feature = "dhat-heap")]
let _profiler = dhat::Profiler::new_heap();
#[cfg(feature = "dhat-heap")]
@@ -39,6 +41,17 @@ fn main() -> io::Result<()> {
.enable_all()
.build()
.unwrap();
ctrlc::set_handler(|| {
log::warn!(
"{}",
TextComponent::text("Stopping Server")
.color_named(NamedColor::Red)
.to_pretty_console()
);
std::process::exit(0);
})
.unwrap();
// ensure rayon is built outside of tokio scope
rayon::ThreadPoolBuilder::new().build_global().unwrap();
rt.block_on(async {

View File

@@ -17,7 +17,6 @@ use mio::{event::Event, Poll, Token};
use num_traits::ToPrimitive;
use pumpkin_entity::{entity_type::EntityType, EntityId};
use pumpkin_protocol::{
bytebuf::ByteBuffer,
client::{
config::CPluginMessage,
play::{
@@ -141,11 +140,11 @@ impl Server {
if client.is_player() {
let id = client.player.as_ref().unwrap().entity_id();
let uuid = client.gameprofile.as_ref().unwrap().id;
self.broadcast_packet_expect(
self.broadcast_packet_except(
&[&client.token],
&CRemovePlayerInfo::new(1.into(), &[UUID(uuid)]),
);
self.broadcast_packet_expect(&[&client.token], &CRemoveEntities::new(&[id.into()]))
self.broadcast_packet_except(&[&client.token], &CRemoveEntities::new(&[id.into()]))
}
}
@@ -241,7 +240,7 @@ impl Server {
let gameprofile = client.gameprofile.as_ref().unwrap();
// spawn player for every client
self.broadcast_packet_expect(
self.broadcast_packet_except(
&[&client.token],
// TODO: add velo
&CSpawnEntity::new(
@@ -327,7 +326,8 @@ impl Server {
}
}
pub fn broadcast_packet_expect<P>(&self, from: &[&Token], packet: &P)
/// Sends a packet to all players except those specified in `from`
pub fn broadcast_packet_except<P>(&self, from: &[&Token], packet: &P)
where
P: ClientPacket,
{
@@ -363,14 +363,15 @@ impl Server {
chunk_z: 0.into(),
});
while let Some((chunk_pos, chunk_data)) = chunk_receiver.recv().await {
while let Some((_chunk_pos, chunk_data)) = chunk_receiver.recv().await {
// dbg!(chunk_pos);
let chunk_data = match chunk_data {
Ok(d) => d,
Err(_) => continue,
};
#[cfg(debug_assertions)]
if chunk_pos == (0, 0) {
if _chunk_pos == (0, 0) {
use pumpkin_protocol::bytebuf::ByteBuffer;
let mut test = ByteBuffer::empty();
CChunkData(&chunk_data).write(&mut test);
let len = test.buf().len();