diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 57acdd34d..af6568d7b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -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 \ No newline at end of file + - 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 \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 28e90afa5..6e165f7d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/pumpkin-core/Cargo.toml b/pumpkin-core/Cargo.toml index 34ad5239d..a77ec0311 100644 --- a/pumpkin-core/Cargo.toml +++ b/pumpkin-core/Cargo.toml @@ -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" \ No newline at end of file diff --git a/pumpkin-core/src/text/_README.md b/pumpkin-core/src/text/README.md similarity index 100% rename from pumpkin-core/src/text/_README.md rename to pumpkin-core/src/text/README.md diff --git a/pumpkin-core/src/text/color.rs b/pumpkin-core/src/text/color.rs index 6fa25219b..94d83f5bf 100644 --- a/pumpkin-core/src/text/color.rs +++ b/pumpkin-core/src/text/color.rs @@ -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")] diff --git a/pumpkin-core/src/text/mod.rs b/pumpkin-core/src/text/mod.rs index 9bd4499d2..706c034ea 100644 --- a/pumpkin-core/src/text/mod.rs +++ b/pumpkin-core/src/text/mod.rs @@ -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> { diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index 1ab7cba1c..044980cc4 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -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" + } +} diff --git a/pumpkin-inventory/src/player.rs b/pumpkin-inventory/src/player.rs index f7147f181..fd92220a9 100644 --- a/pumpkin-inventory/src/player.rs +++ b/pumpkin-inventory/src/player.rs @@ -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> { + 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 + } } diff --git a/pumpkin-protocol/src/bytebuf/mod.rs b/pumpkin-protocol/src/bytebuf/mod.rs index 11d304223..250d4fa1f 100644 --- a/pumpkin-protocol/src/bytebuf/mod.rs +++ b/pumpkin-protocol/src/bytebuf/mod.rs @@ -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); diff --git a/pumpkin-protocol/src/bytebuf/serializer.rs b/pumpkin-protocol/src/bytebuf/serializer.rs index 446c87650..825288c2a 100644 --- a/pumpkin-protocol/src/bytebuf/serializer.rs +++ b/pumpkin-protocol/src/bytebuf/serializer.rs @@ -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( self, _name: &'static str, - _variant_index: u32, + variant_index: u32, _variant: &'static str, - _value: &T, + value: &T, ) -> Result where T: ?Sized + Serialize, { - unimplemented!() + self.output.put_var_int(&variant_index.into()); + value.serialize(self) } fn serialize_none(self) -> Result { 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 { - 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 { - unimplemented!() + // Serialize ENUM index as varint + self.output.put_var_int(&variant_index.into()); + Ok(self) } fn serialize_u128(self, _v: u128) -> Result { 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 { - 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(&mut self, _value: &T) -> Result<(), Self::Error> + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> where T: ?Sized + Serialize, { - todo!() + value.serialize(&mut **self) } fn end(self) -> Result<(), Self::Error> { - todo!() + Ok(()) } } diff --git a/pumpkin-protocol/src/client/play/c_entity_velocity.rs b/pumpkin-protocol/src/client/play/c_entity_velocity.rs index 842fc1cda..fcfdccd59 100644 --- a/pumpkin-protocol/src/client/play/c_entity_velocity.rs +++ b/pumpkin-protocol/src/client/play/c_entity_velocity.rs @@ -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, diff --git a/pumpkin-protocol/src/client/play/c_hurt_animation.rs b/pumpkin-protocol/src/client/play/c_hurt_animation.rs index 6166acbd3..2b1b04d8f 100644 --- a/pumpkin-protocol/src/client/play/c_hurt_animation.rs +++ b/pumpkin-protocol/src/client/play/c_hurt_animation.rs @@ -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 } } } diff --git a/pumpkin-protocol/src/client/play/c_player_chat_message.rs b/pumpkin-protocol/src/client/play/c_player_chat_message.rs index e47e9d225..a79d0516c 100644 --- a/pumpkin-protocol/src/client/play/c_player_chat_message.rs +++ b/pumpkin-protocol/src/client/play/c_player_chat_message.rs @@ -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>, - /// See `FilterType` - filter_type: VarInt, - - // TODO: Implement - #[allow(dead_code)] - filter_type_bits: Option>, + filter_type: FilterType<'a>, chat_type: VarInt, sender_name: TextComponent<'a>, target_name: Option>, @@ -39,8 +32,7 @@ impl<'a> CPlayerChatMessage<'a> { salt: i64, previous_messages: &'a [PreviousMessage<'a>], unsigned_content: Option>, - filter_type: VarInt, - filter_type_bits: Option>, + filter_type: FilterType<'a>, chat_type: VarInt, sender_name: TextComponent<'a>, target_name: Option>, @@ -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, } diff --git a/pumpkin-protocol/src/client/play/c_remove_entities.rs b/pumpkin-protocol/src/client/play/c_remove_entities.rs index 9e89ec260..1b16ad92e 100644 --- a/pumpkin-protocol/src/client/play/c_remove_entities.rs +++ b/pumpkin-protocol/src/client/play/c_remove_entities.rs @@ -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, } } } diff --git a/pumpkin-protocol/src/client/play/c_set_container_content.rs b/pumpkin-protocol/src/client/play/c_set_container_content.rs new file mode 100644 index 000000000..791ee1db5 --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_set_container_content.rs @@ -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, + } + } +} diff --git a/pumpkin-protocol/src/client/play/c_set_container_slot.rs b/pumpkin-protocol/src/client/play/c_set_container_slot.rs new file mode 100644 index 000000000..e07d07d82 --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_set_container_slot.rs @@ -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, + } + } +} diff --git a/pumpkin-protocol/src/client/play/c_sync_player_position.rs b/pumpkin-protocol/src/client/play/c_sync_player_position.rs index 056214281..422160ad2 100644 --- a/pumpkin-protocol/src/client/play/c_sync_player_position.rs +++ b/pumpkin-protocol/src/client/play/c_sync_player_position.rs @@ -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, diff --git a/pumpkin-protocol/src/client/play/c_system_chat_message.rs b/pumpkin-protocol/src/client/play/c_system_chat_message.rs index a0f2342d1..2751b40c7 100644 --- a/pumpkin-protocol/src/client/play/c_system_chat_message.rs +++ b/pumpkin-protocol/src/client/play/c_system_chat_message.rs @@ -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 } } diff --git a/pumpkin-protocol/src/client/play/c_unload_chunk.rs b/pumpkin-protocol/src/client/play/c_unload_chunk.rs new file mode 100644 index 000000000..09294b9b0 --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_unload_chunk.rs @@ -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 } + } +} diff --git a/pumpkin-protocol/src/client/play/c_update_entitiy_pos_rot.rs b/pumpkin-protocol/src/client/play/c_update_entity_pos_rot.rs similarity index 100% rename from pumpkin-protocol/src/client/play/c_update_entitiy_pos_rot.rs rename to pumpkin-protocol/src/client/play/c_update_entity_pos_rot.rs diff --git a/pumpkin-protocol/src/client/play/mod.rs b/pumpkin-protocol/src/client/play/mod.rs index 2ff20e4c0..d5064aae4 100644 --- a/pumpkin-protocol/src/client/play/mod.rs +++ b/pumpkin-protocol/src/client/play/mod.rs @@ -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::*; diff --git a/pumpkin-protocol/src/lib.rs b/pumpkin-protocol/src/lib.rs index 3f7a4b607..e79388163 100644 --- a/pumpkin-protocol/src/lib.rs +++ b/pumpkin-protocol/src/lib.rs @@ -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)] diff --git a/pumpkin-protocol/src/packet_decoder.rs b/pumpkin-protocol/src/packet_decoder.rs index 3d3e3449a..308716ccb 100644 --- a/pumpkin-protocol/src/packet_decoder.rs +++ b/pumpkin-protocol/src/packet_decoder.rs @@ -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) { diff --git a/pumpkin-protocol/src/server/play/s_chat_message.rs b/pumpkin-protocol/src/server/play/s_chat_message.rs index fc153094a..9332a28f2 100644 --- a/pumpkin-protocol/src/server/play/s_chat_message.rs +++ b/pumpkin-protocol/src/server/play/s_chat_message.rs @@ -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, - 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), }) } } diff --git a/pumpkin-protocol/src/server/play/s_player_command.rs b/pumpkin-protocol/src/server/play/s_player_command.rs index 235baa6d5..f6365cc02 100644 --- a/pumpkin-protocol/src/server/play/s_player_command.rs +++ b/pumpkin-protocol/src/server/play/s_player_command.rs @@ -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 { 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(), }) diff --git a/pumpkin-protocol/src/slot.rs b/pumpkin-protocol/src/slot.rs index 6685ec207..2ccab79b9 100644 --- a/pumpkin-protocol/src/slot.rs +++ b/pumpkin-protocol/src/slot.rs @@ -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(&self, serializer: S) -> Result + 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 { 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 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> for Slot { + fn from(item: Option<&Item>) -> Self { + item.map(Slot::from).unwrap_or(Slot::empty()) + } +} diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index c6d12a44c..1bd1fe81c 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -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" diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs new file mode 100644 index 000000000..b8a242dbd --- /dev/null +++ b/pumpkin/src/client/container.rs @@ -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>>, + 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>>, + carried_item: Option<&'a Item>, + ) { + let player = self.player.as_ref().unwrap(); + + let slots: Vec = { + 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(), + )) + } +} diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index 30121afdc..7793ec46d 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -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 diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index b8a7853d7..a1b4273ec 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -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); } diff --git a/pumpkin/src/commands/cmd_gamemode.rs b/pumpkin/src/commands/cmd_gamemode.rs index 526ac9e56..a90de4eba 100644 --- a/pumpkin/src/commands/cmd_gamemode.rs +++ b/pumpkin/src/commands/cmd_gamemode.rs @@ -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() -> 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( diff --git a/pumpkin/src/commands/cmd_help.rs b/pumpkin/src/commands/cmd_help.rs index 893f19568..98a8bb22e 100644 --- a/pumpkin/src/commands/cmd_help.rs +++ b/pumpkin/src/commands/cmd_help.rs @@ -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( 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 ))); } diff --git a/pumpkin/src/commands/cmd_pumpkin.rs b/pumpkin/src/commands/cmd_pumpkin.rs index 25d515791..80347f7cc 100644 --- a/pumpkin/src/commands/cmd_pumpkin.rs +++ b/pumpkin/src/commands/cmd_pumpkin.rs @@ -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"); diff --git a/pumpkin/src/commands/cmd_stop.rs b/pumpkin/src/commands/cmd_stop.rs index b576c299d..81bd07ea9 100644 --- a/pumpkin/src/commands/cmd_stop.rs +++ b/pumpkin/src/commands/cmd_stop.rs @@ -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) + }), ) } diff --git a/pumpkin/src/commands/dispatcher.rs b/pumpkin/src/commands/dispatcher.rs index a01e20b81..dac9c8894 100644 --- a/pumpkin/src/commands/dispatcher.rs +++ b/pumpkin/src/commands/dispatcher.rs @@ -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)); + } } diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index 0dfa5483f..7ca503fd5 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -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), @@ -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 = 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) { diff --git a/pumpkin/src/commands/tree.rs b/pumpkin/src/commands/tree.rs index b4132b18e..59d2473c8 100644 --- a/pumpkin/src/commands/tree.rs +++ b/pumpkin/src/commands/tree.rs @@ -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>, pub(crate) children: Vec, + 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> = 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::() - + name.len() - + 2 - }) - .sum::(); - - 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> { diff --git a/pumpkin/src/commands/tree_builder.rs b/pumpkin/src/commands/tree_builder.rs index 63531909b..d34ee3a6a 100644 --- a/pumpkin/src/commands/tree_builder.rs +++ b/pumpkin/src/commands/tree_builder.rs @@ -11,10 +11,23 @@ impl<'a> CommandTree<'a> { self } - pub fn new(description: &'a str) -> Self { + /// provide at least one name + pub fn new( + 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, } } diff --git a/pumpkin/src/commands/tree_format.rs b/pumpkin/src/commands/tree_format.rs new file mode 100644 index 000000000..b13b0e1fe --- /dev/null +++ b/pumpkin/src/commands/tree_format.rs @@ -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 { + 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::>(); + + 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(()) + } +} diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index cc9ef23b5..316b2374e 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -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 { diff --git a/pumpkin/src/server.rs b/pumpkin/src/server.rs index 38133844b..0316a34b8 100644 --- a/pumpkin/src/server.rs +++ b/pumpkin/src/server.rs @@ -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

(&self, from: &[&Token], packet: &P) + /// Sends a packet to all players except those specified in `from` + pub fn broadcast_packet_except

(&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();