Compare commits

...

9 Commits

Author SHA1 Message Date
Alexander Medvedev
e42c6d8794 fix: egg 2026-08-25 19:32:19 +02:00
Demetrius Kanios
16c27a93c4 chore(plugin-api): Update wit-bindgen to 0.61.1 and remove workaround (#3048) 2026-08-25 10:10:53 -07:00
Demetrius Kanios
24ae9faa4f chore(bedrock): Update most of the packets to mirror the official docs (#3044)
* chore(bedrock): Update most of the packets to mirror the official docs
more closely.

* Fix typo

* Fix CCreativeContent

* Undo unrelated `registry.rs` change

* Update WIT to merged PR

---------

Signed-off-by: Demetrius Kanios <demetrius@kanios.net>
2026-08-25 09:06:23 -07:00
Alexander Medvedev
a2881a96fd chore(ci): add musl builds 2026-08-25 15:32:07 +02:00
Alexander Medvedev
d8c7c23c0b fix: respawn in nether 2026-08-25 13:42:47 +02:00
Alexander Medvedev
dafc2b2000 fix: https://github.com/Pumpkin-MC/Pumpkin/issues/3029 2026-08-25 11:36:51 +02:00
yunuservices
f3e4788462 fix(wasm-plugins): fix the panic on hot-reloading (#3036)
* fix(wasm-plugins): fix the panic on hot-reloading

* fix(wasm-plugins): do not cache permission denials from non-main threads
2026-08-25 11:28:43 +02:00
Missing_Love
85d635a42b feat(bungeecord): add BungeeGuard auth support (#3038)
* feat(bungeeguard): add BungeeGuard token authentication

Add a BungeeGuard shared secret to the proxy configuration, validate the token during the BungeeCord login flow, reject connections with invalid tokens, and prevent players from bypassing the proxy to connect directly.

* fix

* fix

* fix:ci
2026-08-25 08:35:06 +02:00
Bryan
2b0a20d859 perf(world): batch entity ticks instead of one task per entity (#3008) 2026-08-25 08:32:54 +02:00
179 changed files with 8582 additions and 2921 deletions

View File

@@ -41,17 +41,41 @@ jobs:
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- uses: Swatinem/rust-cache@v2
- name: Install musl-tools (Linux only)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y musl-tools
- name: Build Release
shell: bash
run: |
cargo build --verbose --release
if [ "${{ runner.os }}" == "Linux" ]; then
if [ "${{ runner.arch }}" == "X64" ]; then
TARGET="x86_64-unknown-linux-musl"
elif [ "${{ runner.arch }}" == "ARM64" ]; then
TARGET="aarch64-unknown-linux-musl"
fi
rustup target add "$TARGET"
cargo build --verbose --release --target "$TARGET"
fi
- name: Prepare artifacts
shell: bash
run: |
mkdir -p dist
EXT=""
if [ "${{ runner.os }}" == "Windows" ]; then EXT=".exe"; fi
cp "target/release/pumpkin$EXT" "dist/pumpkin-${{ runner.arch }}-${{ runner.os }}$EXT"
if [ "${{ runner.os }}" == "Linux" ]; then
if [ "${{ runner.arch }}" == "X64" ]; then
TARGET="x86_64-unknown-linux-musl"
elif [ "${{ runner.arch }}" == "ARM64" ]; then
TARGET="aarch64-unknown-linux-musl"
fi
cp "target/$TARGET/release/pumpkin" "dist/pumpkin-${{ runner.arch }}-${{ runner.os }}-musl"
fi
- name: Upload unsigned executable for SignPath (Windows only)
if: runner.os == 'Windows'

View File

@@ -28,7 +28,6 @@ jobs:
machete:
name: Detect unused dependencies
runs-on: ubuntu-latest
needs: [format]
strategy:
matrix:
toolchain:
@@ -42,7 +41,6 @@ jobs:
clippy_debug:
name: Run lints (debug)
runs-on: ubuntu-latest
needs: [machete]
strategy:
matrix:
toolchain:
@@ -59,7 +57,6 @@ jobs:
clippy_release:
name: Run lints (release)
runs-on: ubuntu-latest
needs: [machete]
strategy:
matrix:
toolchain:
@@ -76,7 +73,7 @@ jobs:
build_and_test:
name: Build project and test
runs-on: ${{ matrix.os }}
needs: [clippy_debug, clippy_release]
needs: [format, machete, clippy_debug, clippy_release]
strategy:
matrix:
os:
@@ -124,17 +121,41 @@ jobs:
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- uses: Swatinem/rust-cache@v2
- name: Install musl-tools (Linux only)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y musl-tools
- name: Build Release
shell: bash
run: |
cargo build --verbose --release
if [ "${{ runner.os }}" == "Linux" ]; then
if [ "${{ runner.arch }}" == "X64" ]; then
TARGET="x86_64-unknown-linux-musl"
elif [ "${{ runner.arch }}" == "ARM64" ]; then
TARGET="aarch64-unknown-linux-musl"
fi
rustup target add "$TARGET"
cargo build --verbose --release --target "$TARGET"
fi
- name: Prepare artifacts
shell: bash
run: |
mkdir -p dist
EXT=""
if [ "${{ runner.os }}" == "Windows" ]; then EXT=".exe"; fi
cp "target/release/pumpkin$EXT" "dist/pumpkin-${{ runner.arch }}-${{ runner.os }}$EXT"
if [ "${{ runner.os }}" == "Linux" ]; then
if [ "${{ runner.arch }}" == "X64" ]; then
TARGET="x86_64-unknown-linux-musl"
elif [ "${{ runner.arch }}" == "ARM64" ]; then
TARGET="aarch64-unknown-linux-musl"
fi
cp "target/$TARGET/release/pumpkin" "dist/pumpkin-${{ runner.arch }}-${{ runner.os }}-musl"
fi
- name: Export executable
uses: actions/upload-artifact@v7
with:

58
Cargo.lock generated
View File

@@ -281,7 +281,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.3",
"syn 3.0.4",
]
[[package]]
@@ -458,7 +458,7 @@ checksum = "46d07918caa9eeaaf06b7873925c53a61daac173539b4f7715090745e44e4e69"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.3",
"syn 3.0.4",
]
[[package]]
@@ -707,7 +707,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 3.0.3",
"syn 3.0.4",
]
[[package]]
@@ -1454,7 +1454,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.3",
"syn 3.0.4",
]
[[package]]
@@ -1786,7 +1786,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.3",
"syn 3.0.4",
]
[[package]]
@@ -1898,9 +1898,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.4.18"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "839c0e8a181239723652be9062bb56ca5bf5f64011f73b623f6f4fc59086a228"
checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16"
dependencies = [
"atomic-waker",
"bytes",
@@ -2757,7 +2757,7 @@ checksum = "e4e98dc3b890f6c23a0f9d3d491a2823d0dea0fa656302a13dd225fa924112a8"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.3",
"syn 3.0.4",
]
[[package]]
@@ -3289,7 +3289,7 @@ checksum = "1c8d9ca532f185d5d4db7a7c9d51420b452168ea1c2b913953281bd6fe1fcbd0"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.3",
"syn 3.0.4",
]
[[package]]
@@ -3389,7 +3389,7 @@ dependencies = [
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 3.0.3",
"syn 3.0.4",
]
[[package]]
@@ -3416,7 +3416,7 @@ dependencies = [
"serde",
"serde_json",
"serde_repr",
"syn 3.0.3",
"syn 3.0.4",
"wit-encoder",
]
@@ -3477,7 +3477,7 @@ dependencies = [
"proc-macro2",
"pumpkin-data",
"quote",
"syn 3.0.3",
"syn 3.0.4",
]
[[package]]
@@ -3566,7 +3566,7 @@ dependencies = [
"serde",
"serde_json",
"sha2 0.11.0",
"syn 3.0.3",
"syn 3.0.4",
"thiserror 2.0.20",
"ureq",
"uuid",
@@ -3845,7 +3845,7 @@ checksum = "1c25ef604ac7dd839d44d64648952ea23c97866f124ff671b0ed2cf3ad9bb06e"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.3",
"syn 3.0.4",
]
[[package]]
@@ -4357,7 +4357,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.3",
"syn 3.0.4",
]
[[package]]
@@ -4382,7 +4382,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.3",
"syn 3.0.4",
]
[[package]]
@@ -4626,9 +4626,9 @@ dependencies = [
[[package]]
name = "syn"
version = "3.0.3"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
dependencies = [
"proc-macro2",
"quote",
@@ -4730,7 +4730,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.3",
"syn 3.0.4",
]
[[package]]
@@ -4835,7 +4835,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.3",
"syn 3.0.4",
]
[[package]]
@@ -6022,18 +6022,18 @@ dependencies = [
[[package]]
name = "wit-bindgen"
version = "0.61.0"
version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbfef9403167686bfe35ffa8787df4c0f50742e5b0f471c7fb156a6614193c5d"
checksum = "e473fd0095479f9689ac7d2a52c427cc96bb2b973ace50238dfcc1ab1cd52d93"
dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen-core"
version = "0.61.0"
version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4814f3904ce1f5ba05f82d868edd2e0fc197b3f87f930b0777ca795de17df772"
checksum = "87445680dfe6d6b5369e884bd63c03a3335268e855365b2fc3f7bcdce96a630e"
dependencies = [
"anyhow",
"heck",
@@ -6042,9 +6042,9 @@ dependencies = [
[[package]]
name = "wit-bindgen-rust"
version = "0.61.0"
version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c760bc63d9d3271f0e6476eba4c37707d983cf492095a9db1fed232f29d551"
checksum = "72f2fe494de0d898216caf0fd0ae76af75753fcb3ff4f465b46131537cf24cf8"
dependencies = [
"anyhow",
"heck",
@@ -6058,9 +6058,9 @@ dependencies = [
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.61.0"
version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3521448f8b53272f9aa1caf134e8f008ce13a2b43b832672435c0c5328b01077"
checksum = "59283a01aec94f60f92ada22ffe182a5cea8acfce43be3be688de8d52df55312"
dependencies = [
"anyhow",
"prettyplease",
@@ -6360,7 +6360,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.3",
"syn 3.0.4",
]
[[package]]

View File

@@ -89,8 +89,9 @@ and customizable experience. It prioritizes performance and player enjoyment whi
- [x] Permissions
- [x] Translations
- Proxy
- [x] Bungeecord
- [x] Velocity
- [x] [BungeeCord](https://github.com/SpigotMC/BungeeCord)
- [x] [BungeeGuard](https://github.com/lucko/BungeeGuard)
- [x] [Velocity](https://github.com/PaperMC/Velocity)
<!-- Check out our [Github Project](https://github.com/orgs/Pumpkin-MC/projects/3) to see current progress. -->

View File

@@ -20,6 +20,12 @@ pub struct ProxyConfig {
pub struct BungeeCordConfig {
/// Whether `BungeeCord` support is enabled.
pub enabled: bool,
/// Shared secret for authenticating connections from the `BungeeCord`
/// proxy, as provided by the `BungeeGuard` plugin. When set, the forwarded
/// profile properties must contain a `bungeeguard-token` property holding
/// this secret, otherwise the connection is rejected. This also blocks
/// players connecting directly instead of through the proxy.
pub secret: String,
}
/// Configuration for Velocity proxy integration.

View File

@@ -467,16 +467,3 @@ pub mod persistent_data;
pub use persistent_data::PersistentDataHolder;
/// Game rules definitions and values.
pub use wit::pumpkin::plugin::game_rules::{GameRule, GameRuleValue};
/// Stub for component model `cabi_realloc`
/// Remove me after bytecodealliance/wit-bindgen#1697 makes it to release
#[cfg(not(target_arch = "wasm32"))]
#[unsafe(no_mangle)]
pub extern "C" fn cabi_realloc(
_old_ptr: *mut u8,
_old_len: usize,
_align: usize,
_new_len: usize,
) -> *mut u8 {
panic!("Call to cabi_realloc on native?")
}

View File

@@ -1,73 +1,38 @@
// Last verified for v2169
use crate::{
codec::{var_long::VarLong, var_ulong::VarULong},
serial::PacketWrite,
};
use pumpkin_macros::packet;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::math::{vector2::Vector2, vector3::Vector3};
use super::{
common::EntityLink,
set_actor_data::{EntityMetadata, PropertySyncData},
common::ActorLink,
set_actor_data::{PropertySyncData, SyncedActorDataList},
};
#[derive(PacketWrite)]
#[packet(13)]
pub struct CAddActor {
pub entity_unique_id: VarLong,
pub entity_runtime_id: VarULong,
pub entity_type: String,
pub target_actor_id: VarLong,
pub target_runtime_id: VarULong,
pub actor_type: String,
pub position: Vector3<f32>,
pub velocity: Vector3<f32>,
pub pitch: f32,
pub yaw: f32,
pub head_yaw: f32,
pub body_yaw: f32,
pub attributes: Vec<AttributeValue>,
pub metadata: EntityMetadata,
pub rotation: Vector2<f32>,
pub y_head_rotation: f32,
pub y_body_rotation: f32,
pub attributes_list: Vec<SyncedAttribute>,
pub actor_data: SyncedActorDataList,
pub synced_properties: PropertySyncData,
pub links: Vec<EntityLink>,
}
impl CAddActor {
#[allow(clippy::too_many_arguments)]
#[must_use]
pub const fn new(
entity_unique_id: VarLong,
entity_runtime_id: VarULong,
entity_type: String,
position: Vector3<f32>,
velocity: Vector3<f32>,
pitch: f32,
yaw: f32,
head_yaw: f32,
body_yaw: f32,
attributes: Vec<AttributeValue>,
metadata: EntityMetadata,
synced_properties: PropertySyncData,
links: Vec<EntityLink>,
) -> Self {
Self {
entity_unique_id,
entity_runtime_id,
entity_type,
position,
velocity,
pitch,
yaw,
head_yaw,
body_yaw,
attributes,
metadata,
synced_properties,
links,
}
}
pub actor_links: Vec<ActorLink>,
}
#[derive(PacketWrite)]
pub struct AttributeValue {
pub name: String,
pub min: f32,
pub value: f32,
pub max: f32,
pub struct SyncedAttribute {
pub attribute_name: String,
pub min_value: f32,
pub current_value: f32,
pub max_value: f32,
}

View File

@@ -1,21 +1,23 @@
// TODO: update inventory
use crate::{
bedrock::network_item::ItemStackWrapper,
codec::{var_long::VarLong, var_ulong::VarULong},
serial::PacketWrite,
};
use pumpkin_macros::packet;
use pumpkin_util::math::vector3::Vector3;
use super::set_actor_data::EntityMetadata;
use crate::bedrock::network_item::ItemStackWrapper;
use super::set_actor_data::SyncedActorDataList;
#[derive(PacketWrite)]
#[packet(15)]
pub struct CAddItemActor {
pub entity_unique_id: VarLong,
pub entity_runtime_id: VarULong,
pub target_actor_id: VarLong,
pub target_runtime_id: VarULong,
pub item: ItemStackWrapper,
pub position: Vector3<f32>,
pub velocity: Vector3<f32>,
pub metadata: EntityMetadata,
pub from_fishing: bool,
pub entity_data: SyncedActorDataList,
pub is_from_fishing: bool,
}

View File

@@ -1,110 +1,38 @@
use crate::{
bedrock::network_item::NetworkItemDescriptor,
codec::{var_int::VarInt, var_uint::VarUInt, var_ulong::VarULong},
bedrock::{client::GameType, network_item::NetworkItemStackDescriptor},
codec::var_ulong::VarULong,
serial::PacketWrite,
};
use pumpkin_macros::packet;
use pumpkin_util::math::vector3::Vector3;
use std::io::{Error, Write};
use pumpkin_util::math::{vector2::Vector2, vector3::Vector3};
use uuid::Uuid;
use super::{
common::{AbilityLayer, BuildPlatform, EntityLink},
set_actor_data::EntityMetadata,
common::{ActorLink, BuildPlatform, SerializedAbilitiesData},
set_actor_data::PropertySyncData,
set_actor_data::SyncedActorDataList,
};
#[derive(PacketWrite)]
#[packet(12)]
pub struct CAddPlayer {
pub uuid: Uuid,
pub username: String,
pub entity_runtime_id: VarULong,
pub player_name: String,
pub target_runtime_id: VarULong,
pub platform_chat_id: String,
pub position: Vector3<f32>,
pub velocity: Vector3<f32>,
pub pitch: f32,
pub yaw: f32,
pub head_yaw: f32,
pub held_item: NetworkItemDescriptor,
pub game_mode: VarInt,
pub metadata: EntityMetadata,
pub properties: EntityProperties,
pub ability_data: AbilityData,
pub links: Vec<EntityLink>,
pub rotation: Vector2<f32>,
pub y_head_rotation: f32,
// TODO: update inventory
pub carried_item: NetworkItemStackDescriptor,
pub player_game_type: GameType,
pub entity_data: SyncedActorDataList,
pub synced_properties: PropertySyncData,
pub abilities_data: SerializedAbilitiesData,
pub actor_links: Vec<ActorLink>,
pub device_id: String,
pub build_platform: BuildPlatform,
}
impl CAddPlayer {
#[allow(clippy::too_many_arguments)]
#[must_use]
pub const fn new(
uuid: Uuid,
username: String,
entity_runtime_id: VarULong,
platform_chat_id: String,
position: Vector3<f32>,
velocity: Vector3<f32>,
pitch: f32,
yaw: f32,
head_yaw: f32,
held_item: NetworkItemDescriptor,
game_mode: VarInt,
metadata: EntityMetadata,
properties: EntityProperties,
ability_data: AbilityData,
links: Vec<EntityLink>,
device_id: String,
build_platform: BuildPlatform,
) -> Self {
Self {
uuid,
username,
entity_runtime_id,
platform_chat_id,
position,
velocity,
pitch,
yaw,
head_yaw,
held_item,
game_mode,
metadata,
properties,
ability_data,
links,
device_id,
build_platform,
}
}
}
#[derive(Default, Clone)]
pub struct EntityProperties {
pub ints: Vec<(VarUInt, VarInt)>,
pub floats: Vec<(VarUInt, f32)>,
}
impl PacketWrite for EntityProperties {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
VarUInt(self.ints.len() as u32).write(writer)?;
for (id, val) in &self.ints {
id.write(writer)?;
val.write(writer)?;
}
VarUInt(self.floats.len() as u32).write(writer)?;
for (id, val) in &self.floats {
id.write(writer)?;
val.write(writer)?;
}
Ok(())
}
}
#[derive(Default, Clone, PacketWrite)]
pub struct AbilityData {
pub entity_unique_id: i64,
pub player_permissions: u8,
pub command_permissions: u8,
pub layers: Vec<AbilityLayer>,
}

View File

@@ -1,4 +1,10 @@
use crate::{codec::var_uint::VarUInt, serial::PacketWrite};
// Last verified for v2169
use crate::{
bedrock::{client::CommandPermissionLevel, enum_as_str::EnumAsStr},
codec::var_uint::VarUInt,
serial::PacketWrite,
};
use pumpkin_macros::packet;
#[derive(PacketWrite)]
@@ -6,66 +12,60 @@ use pumpkin_macros::packet;
pub struct CAvailableCommands {
pub enum_values: Vec<String>,
pub chained_subcommand_values: Vec<String>,
pub suffixes: Vec<String>,
pub enums: Vec<CommandEnum>,
pub chained_subcommands: Vec<ChainedSubcommand>,
pub commands: Vec<Command>,
pub soft_enums: Vec<SoftEnum>,
pub constraints: Vec<CommandEnumConstraint>,
pub post_fixes: Vec<String>,
pub enum_data: Vec<EnumData>,
pub chained_subcommand_data: Vec<ChainedSubcommandData>,
pub commands: Vec<CommandData>,
pub soft_enums: Vec<SoftEnumData>,
pub constraints: Vec<ConstrainedValueData>,
}
#[derive(PacketWrite)]
pub struct EnumData {
pub name: String,
pub values: Vec<u32>,
}
// Represents a subcommand that can chain commands, e.g. /execute.
// Written as a flat list in section 3 of the packet; Commands reference
// entries by index via ChainedSubcommandOffsets.
#[derive(PacketWrite)]
pub struct ChainedSubcommand {
pub struct ChainedSubcommandData {
pub name: String,
pub values: Vec<ChainedSubcommandValue>,
pub subcommand_values: Vec<ChainedSubcommandRelationship>,
}
#[derive(PacketWrite)]
pub struct ChainedSubcommandValue {
/// Index into the `ChainedSubcommandValues` flat list — `VarUInt`
pub struct ChainedSubcommandRelationship {
/// Index into the `ChainedSubcommandValues` flat list
pub index: VarUInt,
/// Argument type flags (basic types only, no `ARG_FLAG`_* modifiers) — `VarUInt`
/// Argument type flags (basic types only, no `ARG_FLAG`_* modifiers)
pub value: VarUInt,
}
#[derive(PacketWrite)]
pub struct CommandEnum {
pub name: String,
pub value_indices: Vec<u32>,
}
#[derive(PacketWrite)]
pub struct Command {
pub struct CommandData {
pub name: String,
pub description: String,
/// LE u16 — putLShort
pub flags: u16,
/// Permission string (e.g. "any", "admin")
pub permission: String,
/// LE i32 — putLInt; -1 means no aliases
pub aliases_enum_index: i32,
/// LE u32 each — indices into the `chained_subcommands` flat list
pub chained_subcommand_offsets: Vec<u32>,
pub overloads: Vec<CommandOverload>,
pub permission_level: EnumAsStr<CommandPermissionLevel>,
/// -1 means no aliases
pub alias_enum: i32,
pub command_data_chained_subcommand_indexes: Vec<u32>,
pub overloads: Vec<OverloadData>,
}
#[derive(PacketWrite)]
pub struct CommandOverload {
/// Written as a single byte before parameter count ← MISSING in original
/// true = this overload uses chained subcommands instead of regular params
pub chaining: bool,
pub parameters: Vec<CommandParameter>,
pub struct OverloadData {
pub is_chaining: bool,
pub parameter_data: Vec<ParamData>,
}
#[derive(Clone, PacketWrite)]
pub struct CommandParameter {
pub struct ParamData {
pub name: String,
/// LE u32 — encodes type flags (`ARG_FLAG_VALID` | `ARG_FLAG_ENUM` | index, or raw type)
pub type_info: u32,
pub optional: bool,
/// encodes type flags (`ARG_FLAG_VALID` | `ARG_FLAG_ENUM` | index, or raw type)
pub parse_symbol: u32,
pub is_optional: bool,
/// Options byte (`ARG_FLAG`_* options) — putByte
pub options: u8,
}
@@ -99,24 +99,15 @@ pub mod arg_types {
pub const ARG_TYPE_COMMAND: u32 = 0x46;
}
pub mod command_permissions {
pub const ANY: &str = "any";
pub const GAME_DIRECTORS: &str = "gamedirectors";
pub const ADMIN: &str = "admin";
pub const HOST: &str = "host";
pub const OWNER: &str = "owner";
pub const INTERNAL: &str = "internal";
#[derive(Clone, PacketWrite)]
pub struct SoftEnumData {
pub enum_name: String,
pub enum_options: Vec<String>,
}
#[derive(Clone, PacketWrite)]
pub struct SoftEnum {
pub name: String,
pub values: Vec<String>,
}
#[derive(Clone, PacketWrite)]
pub struct CommandEnumConstraint {
pub affected_value_index: i32,
pub enum_index: i32,
pub constraints: Vec<u8>,
pub struct ConstrainedValueData {
pub enum_value_symbol: u32,
pub enum_symbol: u32,
pub constraint_indices: Vec<u8>,
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos;
@@ -8,14 +10,17 @@ use crate::serial::PacketWrite;
#[derive(PacketWrite)]
#[packet(56)]
pub struct CBlockActorData {
pub position: BlockPos,
pub data: NbtCompound,
pub block_position: BlockPos,
pub actor_data_tags: NbtCompound,
}
impl CBlockActorData {
#[must_use]
pub const fn new(position: BlockPos, data: NbtCompound) -> Self {
Self { position, data }
pub const fn new(block_position: BlockPos, actor_data_tags: NbtCompound) -> Self {
Self {
block_position,
actor_data_tags,
}
}
}
@@ -37,9 +42,12 @@ mod tests {
data.put_byte("color", 11);
let mut encoded = Vec::new();
CBlockActorData::new(BlockPos::new(1, 64, -2), data)
.write(&mut encoded)
.unwrap();
CBlockActorData {
block_position: BlockPos::new(1, 64, -2),
actor_data_tags: data,
}
.write(&mut encoded)
.unwrap();
assert_eq!(&encoded[..4], &[2, 128, 1, 3]);
let mut reader = NbtReadHelperBedrock::new(Cursor::new(&encoded[4..]));

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use pumpkin_util::math::position::BlockPos;
@@ -7,20 +9,9 @@ use crate::{codec::var_int::VarInt, serial::PacketWrite};
#[derive(PacketWrite)]
#[packet(26)]
pub struct CBlockEvent {
pub position: BlockPos,
pub block_position: BlockPos,
pub event_type: VarInt,
pub event_data: VarInt,
}
impl CBlockEvent {
#[must_use]
pub const fn new(position: BlockPos, event_type: i32, event_data: i32) -> Self {
Self {
position,
event_type: VarInt(event_type),
event_data: VarInt(event_data),
}
}
pub event_value: VarInt,
}
#[cfg(test)]
@@ -35,9 +26,13 @@ mod tests {
assert_eq!(<CBlockEvent as Packet>::PACKET_ID, 26);
let mut encoded = Vec::new();
CBlockEvent::new(BlockPos::new(1, 64, -2), 1, 3)
.write(&mut encoded)
.unwrap();
CBlockEvent {
block_position: BlockPos::new(1, 64, -2),
event_type: 1.into(),
event_value: 3.into(),
}
.write(&mut encoded)
.unwrap();
assert_eq!(encoded, [2, 128, 1, 3, 2, 6]);
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use pumpkin_util::math::vector3::Vector3;
@@ -6,20 +8,8 @@ use crate::{codec::var_int::VarInt, serial::PacketWrite};
#[derive(PacketWrite)]
#[packet(61)]
pub struct CChangeDimension {
pub dimension: VarInt,
pub dimension_id: VarInt,
pub position: Vector3<f32>,
pub respawn: bool,
pub loading_screen_id: Option<u32>,
}
impl CChangeDimension {
#[must_use]
pub const fn new(dimension: i32, position: Vector3<f32>, respawn: bool) -> Self {
Self {
dimension: VarInt(dimension),
position,
respawn,
loading_screen_id: None,
}
}
}

View File

@@ -1,10 +1,11 @@
// Last verified for v2169
use pumpkin_macros::packet;
use crate::{codec::var_int::VarInt, serial::PacketWrite};
#[derive(PacketWrite)]
#[packet(70)]
pub struct CChunkRadiusUpdate {
// https://mojang.github.io/bedrock-protocol-docs/html/ChunkRadiusUpdatedPacket.html
pub struct CChunkRadiusUpdated {
pub chunk_radius: VarInt,
}

View File

@@ -1,28 +1,17 @@
use std::io::{Error, Write};
// Last verified for v2169
use pumpkin_macros::packet;
use crate::{codec::var_uint::VarUInt, serial::PacketWrite};
use crate::serial::PacketWrite;
#[derive(Clone, Debug)]
pub struct CacheBlob {
pub hash: u64,
pub payload: Vec<u8>,
#[derive(PacketWrite, Clone, Debug)]
pub struct MissingBlobData {
pub blob_id: u64,
pub blob_data: Vec<u8>,
}
#[derive(PacketWrite)]
#[packet(136)]
pub struct CClientCacheMissResponse<'a> {
pub blobs: &'a [CacheBlob],
}
impl PacketWrite for CClientCacheMissResponse<'_> {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
VarUInt(self.blobs.len() as u32).write(writer)?;
for blob in self.blobs {
writer.write_all(&blob.hash.to_le_bytes())?;
VarUInt(blob.payload.len() as u32).write(writer)?;
writer.write_all(&blob.payload)?;
}
Ok(())
}
pub struct CClientCacheMissResponse {
pub missing_blobs: Vec<MissingBlobData>,
}

View File

@@ -1,6 +1,11 @@
use std::io::{Error, Write};
use crate::{codec::var_long::VarLong, serial::PacketWrite};
use pumpkin_util::GameMode;
use crate::{
codec::{var_int::VarInt, var_long::VarLong},
serial::PacketWrite,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i32)]
@@ -11,7 +16,6 @@ pub enum BuildPlatform {
Osx = 3,
Amazon = 4,
GearVr = 5,
Hololens = 6,
Uwp = 7,
Win32 = 8,
Dedicated = 9,
@@ -29,8 +33,92 @@ impl PacketWrite for BuildPlatform {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum GameType {
Unknown = -1,
Survival = 0,
Creative = 1,
Adventure = 2,
Default = 5,
Spectator = 6,
//WorldDefault = 0,
}
impl PacketWrite for GameType {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
VarInt(*self as i32).write(writer)
}
}
impl From<GameMode> for GameType {
fn from(value: GameMode) -> Self {
match value {
GameMode::Survival => Self::Survival,
GameMode::Creative => Self::Creative,
GameMode::Adventure => Self::Adventure,
GameMode::Spectator => Self::Spectator,
}
}
}
#[derive(Clone, PacketWrite)]
pub struct SerializedAbilitiesData {
pub target_player_raw_id: i64,
pub player_permissions: PlayerPermissionLevel,
pub command_permissions: CommandPermissionLevel,
pub layers: Vec<SerializedAbilitiesDataSerializedLayer>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i8)]
pub enum PlayerPermissionLevel {
Visitor = 0,
Member = 1,
Operator = 2,
Custom = 3,
}
impl PacketWrite for PlayerPermissionLevel {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
(*self as i8).write(writer)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum CommandPermissionLevel {
Any = 0,
GameDirectors = 1,
Admin = 2,
Host = 3,
Owner = 4,
Internal = 5,
}
#[allow(clippy::to_string_trait_impl)]
impl ToString for CommandPermissionLevel {
fn to_string(&self) -> String {
match self {
Self::Any => "any",
Self::GameDirectors => "gamedirectors",
Self::Admin => "admin",
Self::Host => "host",
Self::Owner => "owner",
Self::Internal => "internal",
}
.into()
}
}
impl PacketWrite for CommandPermissionLevel {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
(*self as u8).write(writer)
}
}
#[derive(Default, Clone, PacketWrite)]
pub struct AbilityLayer {
pub struct SerializedAbilitiesDataSerializedLayer {
pub serialized_layer: u16,
pub abilities_set: u32,
pub ability_value: u32,
@@ -40,7 +128,7 @@ pub struct AbilityLayer {
}
#[derive(Default, Clone, PacketWrite)]
pub struct EntityLink {
pub struct ActorLink {
pub ridden_unique_id: VarLong,
pub rider_unique_id: VarLong,
pub link_type: u8,

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use pumpkin_util::math::position::BlockPos;
@@ -6,7 +8,6 @@ use crate::{codec::var_long::VarLong, serial::PacketWrite};
#[derive(PacketWrite)]
#[packet(46)]
pub struct CContainerOpen {
// https://mojang.github.io/bedrock-protocol-docs/html/ContainerOpenPacket.html
pub container_id: u8,
pub container_type: u8,
pub position: BlockPos,

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use pumpkin_util::math::{vector2::Vector2, vector3::Vector3};
@@ -5,8 +7,7 @@ use crate::{codec::var_ulong::VarULong, serial::PacketWrite};
#[derive(PacketWrite)]
#[packet(161)]
pub struct CCorrectPlayerMove {
// https://mojang.github.io/bedrock-protocol-docs/html/CorrectPlayerMovePredictionPacket.html
pub struct CCorrectPlayerMovePrediction {
pub prediction_type: u8,
pub pos: Vector3<f32>,
pub pos_delta: Vector3<f32>,

View File

@@ -8,9 +8,8 @@ use crate::{
#[packet(145)]
pub struct CCreativeContent<'a> {
// https://mojang.github.io/bedrock-protocol-docs/html/CreativeContentPacket.html
pub groups: &'a [Group],
pub entries: &'a [Entry],
pub groups: &'a [CreativeGroupInfoPayload],
pub entries: &'a [CreativeItemEntryPayload],
}
impl PacketWrite for CCreativeContent<'_> {
@@ -28,44 +27,57 @@ impl PacketWrite for CCreativeContent<'_> {
}
}
#[derive(Copy, Clone)]
#[repr(i32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum CreativeCategory {
Construction = 1,
Nature = 2,
Equipment = 3,
Items = 4,
CommandOnly = 5,
Undefined = 6,
All,
Construction,
Nature,
Equipment,
Items,
ItemCommandOnly,
Undefined,
}
impl PacketWrite for CreativeCategory {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
(*self as u8).write(writer)
match self {
Self::Construction
| Self::Nature
| Self::Equipment
| Self::Items
| Self::ItemCommandOnly => (*self as u8).write(writer),
_ => Err(Error::other("Invalid CreativeCategory to send")),
}
}
}
pub struct Group {
pub struct CreativeGroupInfoPayload {
pub creative_category: CreativeCategory,
pub name: String,
pub icon_item: NetworkItemDescriptor,
// TODO: update inventory
pub group_icon_item: NetworkItemDescriptor,
}
impl PacketWrite for Group {
impl PacketWrite for CreativeGroupInfoPayload {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
self.creative_category.write(writer)?;
self.name.write(writer)?;
self.icon_item.write_item_instance(writer)
self.group_icon_item.write_item_instance(writer)
}
}
pub struct Entry {
pub struct CreativeItemEntryPayload {
pub id: VarUInt,
// TODO: update inventory
pub item: NetworkItemDescriptor,
pub group_index: VarUInt,
}
impl PacketWrite for Entry {
impl PacketWrite for CreativeItemEntryPayload {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
self.id.write(writer)?;
self.item.write_item_instance(writer)?;

View File

@@ -1,18 +1,19 @@
// Last verified for v2169
use pumpkin_macros::packet;
use std::io::{Error, Write};
use crate::{codec::var_int::VarInt, serial::PacketWrite};
#[packet(5)]
pub struct CDisconnectPlayer {
// https://mojang.github.io/bedrock-protocol-docs/html/DisconnectPacket.html
pub struct CDisconnect {
pub reason: VarInt,
pub skip_message: bool,
pub message: String,
pub filtered_message: String,
}
impl CDisconnectPlayer {
impl CDisconnect {
#[must_use]
pub const fn new(reason: i32, message: String) -> Self {
Self {
@@ -24,7 +25,7 @@ impl CDisconnectPlayer {
}
}
impl PacketWrite for CDisconnectPlayer {
impl PacketWrite for CDisconnect {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
self.reason.write(writer)?;
self.skip_message.write(writer)?;

View File

@@ -2,14 +2,26 @@ use pumpkin_macros::packet;
use crate::{codec::var_uint::VarUInt, serial::PacketWrite};
#[derive(PacketWrite, Default)]
#[packet(0x48)]
#[derive(PacketWrite)]
#[packet(72)]
pub struct CGamerulesChanged {
pub rule_data: GameRules,
pub rule_data: Vec<GameRule>,
}
#[derive(PacketWrite, Default)]
pub struct GameRules {
// TODO https://mojang.github.io/bedrock-protocol-docs/html/GameRulesChangedPacketData.html
pub list_size: VarUInt,
#[derive(PacketWrite)]
pub struct GameRule {
pub rule_name: String,
pub rule_can_be_modified: bool,
pub rule_value: RuleValue,
}
// TODO: flesh out RuleValue
pub enum RuleValue {
Null,
}
impl PacketWrite for RuleValue {
fn write<W: std::io::prelude::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
VarUInt(0).write(writer)
}
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use crate::{
@@ -9,7 +11,6 @@ use crate::{
#[derive(PacketWrite)]
#[packet(49)]
pub struct CInventoryContent {
// https://mojang.github.io/bedrock-protocol-docs/docs/InventoryContentPacket.html
pub container_id: VarUInt,
pub slots: Vec<NetworkItemStackDescriptor>,
pub full_container_name: FullContainerName,

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use crate::{
bedrock::network_item::{FullContainerName, NetworkItemStackDescriptor},
codec::var_uint::VarUInt,
@@ -8,9 +10,9 @@ use pumpkin_macros::packet;
#[derive(PacketWrite)]
#[packet(50)]
pub struct CInventorySlot {
pub window_id: VarUInt,
pub inventory_slot: VarUInt,
pub container_name: Option<FullContainerName>,
pub storage: Option<NetworkItemStackDescriptor>,
pub container_id: VarUInt,
pub slot: VarUInt,
pub full_container_name: Option<FullContainerName>,
pub storage_item: Option<NetworkItemStackDescriptor>,
pub item: NetworkItemStackDescriptor,
}

View File

@@ -6,14 +6,16 @@ use crate::{codec::var_int::VarInt, serial::PacketWrite};
#[packet(162)]
pub struct CItemRegistry {
// https://mojang.github.io/bedrock-protocol-docs/docs/ItemRegistryPacket.html
pub items: Vec<ItemDefinition>,
pub items: Vec<ItemData>,
}
#[derive(PacketWrite)]
pub struct ItemDefinition {
pub name: String,
pub id: i16,
pub component_based: bool,
pub struct ItemData {
pub item_name: String,
pub item_id: i16,
pub is_component_based: bool,
// TODO: ItemVersion enum
pub item_version: VarInt,
// Normally would be `Nbt`, but for simplicity elsewhere, this is preserialized (via `Nbt::write_bedrock`)

View File

@@ -9,10 +9,10 @@ use pumpkin_macros::packet;
#[derive(Debug, Clone)]
pub struct ItemStackResponseSlotInfo {
pub requested_slot: u8,
pub slot: u8,
pub hotbar_slot: u8,
pub count: u8,
pub item_stack_id: VarInt,
pub amount: u8,
pub item_stack_net_id: VarInt,
pub custom_name: String,
pub filtered_custom_name: String,
pub durability_correction: VarInt,
@@ -26,13 +26,13 @@ impl PacketWrite for ItemStackResponseSlotInfo {
"durability correction must fit in an i16",
));
}
self.requested_slot.write(writer)?;
self.slot.write(writer)?;
self.hotbar_slot.write(writer)?;
self.count.write(writer)?;
self.amount.write(writer)?;
true.write(writer)?;
(self.item_stack_id.0 > 0).write(writer)?;
if self.item_stack_id.0 > 0 {
self.item_stack_id.write(writer)?;
(self.item_stack_net_id.0 > 0).write(writer)?;
if self.item_stack_net_id.0 > 0 {
self.item_stack_net_id.write(writer)?;
}
self.custom_name.write(writer)?;
self.filtered_custom_name.write(writer)?;
@@ -42,26 +42,27 @@ impl PacketWrite for ItemStackResponseSlotInfo {
#[derive(PacketWrite, Debug, Clone)]
pub struct ItemStackResponseContainerInfo {
pub container_name: FullContainerName,
pub full_container_name: FullContainerName,
pub slots: Vec<ItemStackResponseSlotInfo>,
}
#[derive(Debug, Clone)]
pub struct ItemStackResponse {
pub struct ItemStackResponseInfo {
// TODO: proper enum
pub result: u8, // 0 = SUCCESS, 1 = ERROR
pub request_id: VarInt,
pub container_infos: Vec<ItemStackResponseContainerInfo>,
pub client_request_id: VarInt,
pub containers: Vec<ItemStackResponseContainerInfo>,
}
impl PacketWrite for ItemStackResponse {
impl PacketWrite for ItemStackResponseInfo {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
self.result.write(writer)?;
self.request_id.write(writer)?;
self.client_request_id.write(writer)?;
true.write(writer)?;
(!self.container_infos.is_empty()).write(writer)?;
if !self.container_infos.is_empty() {
VarUInt(self.container_infos.len() as u32).write(writer)?;
for info in &self.container_infos {
(!self.containers.is_empty()).write(writer)?;
if !self.containers.is_empty() {
VarUInt(self.containers.len() as u32).write(writer)?;
for info in &self.containers {
info.write(writer)?;
}
}
@@ -72,7 +73,7 @@ impl PacketWrite for ItemStackResponse {
#[derive(Debug, Clone)]
#[packet(148)]
pub struct CItemStackResponse {
pub responses: Vec<ItemStackResponse>,
pub responses: Vec<ItemStackResponseInfo>,
}
impl PacketWrite for CItemStackResponse {
@@ -98,10 +99,10 @@ mod tests {
#[test]
fn rejects_out_of_range_durability_correction() {
let slot = ItemStackResponseSlotInfo {
requested_slot: 0,
slot: 0,
hotbar_slot: 0,
count: 1,
item_stack_id: VarInt(1),
amount: 1,
item_stack_net_id: VarInt(1),
custom_name: String::new(),
filtered_custom_name: String::new(),
durability_correction: VarInt(32768),

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use pumpkin_util::math::vector3::Vector3;

View File

@@ -1,20 +1,18 @@
// Last verified for v2169
use pumpkin_macros::packet;
use pumpkin_util::math::vector3::Vector3;
use crate::{codec::var_int::VarInt, serial::PacketWrite};
/// Sent by the server to spawn a visual particle effect at a specific 3D location in the world.
///
/// Packet ID: `123`
/// Ref: <https://mojang.github.io/bedrock-protocol-docs/html/LevelSoundEventPacket.html>
#[derive(PacketWrite)]
#[packet(123)]
pub struct CLevelSoundEvent {
pub sound_id: String,
pub sound_event: String,
pub position: Vector3<f32>,
pub extra_data: VarInt,
pub entity_type: String,
pub is_baby_mob: bool,
pub data: VarInt,
pub actor_identifier: String,
pub is_baby: bool,
pub is_global: bool,
pub actor_unique_id: i64,
pub fire_at_position: Option<Vector3<f32>>,
@@ -29,11 +27,11 @@ mod tests {
assert_eq!(<CLevelSoundEvent as crate::Packet>::PACKET_ID, 123);
let packet = CLevelSoundEvent {
sound_id: "test".into(),
sound_event: "test".into(),
position: Vector3::new(1.0, 2.0, 3.0),
extra_data: VarInt(-1),
entity_type: "actor".into(),
is_baby_mob: true,
data: VarInt(-1),
actor_identifier: "actor".into(),
is_baby: true,
is_global: false,
actor_unique_id: 42,
fire_at_position: Some(Vector3::new(4.0, 5.0, 6.0)),

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use crate::{
@@ -8,12 +10,15 @@ use crate::{
#[derive(PacketWrite)]
#[packet(28)]
pub struct CMobEffect {
pub runtime_entity_id: VarULong,
pub target_runtime_id: VarULong,
// TODO: Event enum
pub event_id: u8,
pub effect_id: VarInt,
pub amplifier: VarInt,
pub particles: bool,
pub duration: VarInt,
pub effect_amplifier: VarInt,
pub show_particles: bool,
pub effect_duration_ticks: VarInt,
pub tick: VarULong,
pub ambient: bool,
}
@@ -22,28 +27,4 @@ impl CMobEffect {
pub const EVENT_ADD: u8 = 1;
pub const EVENT_MODIFY: u8 = 2;
pub const EVENT_REMOVE: u8 = 3;
#[expect(clippy::too_many_arguments)]
#[must_use]
pub const fn new(
runtime_entity_id: VarULong,
event_id: u8,
effect_id: VarInt,
amplifier: VarInt,
particles: bool,
duration: VarInt,
tick: VarULong,
ambient: bool,
) -> Self {
Self {
runtime_entity_id,
event_id,
effect_id,
amplifier,
particles,
duration,
tick,
ambient,
}
}
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use crate::{
bedrock::network_item::NetworkItemStackDescriptor, codec::var_ulong::VarULong,
serial::PacketWrite,
@@ -7,39 +9,9 @@ use pumpkin_macros::packet;
#[derive(PacketWrite, Debug)]
#[packet(31)]
pub struct CMobEquipment {
pub entity_runtime_id: VarULong,
pub target_runtime_id: VarULong,
pub item: NetworkItemStackDescriptor,
pub inventory_slot: u8,
pub hotbar_slot: u8,
pub window_id: u8,
}
impl CMobEquipment {
#[must_use]
pub const fn new(
entity_runtime_id: u64,
item: NetworkItemStackDescriptor,
inventory_slot: u8,
hotbar_slot: u8,
window_id: u8,
) -> Self {
Self {
entity_runtime_id: VarULong(entity_runtime_id),
item,
inventory_slot,
hotbar_slot,
window_id,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packet::Packet;
#[test]
fn mob_equipment_packet_id() {
assert_eq!(CMobEquipment::PACKET_ID, 31);
}
pub slot: u8,
pub selected_slot: u8,
pub container_id: u8,
}

View File

@@ -7,14 +7,14 @@ pub mod block_actor_data;
pub mod block_event;
pub mod boss_event;
pub mod change_dimension;
pub mod chunk_radius_update;
pub mod chunk_radius_updated;
pub mod client_cache_miss_response;
pub mod common;
pub mod container_open;
pub mod correct_player_move;
pub mod correct_player_move_prediction;
pub mod crafting_data;
pub mod creative_content;
pub mod disconnect_player;
pub mod disconnect;
pub mod gamerules_changed;
pub mod inventory_content;
pub mod inventory_slot;
@@ -35,16 +35,17 @@ pub mod play_status;
pub mod player_hotbar;
pub mod player_list;
pub mod remove_actor;
pub mod remove_objective;
pub mod resource_pack_stack;
pub mod resource_packs_info;
pub mod respawn;
pub mod scoreboard;
pub mod set_actor_data;
pub mod set_actor_link;
pub mod set_actor_motion;
pub mod set_difficulty;
pub mod set_display_objective;
pub mod set_health;
pub mod set_player_gamemode;
pub mod set_score;
pub mod set_spawn_position;
pub mod set_time;
pub mod set_title;
@@ -66,14 +67,14 @@ pub use block_actor_data::*;
pub use block_event::*;
pub use boss_event::*;
pub use change_dimension::*;
pub use chunk_radius_update::*;
pub use chunk_radius_updated::*;
pub use client_cache_miss_response::*;
pub use common::*;
pub use container_open::*;
pub use correct_player_move::*;
pub use correct_player_move_prediction::*;
pub use crafting_data::*;
pub use creative_content::*;
pub use disconnect_player::*;
pub use disconnect::*;
pub use gamerules_changed::*;
pub use inventory_content::*;
pub use inventory_slot::*;
@@ -94,16 +95,17 @@ pub use play_status::*;
pub use player_hotbar::*;
pub use player_list::*;
pub use remove_actor::*;
pub use remove_objective::*;
pub use resource_pack_stack::*;
pub use resource_packs_info::*;
pub use respawn::*;
pub use scoreboard::*;
pub use set_actor_data::*;
pub use set_actor_link::*;
pub use set_actor_motion::*;
pub use set_difficulty::*;
pub use set_display_objective::*;
pub use set_health::*;
pub use set_player_gamemode::*;
pub use set_score::*;
pub use set_spawn_position::*;
pub use set_time::*;
pub use set_title::*;

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use crate::{codec::var_uint::VarUInt, serial::PacketWrite};
use pumpkin_macros::packet;
@@ -5,5 +7,5 @@ use pumpkin_macros::packet;
#[derive(PacketWrite)]
pub struct CModalFormRequest {
pub form_id: VarUInt,
pub form_data: String,
pub form_ui_json: String,
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use crate::{codec::var_ulong::VarULong, serial::PacketWrite};
use pumpkin_macros::packet;
use pumpkin_util::math::vector3::Vector3;
@@ -5,35 +7,16 @@ use pumpkin_util::math::vector3::Vector3;
#[derive(PacketWrite)]
#[packet(18)]
pub struct CMoveActorAbsolute {
pub entity_runtime_id: VarULong,
pub flags: u8,
pub actor_runtime_id: VarULong,
pub header: u8,
pub position: Vector3<f32>,
pub pitch: u8,
pub yaw: u8,
pub head_yaw: u8,
pub rotation_x: u8,
pub rotation_y: u8,
pub rotation_y_head: u8,
}
impl CMoveActorAbsolute {
pub const FLAG_ON_GROUND: u8 = 0x01;
pub const FLAG_TELEPORT: u8 = 0x02;
pub const FLAG_FORCE_MOVE: u8 = 0x04;
#[must_use]
pub const fn new(
entity_runtime_id: VarULong,
flags: u8,
position: Vector3<f32>,
pitch: u8,
yaw: u8,
head_yaw: u8,
) -> Self {
Self {
entity_runtime_id,
flags,
position,
pitch,
yaw,
head_yaw,
}
}
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use crate::serial::PacketWrite;
@@ -6,28 +8,12 @@ use crate::serial::PacketWrite;
#[packet(143)]
pub struct CNetworkSettings {
pub compression_threshold: u16,
// TODO: CompressionAlgorithm enum
/// `ZLib` = 0, Snappy = 1, None = 255
pub compression_method: u16,
pub compression_algorithm: u16,
pub client_throttle_enabled: bool,
pub client_throttle_threshold: u8,
pub client_throttle_scalar: f32,
}
impl CNetworkSettings {
#[must_use]
pub const fn new(
compression_threshold: u16,
compression_method: u16,
client_throttle_enabled: bool,
client_throttle_threshold: u8,
client_throttle_scalar: f32,
) -> Self {
Self {
compression_threshold,
compression_method,
client_throttle_enabled,
client_throttle_threshold,
client_throttle_scalar,
}
}
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use std::io::{Error, Write};
use pumpkin_macros::packet;
@@ -5,6 +7,7 @@ use pumpkin_macros::packet;
use crate::serial::PacketWrite;
#[derive(Clone, Copy)]
#[repr(i32)]
#[packet(2)]
pub enum CPlayStatus {
LoginSuccess = 0,

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use crate::{codec::var_uint::VarUInt, serial::PacketWrite};
@@ -7,5 +9,5 @@ use crate::{codec::var_uint::VarUInt, serial::PacketWrite};
pub struct CPlayerHotbar {
pub selected_slot: VarUInt,
pub container_id: u8,
pub should_select_block: bool,
pub should_select_slot: bool,
}

View File

@@ -1,15 +1,17 @@
// Last verified for v2169
use crate::{codec::var_long::VarLong, serial::PacketWrite};
use pumpkin_macros::packet;
#[derive(PacketWrite)]
#[packet(14)]
pub struct CRemoveActor {
pub entity_unique_id: VarLong,
pub target_actor_id: VarLong,
}
impl CRemoveActor {
#[must_use]
pub const fn new(entity_unique_id: VarLong) -> Self {
Self { entity_unique_id }
pub const fn new(target_actor_id: VarLong) -> Self {
Self { target_actor_id }
}
}

View File

@@ -0,0 +1,10 @@
// Last verified for v2169
use crate::serial::PacketWrite;
use pumpkin_macros::packet;
#[derive(PacketWrite)]
#[packet(106)]
pub struct CRemoveObjective {
pub objective_name: String,
}

View File

@@ -1,9 +1,11 @@
// Last verified for v2169
use crate::{bedrock::client::start_game::Experiments, serial::PacketWrite};
use pumpkin_macros::packet;
#[derive(PacketWrite)]
pub struct ResourcePackStackEntry {
pub uuid: String,
pub struct PackInstanceId {
pub pack_id: String,
pub version: String,
pub sub_pack_name: String,
}
@@ -11,28 +13,9 @@ pub struct ResourcePackStackEntry {
#[derive(PacketWrite)]
#[packet(7)]
pub struct CResourcePackStackPacket {
pub resource_pack_required: bool,
pub resource_packs: Vec<ResourcePackStackEntry>,
pub game_version: String,
pub texture_pack_required: bool,
pub texture_pack_list: Vec<PackInstanceId>,
pub base_game_version: String,
pub experiments: Experiments,
pub include_editor_packs: bool,
}
impl CResourcePackStackPacket {
#[must_use]
pub const fn new(
resource_pack_required: bool,
resource_packs: Vec<ResourcePackStackEntry>,
game_version: String,
experiments: Experiments,
include_editor_packs: bool,
) -> Self {
Self {
resource_pack_required,
resource_packs,
game_version,
experiments,
include_editor_packs,
}
}
}

View File

@@ -1,49 +1,44 @@
// Last verified for v2169
use crate::serial::PacketWrite;
use pumpkin_macros::packet;
use std::io::{Error, Write};
#[derive(PacketWrite)]
pub struct ResourcePackEntry {
pub uuid: uuid::Uuid,
pub version: String,
pub size: u64,
pub struct PackInfoData {
pub pack_id_version: PackIdVersion,
pub pack_size: u64,
pub content_key: String,
pub sub_pack_name: String,
pub content_id: String,
pub subpack_name: String,
pub content_identity: String,
pub has_scripts: bool,
pub addon_pack: bool,
pub rtx_enabled: bool,
pub download_url: String,
pub is_addon_pack: bool,
pub is_ray_tracing_capable: bool,
pub cdn_url: String,
}
#[derive(PacketWrite)]
#[packet(6)]
pub struct CResourcePacksInfo {
pub resource_pack_required: bool,
pub has_addon_packs: bool,
pub has_scripts: bool,
pub is_vibrant_visuals_force_disabled: bool,
pub world_template_id: uuid::Uuid,
pub world_template_version: String,
pub resource_packs: Vec<ResourcePackEntry>,
pub force_disable_vibrant_visuals: bool,
pub world_template_id_and_version: PackIdVersion,
pub resource_packs: Vec<PackInfoData>,
}
impl PacketWrite for CResourcePacksInfo {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
self.resource_pack_required.write(writer)?;
self.has_addon_packs.write(writer)?;
self.has_scripts.write(writer)?;
self.is_vibrant_visuals_force_disabled.write(writer)?;
#[derive(PacketWrite)]
pub struct PackIdVersion {
pub pack_uuid: uuid::Uuid,
pub pack_version: String,
}
self.world_template_id.write(writer)?;
self.world_template_version.write(writer)?;
crate::codec::var_uint::VarUInt(self.resource_packs.len() as u32).write(writer)?;
for entry in &self.resource_packs {
entry.write(writer)?;
impl PackIdVersion {
#[must_use]
pub const fn new(pack_uuid: uuid::Uuid, pack_version: String) -> Self {
Self {
pack_uuid,
pack_version,
}
Ok(())
}
}

View File

@@ -1,49 +0,0 @@
use pumpkin_macros::packet;
use pumpkin_util::math::vector3::Vector3;
use crate::{bedrock::respawn::RespawnState, codec::var_ulong::VarULong, serial::PacketWrite};
#[derive(PacketWrite)]
#[packet(45)]
pub struct CRespawn {
pub position: Vector3<f32>,
pub state: RespawnState,
pub player_runtime_id: VarULong,
}
impl CRespawn {
#[must_use]
pub const fn new(
position: Vector3<f32>,
state: RespawnState,
player_runtime_id: VarULong,
) -> Self {
Self {
position,
state,
player_runtime_id,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{bedrock::server::respawn::SRespawn, serial::PacketRead};
#[test]
fn respawn_packet_roundtrip() {
let packet = CRespawn::new(
Vector3::new(1.5, 64.0, -2.25),
RespawnState::ReadyToSpawn,
VarULong(42),
);
let mut encoded = Vec::new();
packet.write(&mut encoded).unwrap();
let decoded = SRespawn::read(&mut encoded.as_slice()).unwrap();
assert_eq!(decoded.position, packet.position);
assert_eq!(decoded.state, packet.state);
assert_eq!(decoded.player_runtime_id.0, packet.player_runtime_id.0);
}
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use std::{collections::HashMap, io::Write};
use crate::{
@@ -9,34 +11,34 @@ use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use std::io::Error;
#[derive(PacketWrite)]
#[packet(39)] // ProtocolInfo::SET_ACTOR_DATA_PACKET is 39
#[packet(39)]
pub struct CSetActorData {
/// The unique runtime ID of the entity being updated
pub actor_runtime_id: VarULong,
pub target_runtime_id: VarULong,
/// A map of entity metadata properties (e.g., flags, name tags, scale)
pub metadata: EntityMetadata,
pub actor_data: SyncedActorDataList,
/// Dynamic properties synced between client and server
pub synced_properties: PropertySyncData,
/// The server tick at which this update occurred
pub tick: VarULong,
}
pub struct EntityMetadata(pub HashMap<u32, MetadataValue>);
pub struct SyncedActorDataList(pub HashMap<u32, MetadataValue>);
impl Default for EntityMetadata {
impl Default for SyncedActorDataList {
fn default() -> Self {
Self::new()
}
}
impl EntityMetadata {
impl SyncedActorDataList {
#[must_use]
pub fn new() -> Self {
Self(HashMap::new())
}
}
impl EntityMetadata {
impl SyncedActorDataList {
pub fn set(&mut self, key: u32, value: MetadataValue) {
self.0.insert(key, value);
}
@@ -55,7 +57,7 @@ impl EntityMetadata {
self.0.insert(key, MetadataValue::Byte(new_value));
} else {
let current_value = match self.0.get(&key) {
Some(MetadataValue::Long(v)) => *v,
Some(MetadataValue::Int64(v)) => *v,
_ => 0,
};
let new_value = if value {
@@ -63,12 +65,12 @@ impl EntityMetadata {
} else {
current_value & !(1i64 << index)
};
self.0.insert(key, MetadataValue::Long(new_value));
self.0.insert(key, MetadataValue::Int64(new_value));
}
}
}
impl PacketWrite for EntityMetadata {
impl PacketWrite for SyncedActorDataList {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
VarUInt(self.0.len() as u32).write(writer)?;
@@ -90,8 +92,8 @@ pub enum MetadataValue {
Float(f32),
String(String),
CompoundTag,
BlockPos(BlockPos),
Long(i64),
ItemPos(BlockPos),
Int64(i64),
Vec3(Vector3<f32>),
}
@@ -105,8 +107,8 @@ impl MetadataValue {
Self::Float(_) => 3,
Self::String(_) => 4,
Self::CompoundTag => 5,
Self::BlockPos(_) => 6,
Self::Long(_) => 7,
Self::ItemPos(_) => 6,
Self::Int64(_) => 7,
Self::Vec3(_) => 8,
}
}
@@ -116,39 +118,34 @@ impl MetadataValue {
Self::Byte(v) => v.write(writer),
Self::Short(v) => v.write(writer),
Self::Int(v) => VarInt(*v).write(writer),
Self::Float(v) => writer.write_all(&v.to_le_bytes()),
Self::Float(v) => v.write(writer),
Self::String(v) => v.write(writer),
Self::CompoundTag => Err(Error::other("CompoundTag not implemented")),
Self::BlockPos(v) => v.write(writer),
Self::Long(v) => VarLong(*v).write(writer),
Self::Vec3(v) => {
writer.write_all(&v.x.to_le_bytes())?;
writer.write_all(&v.y.to_le_bytes())?;
writer.write_all(&v.z.to_le_bytes())
}
Self::ItemPos(v) => v.write(writer),
Self::Int64(v) => VarLong(*v).write(writer),
Self::Vec3(v) => v.write(writer),
}
}
}
#[derive(Default)]
pub struct PropertySyncData {
pub int_properties: std::collections::HashMap<u32, i32>,
pub float_properties: std::collections::HashMap<u32, f32>,
pub int_entries_list: std::collections::HashMap<u32, i32>,
pub float_entries_list: std::collections::HashMap<u32, f32>,
}
impl PacketWrite for PropertySyncData {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
// Int Properties
VarUInt(self.int_properties.len() as u32).write(writer)?;
for (key, value) in &self.int_properties {
VarUInt(self.int_entries_list.len() as u32).write(writer)?;
for (key, value) in &self.int_entries_list {
VarUInt(*key).write(writer)?;
VarInt(*value).write(writer)?; // Signed VarInt
VarInt(*value).write(writer)?;
}
// Float Properties
VarUInt(self.float_properties.len() as u32).write(writer)?;
for (key, value) in &self.float_properties {
VarUInt(self.float_entries_list.len() as u32).write(writer)?;
for (key, value) in &self.float_entries_list {
VarUInt(*key).write(writer)?;
writer.write_all(&value.to_le_bytes())?; // LE Float
value.write(writer)?;
}
Ok(())
}
@@ -431,11 +428,11 @@ pub mod entity_data_flag {
#[cfg(test)]
mod tests {
use super::{EntityMetadata, entity_data_key};
use super::{SyncedActorDataList, entity_data_key};
#[test]
fn partial_metadata_does_not_reset_flags() {
let metadata = EntityMetadata::new();
let metadata = SyncedActorDataList::new();
assert!(!metadata.0.contains_key(&entity_data_key::FLAGS));
assert!(!metadata.0.contains_key(&entity_data_key::FLAGS_TWO));

View File

@@ -1,13 +1,11 @@
// Last verified for v2169
use pumpkin_macros::packet;
use crate::{bedrock::client::common::EntityLink, serial::PacketWrite};
use crate::{bedrock::client::common::ActorLink, serial::PacketWrite};
/// Sent by the server to set the entity an actor is riding or to unmount an actor.
///
/// Packet ID: `41`
/// Ref: <https://mojang.github.io/bedrock-protocol-docs/html/SetActorLinkPacket.html>
#[derive(PacketWrite)]
#[packet(41)]
pub struct CSetActorLink {
pub link: EntityLink,
pub link: ActorLink,
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use pumpkin_util::math::vector3::Vector3;
@@ -10,14 +12,3 @@ pub struct CSetActorMotion {
pub motion: Vector3<f32>,
pub tick: VarULong,
}
impl CSetActorMotion {
#[must_use]
pub const fn new(target_runtime_id: VarULong, motion: Vector3<f32>, tick: VarULong) -> Self {
Self {
target_runtime_id,
motion,
tick,
}
}
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use crate::{codec::var_uint::VarUInt, serial::PacketWrite};
@@ -7,12 +9,3 @@ use crate::{codec::var_uint::VarUInt, serial::PacketWrite};
pub struct CSetDifficulty {
pub difficulty: VarUInt,
}
impl CSetDifficulty {
#[must_use]
pub const fn new(difficulty: u32) -> Self {
Self {
difficulty: VarUInt(difficulty),
}
}
}

View File

@@ -0,0 +1,14 @@
// Last verified for v2169
use crate::{codec::var_int::VarInt, serial::PacketWrite};
use pumpkin_macros::packet;
#[derive(PacketWrite)]
#[packet(107)]
pub struct CSetDisplayObjective {
pub display_slot_name: String,
pub objective_name: String,
pub objective_display_name: String,
pub criteria_name: String,
pub sort_order: VarInt,
}

View File

@@ -1,18 +1,10 @@
// Last verified for v2169
use crate::{codec::var_int::VarInt, serial::PacketWrite};
use pumpkin_macros::packet;
#[derive(PacketWrite)]
#[packet(42)]
pub struct CSetHealth {
// https://mojang.github.io/bedrock-protocol-docs/html/SetHealthPacket.html
pub health: VarInt,
}
impl CSetHealth {
#[must_use]
pub const fn new(health: i32) -> Self {
Self {
health: VarInt(health),
}
}
}

View File

@@ -1,9 +1,10 @@
use crate::serial::PacketWrite;
// Last verified for v2169
use crate::{bedrock::client::GameType, serial::PacketWrite};
use pumpkin_macros::packet;
use pumpkin_util::GameMode;
#[derive(PacketWrite)]
#[packet(62)]
pub struct CSetPlayerGamemode {
pub gamemode: GameMode,
pub struct CSetPlayerGameType {
pub player_game_type: GameType,
}

View File

@@ -6,16 +6,6 @@ use crate::{
};
use pumpkin_macros::packet;
#[derive(PacketWrite)]
#[packet(107)]
pub struct CSetDisplayObjective {
pub display_slot: String,
pub objective_name: String,
pub display_name: String,
pub criteria_name: String,
pub sort_order: VarInt,
}
#[packet(108)]
pub struct CSetScore {
pub action: VarInt, // 0 = change, 1 = remove
@@ -92,17 +82,3 @@ impl ScoreEntry {
Ok(())
}
}
#[derive(PacketWrite)]
#[packet(106)]
pub struct CRemoveObjective {
pub objective_name: String,
}
impl CRemoveObjective {
pub fn new(objective_name: impl Into<String>) -> Self {
Self {
objective_name: objective_name.into(),
}
}
}

View File

@@ -1,3 +1,7 @@
// Last verified for v2169
use std::io::{Error, Write};
use pumpkin_macros::packet;
use pumpkin_util::math::position::BlockPos;
@@ -6,25 +10,21 @@ use crate::{codec::var_int::VarInt, serial::PacketWrite};
#[derive(Clone, Copy, PacketWrite)]
#[packet(43)]
pub struct CSetSpawnPosition {
pub spawn_type: VarInt,
pub position: BlockPos,
pub dimension: VarInt,
pub spawn_position: BlockPos,
pub spawn_position_type: SpawnPositionType,
pub block_position: BlockPos,
pub dimension_type: VarInt,
pub spawn_block_pos: BlockPos,
}
impl CSetSpawnPosition {
#[must_use]
pub const fn new(
spawn_type: i32,
position: BlockPos,
dimension: i32,
spawn_position: BlockPos,
) -> Self {
Self {
spawn_type: VarInt(spawn_type),
position,
dimension: VarInt(dimension),
spawn_position,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum SpawnPositionType {
PlayerRespawn,
WorldRespawn,
}
impl PacketWrite for SpawnPositionType {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
VarInt(*self as i32).write(writer)
}
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use crate::{codec::var_int::VarInt, serial::PacketWrite};

View File

@@ -1,37 +1,61 @@
// Last verified for v2169
use std::io::{Error, Write};
use crate::{codec::var_int::VarInt, serial::PacketWrite};
use pumpkin_macros::packet;
#[derive(PacketWrite)]
#[packet(88)]
pub struct CSetTitle {
pub action_type: VarInt,
pub text: String,
pub fade_in_duration: VarInt,
pub remain_duration: VarInt,
pub fade_out_duration: VarInt,
pub title_type: TitleType,
pub title_text: String,
pub fade_in_time: VarInt,
pub stay_time: VarInt,
pub fade_out_time: VarInt,
pub xuid: String,
pub platform_online_id: String,
pub filtered_message: String,
pub filtered_title_message: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum TitleType {
Clear,
Reset,
Title,
Subtitle,
Actionbar,
Times,
TitleTextObject,
SubtitleTextObject,
ActionbarTextObject,
}
impl PacketWrite for TitleType {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
VarInt(*self as i32).write(writer)
}
}
impl CSetTitle {
#[must_use]
pub const fn new(
action_type: i32,
text: String,
fade_in_duration: i32,
remain_duration: i32,
fade_out_duration: i32,
title_type: TitleType,
title_text: String,
fade_in_time: i32,
stay_time: i32,
fade_out_time: i32,
) -> Self {
Self {
action_type: VarInt(action_type),
text,
fade_in_duration: VarInt(fade_in_duration),
remain_duration: VarInt(remain_duration),
fade_out_duration: VarInt(fade_out_duration),
title_type,
title_text,
fade_in_time: VarInt(fade_in_time),
stay_time: VarInt(stay_time),
fade_out_time: VarInt(fade_out_time),
xuid: String::new(),
platform_online_id: String::new(),
filtered_message: String::new(),
filtered_title_message: String::new(),
}
}
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use crate::{
codec::{var_int::VarInt, var_ulong::VarULong},
serial::PacketWrite,
@@ -8,15 +10,5 @@ use pumpkin_macros::packet;
#[packet(75)]
pub struct CShowCredits {
pub player_runtime_id: VarULong,
pub status: VarInt,
}
impl CShowCredits {
#[must_use]
pub const fn new(player_runtime_id: VarULong, status: VarInt) -> Self {
Self {
player_runtime_id,
status,
}
}
pub credits_state: VarInt,
}

View File

@@ -1,15 +1,12 @@
use std::io::{Error, Write};
use crate::{
bedrock::client::gamerules_changed::GameRules,
bedrock::client::{GameType, gamerules_changed::GameRule},
codec::{var_int::VarInt, var_long::VarLong, var_uint::VarUInt, var_ulong::VarULong},
serial::PacketWrite,
};
use pumpkin_macros::packet;
use pumpkin_util::{
GameMode,
math::{position::BlockPos, vector3::Vector3},
};
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use uuid::Uuid;
#[derive(PacketWrite)]
@@ -22,7 +19,7 @@ pub struct CStartGame {
// The runtime ID is unique for each world session, and
// entities are generally identified in packets using this runtime ID.
pub runtime_entity_id: VarULong,
pub player_gamemode: GameMode,
pub player_gamemode: GameType,
pub position: Vector3<f32>,
pub pitch: f32,
pub yaw: f32,
@@ -113,7 +110,7 @@ pub struct LevelSettings {
// Level Settings
pub generator_type: VarInt,
pub world_gamemode: GameMode,
pub world_gamemode: GameType,
pub hardcore: bool,
pub difficulty: VarInt,
pub spawn_position: BlockPos,
@@ -135,7 +132,7 @@ pub struct LevelSettings {
pub commands_enabled: bool,
pub is_texture_packs_required: bool,
pub rule_data: GameRules,
pub rule_data: Vec<GameRule>,
pub experiments: Experiments,
pub bonus_chest: bool,
@@ -167,13 +164,30 @@ pub struct LevelSettings {
pub allow_anonymous_block_drops_in_editor_worlds: bool,
}
#[derive(Default, PacketWrite)]
#[derive(Default)]
pub struct Experiments {
//TODO! https://mojang.github.io/bedrock-protocol-docs/html/Experiments.html
pub names_size: u32,
pub toggles: Vec<ExperimentToggle>,
pub experiments_ever_toggled: bool,
}
impl PacketWrite for Experiments {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
(self.toggles.len() as u32).write(writer)?;
for toggle in &self.toggles {
toggle.write(writer)?;
}
self.experiments_ever_toggled.write(writer)?;
Ok(())
}
}
#[derive(PacketWrite)]
pub struct ExperimentToggle {
pub name: String,
pub enabled: bool,
}
#[derive(Clone, Copy)]
pub enum GamePublishSetting {
NoMultiPlay = 0,

View File

@@ -1,19 +1,11 @@
// Last verified for v2169
use crate::{codec::var_ulong::VarULong, serial::PacketWrite};
use pumpkin_macros::packet;
#[derive(PacketWrite)]
#[packet(17)]
pub struct CTakeItemActor {
// https://github.com/Sandertv/gophertunnel/blob/master/minecraft/protocol/packet/take_item_actor.go
pub item_runtime_id: VarULong,
pub actor_runtime_id: VarULong,
}
impl CTakeItemActor {
#[must_use]
pub const fn new(item_runtime_id: VarULong, actor_runtime_id: VarULong) -> Self {
Self {
item_runtime_id,
actor_runtime_id,
}
}
}

View File

@@ -6,15 +6,15 @@ use crate::serial::PacketWrite;
#[packet(85)]
pub struct CTransfer {
pub address: String,
pub port: u16,
pub server_address: String,
pub server_port: u16,
pub reload_world: bool,
}
impl PacketWrite for CTransfer {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
self.address.write(writer)?;
self.port.write(writer)?;
self.server_address.write(writer)?;
self.server_port.write(writer)?;
self.reload_world.write(writer)?;
// Optional GatheringsConfigurationJoinInfo.
false.write(writer)
@@ -23,10 +23,10 @@ impl PacketWrite for CTransfer {
impl CTransfer {
#[must_use]
pub const fn new(address: String, port: u16, reload_world: bool) -> Self {
pub const fn new(server_address: String, server_port: u16, reload_world: bool) -> Self {
Self {
address,
port,
server_address,
server_port,
reload_world,
}
}

View File

@@ -1,20 +1,16 @@
// Last verified for v2169
use pumpkin_macros::packet;
use crate::serial::PacketWrite;
use super::common::AbilityLayer;
use crate::{bedrock::client::SerializedAbilitiesData, serial::PacketWrite};
#[packet(187)]
#[derive(PacketWrite)]
pub struct CUpdateAbilities {
// https://mojang.github.io/bedrock-protocol-docs/html/UpdateAbilitiesPacket.html
// https://mojang.github.io/bedrock-protocol-docs/html/SerializedAbilitiesData.html
pub target_player_raw_id: i64,
pub player_permission: u8,
pub command_permission: u8,
pub layers: Vec<AbilityLayer>,
pub data: SerializedAbilitiesData,
}
// TODO: confirm these
#[repr(u32)]
pub enum Ability {
Build = 0,

View File

@@ -1,20 +1,19 @@
// Last verified for v2169
use pumpkin_macros::packet;
use crate::{
codec::{var_uint::VarUInt, var_ulong::VarULong},
serial::PacketWrite,
};
use crate::{codec::var_ulong::VarULong, serial::PacketWrite};
#[derive(PacketWrite)]
#[packet(29)]
pub struct CUpdateAttributes {
pub runtime_id: VarULong,
pub attributes: Vec<Attribute>,
pub player_tick: VarULong,
pub target_runtime_id: VarULong,
pub attribute_list: Vec<AttributeData>,
pub tick: VarULong,
}
#[derive(PacketWrite)]
pub struct Attribute {
pub struct AttributeData {
pub min_value: f32,
pub max_value: f32,
pub current_value: f32,
@@ -22,5 +21,15 @@ pub struct Attribute {
pub default_max_value: f32,
pub default_value: f32,
pub name: String,
pub modifiers_list_size: VarUInt,
pub modifiers: Vec<AttributeModifier>,
}
#[derive(PacketWrite)]
pub struct AttributeModifier {
pub id: String,
pub name: String,
pub amount: f32,
pub operation: i32,
pub operand: i32,
pub is_serializable: bool,
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use pumpkin_util::math::position::BlockPos;
@@ -6,7 +8,7 @@ use crate::{codec::var_uint::VarUInt, serial::PacketWrite};
#[derive(PacketWrite)]
#[packet(21)]
pub struct CUpdateBlock {
pub position: BlockPos,
pub block_position: BlockPos,
pub block_runtime_id: VarUInt,
pub flags: VarUInt,
pub layer: VarUInt,
@@ -14,14 +16,14 @@ pub struct CUpdateBlock {
impl CUpdateBlock {
#[must_use]
pub const fn new(position: BlockPos, block_runtime_id: u32) -> Self {
Self::with_layer(position, block_runtime_id, 0)
pub const fn new(block_position: BlockPos, block_runtime_id: u32) -> Self {
Self::with_layer(block_position, block_runtime_id, 0)
}
#[must_use]
pub const fn with_layer(position: BlockPos, block_runtime_id: u32, layer: u32) -> Self {
pub const fn with_layer(block_position: BlockPos, block_runtime_id: u32, layer: u32) -> Self {
Self {
position,
block_position,
block_runtime_id: VarUInt(block_runtime_id),
flags: VarUInt(0x3), // neighbors | network
layer: VarUInt(layer),

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use pumpkin_nbt::compound::NbtCompound;

View File

@@ -0,0 +1,44 @@
use crate::serial::{PacketRead, PacketWrite};
use std::str::FromStr;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct EnumAsStr<T>(T);
impl<T: FromStr> FromStr for EnumAsStr<T>
where
std::io::Error: From<T::Err>,
{
type Err = std::io::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(T::from_str(s)?))
}
}
#[allow(clippy::to_string_trait_impl)]
impl<T: ToString> ToString for EnumAsStr<T> {
fn to_string(&self) -> String {
self.0.to_string()
}
}
impl<T: FromStr> PacketRead for EnumAsStr<T>
where
std::io::Error: From<T::Err>,
{
fn read<R: std::io::Read>(reader: &mut R) -> Result<Self, std::io::Error> {
Self::from_str(&String::read(reader)?)
}
}
impl<T: ToString> PacketWrite for EnumAsStr<T> {
fn write<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
self.to_string().write(writer)
}
}
impl<T> From<T> for EnumAsStr<T> {
fn from(value: T) -> Self {
Self(value)
}
}

View File

@@ -1,8 +1,8 @@
pub mod client;
pub mod enum_as_str;
pub mod network_item;
pub mod packet_decoder;
pub mod packet_encoder;
pub mod respawn;
pub mod server;
pub mod status;

View File

@@ -1,31 +0,0 @@
use std::io::{Error, ErrorKind, Read, Write};
use crate::serial::{PacketRead, PacketWrite};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum RespawnState {
SearchingForSpawn = 0,
ReadyToSpawn = 1,
ClientReadyToSpawn = 2,
}
impl PacketRead for RespawnState {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
match u8::read(reader)? {
0 => Ok(Self::SearchingForSpawn),
1 => Ok(Self::ReadyToSpawn),
2 => Ok(Self::ClientReadyToSpawn),
state => Err(Error::new(
ErrorKind::InvalidData,
format!("invalid Bedrock respawn state {state}"),
)),
}
}
}
impl PacketWrite for RespawnState {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
(*self as u8).write(writer)
}
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use std::io::{Error, Read, Write};
use pumpkin_macros::packet;
@@ -11,79 +13,79 @@ use crate::{
#[derive(Debug, PacketRead, PacketWrite)]
#[packet(27)]
pub struct SActorEvent {
pub entity_runtime_id: VarULong,
pub event_type: ActorEventType,
pub event_data: VarInt,
pub target_runtime_id: VarULong,
pub event_id: ActorEventID,
pub data: VarInt,
pub fire_at_position: Option<Vector3<f32>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ActorEventType {
None = 0,
Jump = 1,
Hurt = 2,
Death = 3,
StartAttacking = 4,
StopAttacking = 5,
TamingFailed = 6,
TamingSucceeded = 7,
ShakeWetness = 8,
pub enum ActorEventID {
None,
Jump,
Hurt,
Death,
StartAttacking,
StopAttacking,
TamingFailed,
TamingSucceeded,
ShakeWetness,
EatGrass = 10,
FishhookBubble = 11,
FishhookFishPosition = 12,
FishhookHookTime = 13,
FishhookTease = 14,
SquidFleeing = 15,
ZombieConverting = 16,
PlayAmbient = 17,
SpawnAlive = 18,
StartOfferFlower = 19,
StopOfferFlower = 20,
LoveHearts = 21,
VillagerAngry = 22,
VillagerHappy = 23,
WitchHatMagic = 24,
FireworksExplode = 25,
InLoveHearts = 26,
SilverfishMergeAnimation = 27,
GuardianAttackSound = 28,
DrinkPotion = 29,
ThrowPotion = 30,
CartWithPrimeTNT = 31,
PrimeCreeper = 32,
AirSupply = 33,
AddPlayerLevels = 34,
GuardianMiningFatigue = 35,
AgentSwingArm = 36,
DragonStartDeathAnim = 37,
GroundDust = 38,
Shake = 39,
FishhookBubble,
FishhookFishPos,
FishhookHookTime,
FishhookTease,
SquidFleeing,
ZombieConverting,
PlayAmbient,
SpawnAlive,
StartOfferFlower,
StopOfferFlower,
LoveHearts,
VillagerAngry,
VillagerHappy,
WitchHatMagic,
FireworksExplode,
InLoveHearts,
SilverfishMergeAnimation,
GuardianAttackSound,
DrinkPotion,
ThrowPotion,
PrimeTNTCart,
PrimeCreeper,
AirSupply,
DeprecatedAddPlayerLevels,
GuardianMiningFatigue,
AgentSwingArm,
DragonStartDeathAnim,
GroundDust,
Shake,
Feed = 57,
BabyAge = 60,
InstantDeath = 61,
NotifyTrade = 62,
LeashDestroyed = 63,
CaravanUpdated = 64,
TalismanActivate = 65,
UpdateStructureFeature = 66,
PlayerSpawnedMob = 67,
Puke = 68,
UpdateStackSize = 69,
StartSwimming = 70,
BalloonPop = 71,
TreasureHunt = 72,
SummonAgent = 73,
FinishedChargingItem = 74,
InstantDeath,
NotifyTrade,
LeashDestroyed,
CaravanUpdated,
TalismanActivate,
DeprecatedUpdateStructureFeature,
PlayerSpawnedMob,
Puke,
UpdateStackSize,
StartSwimming,
BalloonPop,
TreasureHunt,
SummonAgent,
FinishedChargingItem,
ActorGrowUp = 76,
VibrationDetected = 77,
DrinkMilk = 78,
ShakeWetnessStop = 79,
KineticDamageDealt = 80,
HurtWithoutReceivingDamage = 81,
VibrationDetected,
DrinkMilk,
ShakeWetnessStop,
KineticDamageDealt,
HurtWithoutReceivingDamage,
}
impl PacketRead for ActorEventType {
impl PacketRead for ActorEventID {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
Ok(match u8::read(reader)? {
0 => Self::None,
@@ -97,7 +99,7 @@ impl PacketRead for ActorEventType {
8 => Self::ShakeWetness,
10 => Self::EatGrass,
11 => Self::FishhookBubble,
12 => Self::FishhookFishPosition,
12 => Self::FishhookFishPos,
13 => Self::FishhookHookTime,
14 => Self::FishhookTease,
15 => Self::SquidFleeing,
@@ -116,10 +118,10 @@ impl PacketRead for ActorEventType {
28 => Self::GuardianAttackSound,
29 => Self::DrinkPotion,
30 => Self::ThrowPotion,
31 => Self::CartWithPrimeTNT,
31 => Self::PrimeTNTCart,
32 => Self::PrimeCreeper,
33 => Self::AirSupply,
34 => Self::AddPlayerLevels,
34 => Self::DeprecatedAddPlayerLevels,
35 => Self::GuardianMiningFatigue,
36 => Self::AgentSwingArm,
37 => Self::DragonStartDeathAnim,
@@ -132,7 +134,7 @@ impl PacketRead for ActorEventType {
63 => Self::LeashDestroyed,
64 => Self::CaravanUpdated,
65 => Self::TalismanActivate,
66 => Self::UpdateStructureFeature,
66 => Self::DeprecatedUpdateStructureFeature,
67 => Self::PlayerSpawnedMob,
68 => Self::Puke,
69 => Self::UpdateStackSize,
@@ -152,7 +154,7 @@ impl PacketRead for ActorEventType {
}
}
impl PacketWrite for ActorEventType {
impl PacketWrite for ActorEventID {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
(*self as u8).write(writer)
}
@@ -166,21 +168,21 @@ mod tests {
fn reads_feed_event() {
let packet = SActorEvent::read(&mut b"\x019\x80\x80\x90\x11\0".as_slice()).unwrap();
assert_eq!(packet.entity_runtime_id, VarULong(1));
assert_eq!(packet.event_type, ActorEventType::Feed);
assert_eq!(packet.event_data, VarInt(17_956_864));
assert_eq!(packet.target_runtime_id, VarULong(1));
assert_eq!(packet.event_id, ActorEventID::Feed);
assert_eq!(packet.data, VarInt(17_956_864));
assert_eq!(packet.fire_at_position, None);
}
#[test]
fn feed_event_wire_value_is_bidirectional() {
let mut encoded = Vec::new();
ActorEventType::Feed.write(&mut encoded).unwrap();
ActorEventID::Feed.write(&mut encoded).unwrap();
assert_eq!(encoded, [57]);
assert_eq!(
ActorEventType::read(&mut encoded.as_slice()).unwrap(),
ActorEventType::Feed
ActorEventID::read(&mut encoded.as_slice()).unwrap(),
ActorEventID::Feed
);
}
}

View File

@@ -1,8 +1,14 @@
use std::io::{Error, Read, Write};
// Last verified for v2169
use std::{
io::{Error, Read, Write},
str::FromStr,
};
use pumpkin_macros::packet;
use crate::{
bedrock::enum_as_str::EnumAsStr,
codec::var_ulong::VarULong,
serial::{PacketRead, PacketWrite},
};
@@ -39,21 +45,22 @@ impl PacketWrite for AnimateAction {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum AnimateSwingSource {
None = 1,
Build = 2,
Mine = 3,
Interact = 4,
Attack = 5,
UseItem = 6,
ThrowItem = 7,
DropItem = 8,
Event = 9,
pub enum ActorSwingSource {
None,
Build,
Mine,
Interact,
Attack,
UseItem,
ThrowItem,
DropItem,
Event,
}
impl PacketRead for AnimateSwingSource {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
match String::read(reader)?.as_str() {
impl FromStr for ActorSwingSource {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"none" => Ok(Self::None),
"build" => Ok(Self::Build),
"mine" => Ok(Self::Mine),
@@ -68,8 +75,9 @@ impl PacketRead for AnimateSwingSource {
}
}
impl PacketWrite for AnimateSwingSource {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
#[allow(clippy::to_string_trait_impl)]
impl ToString for ActorSwingSource {
fn to_string(&self) -> String {
match self {
Self::None => "none",
Self::Build => "build",
@@ -81,7 +89,7 @@ impl PacketWrite for AnimateSwingSource {
Self::DropItem => "dropitem",
Self::Event => "event",
}
.write(writer)
.into()
}
}
@@ -89,9 +97,9 @@ impl PacketWrite for AnimateSwingSource {
#[packet(44)]
pub struct SAnimate {
pub action: AnimateAction,
pub runtime_entity_id: VarULong,
pub target_actor_runtime_id: VarULong,
pub data: f32,
pub swing_source: Option<AnimateSwingSource>,
pub swing_source: Option<EnumAsStr<ActorSwingSource>>,
}
#[cfg(test)]
@@ -102,9 +110,9 @@ mod tests {
fn animate_uses_cereal_swing_source_encoding() {
let packet = SAnimate {
action: AnimateAction::SwingArm,
runtime_entity_id: VarULong(42),
target_actor_runtime_id: VarULong(42),
data: 0.0,
swing_source: Some(AnimateSwingSource::Attack),
swing_source: Some(ActorSwingSource::Attack.into()),
};
let mut encoded = Vec::new();
packet.write(&mut encoded).unwrap();
@@ -113,16 +121,16 @@ mod tests {
let decoded = SAnimate::read(&mut encoded.as_slice()).unwrap();
assert_eq!(decoded.action, AnimateAction::SwingArm);
assert_eq!(decoded.runtime_entity_id, VarULong(42));
assert_eq!(decoded.target_actor_runtime_id, VarULong(42));
assert_eq!(decoded.data, 0.0);
assert_eq!(decoded.swing_source, Some(AnimateSwingSource::Attack));
assert_eq!(decoded.swing_source, Some(ActorSwingSource::Attack.into()));
}
#[test]
fn animate_omits_absent_swing_source_value() {
let packet = SAnimate {
action: AnimateAction::NoAction,
runtime_entity_id: VarULong(1),
target_actor_runtime_id: VarULong(1),
data: 0.0,
swing_source: None,
};

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use pumpkin_util::math::position::BlockPos;
@@ -6,7 +8,7 @@ use crate::serial::PacketRead;
#[derive(Debug, PacketRead)]
#[packet(34)]
pub struct SBlockPickRequest {
pub block_pos: BlockPos,
pub add_block_nbt: bool,
pub hotbar_slot: u8,
pub position: BlockPos,
pub with_data: bool,
pub max_slots: u8,
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use std::io::{Error, Read};
use pumpkin_macros::packet;
@@ -21,9 +23,7 @@ impl PacketRead for SClientCacheBlobStatus {
}
let mut miss_hashes = Vec::with_capacity(miss_count.min(256));
for _ in 0..miss_count {
let mut bytes = [0u8; 8];
reader.read_exact(&mut bytes)?;
miss_hashes.push(u64::from_le_bytes(bytes));
miss_hashes.push(u64::read(reader)?);
}
let hit_count = VarUInt::read(reader)?.0 as usize;
@@ -35,9 +35,7 @@ impl PacketRead for SClientCacheBlobStatus {
}
let mut hit_hashes = Vec::with_capacity(hit_count.min(256));
for _ in 0..hit_count {
let mut bytes = [0u8; 8];
reader.read_exact(&mut bytes)?;
hit_hashes.push(u64::from_le_bytes(bytes));
hit_hashes.push(u64::read(reader)?);
}
Ok(Self {

View File

@@ -1,9 +1,10 @@
// Last verified for v2169
use crate::serial::PacketRead;
use pumpkin_macros::packet;
#[derive(PacketRead)]
#[packet(129)]
pub struct SClientCacheStatus {
// https://mojang.github.io/bedrock-protocol-docs/html/ClientCacheStatusPacket.html
pub cache_supported: bool,
pub is_cache_supported: bool,
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use std::borrow::Cow;
use uuid::Uuid;
@@ -8,10 +10,17 @@ use crate::serial::{PacketRead, PacketReadSlice};
#[packet(77)]
pub struct SCommandRequest<'a> {
pub command: Cow<'a, str>,
pub command_type: Cow<'a, str>,
pub command_uuid: Uuid,
pub request_id: Cow<'a, str>,
pub player_actor_unique_id: i64,
pub is_internal_source: bool,
pub origin: CommandOriginData<'a>,
pub is_internal: bool,
// TODO: enum CurrentCmdVersion
pub version: Cow<'a, str>,
}
#[derive(Debug, PacketRead, PacketReadSlice)]
pub struct CommandOriginData<'a> {
pub r#type: Cow<'a, str>,
pub uuid: Uuid,
pub request_id: Cow<'a, str>,
pub player_id: i64,
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use crate::serial::{PacketRead, PacketWrite};
@@ -5,8 +7,7 @@ use crate::serial::{PacketRead, PacketWrite};
#[derive(Debug, PacketWrite, PacketRead)]
#[packet(47)]
pub struct SContainerClose {
// https://mojang.github.io/bedrock-protocol-docs/html/ContainerClosePacket.html
pub container_id: u8,
pub container_type: u8,
pub server_initiated: bool,
pub server_initiated_close: bool,
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use crate::codec::var_uint::VarUInt;
use crate::codec::var_ulong::VarULong;
use crate::serial::{PacketRead, PacketReadSlice, PacketWrite};
@@ -10,9 +12,9 @@ pub const EMOTE_FLAG_MUTE_CHAT: u8 = 1 << 1;
#[derive(Debug, PacketRead, PacketReadSlice, PacketWrite)]
#[packet(138)]
pub struct SEmote<'a> {
pub runtime_entity_id: VarULong,
pub emote_length: VarUInt,
pub actor_runtime_id: VarULong,
pub emote_id: Cow<'a, str>,
pub emote_length_ticks: VarUInt,
pub xuid: Cow<'a, str>,
pub platform_id: Cow<'a, str>,
pub flags: u8,

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use uuid::Uuid;
use crate::{
@@ -9,8 +11,8 @@ use pumpkin_macros::packet;
#[derive(Debug, PacketRead, PacketWrite)]
#[packet(152)]
pub struct SEmoteList {
pub runtime_entity_id: VarULong,
pub emote_pieces: Vec<Uuid>,
pub runtime_id: VarULong,
pub emote_piece_ids: Vec<Uuid>,
}
#[cfg(test)]
@@ -21,8 +23,8 @@ mod tests {
#[test]
fn emote_list_serialization() {
let packet = SEmoteList {
runtime_entity_id: VarULong(123),
emote_pieces: vec![Uuid::new_v4(), Uuid::new_v4()],
runtime_id: VarULong(123),
emote_piece_ids: vec![Uuid::new_v4(), Uuid::new_v4()],
};
let mut buf = Vec::new();
@@ -31,7 +33,7 @@ mod tests {
let mut reader = Cursor::new(buf);
let decoded = SEmoteList::read(&mut reader).unwrap();
assert_eq!(packet.runtime_entity_id.0, decoded.runtime_entity_id.0);
assert_eq!(packet.emote_pieces, decoded.emote_pieces);
assert_eq!(packet.runtime_id.0, decoded.runtime_id.0);
assert_eq!(packet.emote_piece_ids, decoded.emote_piece_ids);
}
}

View File

@@ -0,0 +1,39 @@
// Last verified for v2169
use std::io::{Error, Read};
use pumpkin_macros::packet;
use pumpkin_util::math::vector3::Vector3;
use crate::{codec::var_ulong::VarULong, serial::PacketRead};
#[derive(Debug, PacketRead)]
#[packet(33)]
pub struct SInteract {
pub action: Action,
pub target_runtime_id: VarULong,
pub position: Option<Vector3<f32>>,
}
#[derive(Debug)]
#[repr(u8)]
pub enum Action {
Invalid = 0,
StopRiding = 3,
InteractUpdate = 4,
NpcOpen = 5,
OpenInventory = 6,
}
impl PacketRead for Action {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
match u8::read(reader)? {
0 => Ok(Self::Invalid),
3 => Ok(Self::StopRiding),
4 => Ok(Self::InteractUpdate),
5 => Ok(Self::NpcOpen),
6 => Ok(Self::OpenInventory),
_ => Err(Error::other("")),
}
}
}

View File

@@ -1,48 +0,0 @@
use std::io::{Error, Read};
use pumpkin_macros::packet;
use pumpkin_util::math::vector3::Vector3;
use crate::{codec::var_ulong::VarULong, serial::PacketRead};
#[derive(Debug, PacketRead)]
#[packet(33)]
pub struct SInteraction {
// https://mojang.github.io/bedrock-protocol-docs/html/InteractPacket.html
pub action: Action,
pub target_runtime_id: VarULong,
pub position: Option<Vector3<f32>>,
}
#[derive(Debug)]
#[repr(i8)]
pub enum Action {
Invalid = 0,
Interact = 1,
// No longer used in newer versions
Attack = 2,
StopRiding = 3,
InteractUpdate = 4,
NpcOpen = 5,
OpenInventory = 6,
}
impl PacketRead for Action {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
let mut byte = [0];
reader.read_exact(&mut byte)?;
let this = match byte[0] {
0 => Self::Invalid,
1 => Self::Interact,
2 => Self::Attack,
3 => Self::StopRiding,
4 => Self::InteractUpdate,
5 => Self::NpcOpen,
6 => Self::OpenInventory,
_ => return Err(Error::other("")),
};
Ok(this)
}
}

View File

@@ -1,3 +1,7 @@
// Last verified for v2169
use std::io::{Error, Read};
use pumpkin_macros::packet;
use crate::{codec::var_int::VarInt, serial::PacketRead};
@@ -5,16 +9,32 @@ use crate::{codec::var_int::VarInt, serial::PacketRead};
#[derive(PacketRead)]
#[packet(312)]
pub struct SLoadingScreen {
// https://mojang.github.io/bedrock-protocol-docs/html/ServerboundLoadingScreenPacket.html
// Loading Screen Packet Type
// 0: Inavil, 1: Start, 2: End
status: VarInt,
_id: Option<u32>,
loading_screen_packet_type: LoadingScreenPacketType,
_loading_screen_id: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum LoadingScreenPacketType {
StartLoadingScreen = 0,
EndLoadingScreen = 1,
}
impl PacketRead for LoadingScreenPacketType {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
match VarInt::read(reader)?.0 {
0 => Ok(Self::StartLoadingScreen),
1 => Ok(Self::EndLoadingScreen),
val => Err(Error::other(format!(
"Invalid LoadingScreenPacketType: {val}"
))),
}
}
}
impl SLoadingScreen {
#[must_use]
pub const fn is_loading_done(&self) -> bool {
self.status.0 == 2
pub fn is_loading_done(&self) -> bool {
self.loading_screen_packet_type == LoadingScreenPacketType::EndLoadingScreen
}
}

View File

@@ -1,36 +1,19 @@
// Last verified for v2169
use crate::{
bedrock::network_item::NetworkItemStackDescriptor, codec::var_ulong::VarULong,
serial::PacketRead,
};
use pumpkin_macros::packet;
use std::io::{Error, Read};
#[derive(Debug)]
#[derive(Debug, PacketRead)]
#[packet(31)]
pub struct SMobEquipment {
pub entity_runtime_id: VarULong,
pub item: NetworkItemStackDescriptor,
pub inventory_slot: u8,
pub hotbar_slot: u8,
pub window_id: u8,
}
impl PacketRead for SMobEquipment {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
let entity_runtime_id = VarULong::read(reader)?;
let item = NetworkItemStackDescriptor::read(reader)?;
let inventory_slot = u8::read(reader)?;
let hotbar_slot = u8::read(reader)?;
let window_id = u8::read(reader)?;
Ok(Self {
entity_runtime_id,
item,
inventory_slot,
hotbar_slot,
window_id,
})
}
pub slot: u8,
pub selected_slot: u8,
pub container_id: u8,
}
#[cfg(test)]
@@ -54,9 +37,9 @@ mod tests {
assert_eq!(packet.entity_runtime_id.0, 42);
assert_eq!(packet.item.id, 0);
assert_eq!(packet.inventory_slot, 3);
assert_eq!(packet.hotbar_slot, 4);
assert_eq!(packet.window_id, 5);
assert_eq!(packet.slot, 3);
assert_eq!(packet.selected_slot, 4);
assert_eq!(packet.container_id, 5);
assert_eq!(reader.position(), reader.get_ref().len() as u64);
}
}

View File

@@ -7,7 +7,7 @@ pub mod command_request;
pub mod container_close;
pub mod emote;
pub mod emote_list;
pub mod interaction;
pub mod interact;
pub mod inventory_transaction;
pub mod item_stack_request;
pub mod loading_screen;
@@ -21,7 +21,7 @@ pub mod player_hotbar;
pub mod request_ability;
pub mod request_chunk_radius;
pub mod request_network_settings;
pub mod resource_pack_response;
pub mod resource_pack_client_response;
pub mod respawn;
pub mod set_local_player_as_initialized;
pub mod set_player_inventory_options;
@@ -36,7 +36,7 @@ pub use command_request::*;
pub use container_close::*;
pub use emote::*;
pub use emote_list::*;
pub use interaction::{Action as InteractAction, SInteraction};
pub use interact::{Action as InteractAction, SInteract};
pub use inventory_transaction::*;
pub use item_stack_request::*;
pub use loading_screen::*;
@@ -44,13 +44,13 @@ pub use login::*;
pub use mob_equipment::*;
pub use modal_form_response::*;
pub use packet_violation_warning::*;
pub use player_action::{Action as PlayerActionType, SPlayerAction};
pub use player_action::{PlayerActionType, SPlayerAction};
pub use player_auth_input::*;
pub use player_hotbar::*;
pub use request_ability::*;
pub use request_chunk_radius::*;
pub use request_network_settings::*;
pub use resource_pack_response::*;
pub use resource_pack_client_response::*;
pub use respawn::*;
pub use set_local_player_as_initialized::*;
pub use set_player_inventory_options::*;

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use crate::codec::var_uint::VarUInt;
use crate::serial::{PacketRead, PacketReadSlice};
use pumpkin_macros::packet;
@@ -7,6 +9,8 @@ use std::borrow::Cow;
#[packet(101)]
pub struct SModalFormResponse<'a> {
pub form_id: VarUInt,
pub form_data: Option<Cow<'a, str>>,
pub cancel_reason: Option<u8>,
pub json_response: Option<Cow<'a, str>>,
// TODO: enum ModalFormCancelReason
pub form_cancel_reason: Option<u8>,
}

View File

@@ -1,25 +1,16 @@
use std::io::{Error, Read};
// Last verified for v2169
use pumpkin_macros::packet;
use crate::{codec::var_int::VarInt, serial::PacketRead};
#[derive(Debug)]
#[derive(Debug, PacketRead)]
#[packet(156)]
pub struct SPacketViolationWarning {
// TODO: enum PacketViolationType
pub violation_type: VarInt,
pub severity: VarInt,
pub packet_id: VarInt,
pub context: String,
}
impl PacketRead for SPacketViolationWarning {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
Ok(Self {
violation_type: VarInt::read(reader)?,
severity: VarInt::read(reader)?,
packet_id: VarInt::read(reader)?,
context: String::read(reader)?,
})
}
// TODO: enum PacketViolationSeverity
pub violation_severity: VarInt,
pub violation_packet_id: VarInt,
pub violation_context: String,
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use std::io::{Error, Read};
use pumpkin_macros::packet;
@@ -11,92 +13,92 @@ use crate::{
#[derive(Debug, PacketRead)]
#[packet(36)]
pub struct SPlayerAction {
pub runtime_id: VarULong,
pub action: Action,
pub block_pos: BlockPos,
pub player_runtime_id: VarULong,
pub action: PlayerActionType,
pub block_position: BlockPos,
pub result_pos: BlockPos,
pub face: VarInt,
}
#[derive(Debug)]
#[repr(i32)]
pub enum Action {
pub enum PlayerActionType {
Unknown = -1,
StartBreak = 0,
AbortBreak = 1,
StopBreak = 2,
GetUpdatedBlock = 3,
StartDestroyBlock,
AbortDestroyBlock,
StopDestroyBlock,
GetUpdatedBlock,
/// Seems to be not used, or atleast not send by client
DropItem = 4,
StartSleeping = 5,
StopSleeping = 6,
Respawn = 7,
Jump = 8,
StartSprint = 9,
StopSprint = 10,
StartSneak = 11,
StopSneak = 12,
CreativePlayerDestroyBlock = 13,
DimensionChangeAck = 14,
StartGlide = 15,
StopGlide = 16,
BuildDenied = 17,
CrackBreak = 18,
ChangeSkin = 19,
SetEnchantmentSeed = 20,
Swimming = 21,
StopSwimming = 22,
StartSpinAttack = 23,
StopSpinAttack = 24,
InteractBlock = 25,
PredictDestroyBlock = 26,
ContinueDestroyBlock = 27,
StartItemUseOn = 28,
StopItemUseOn = 29,
HandledTeleport = 30,
MissedSwing = 31,
StartCrawling = 32,
StopCrawling = 33,
StartFlying = 34,
StopFlying = 35,
ClientAckServerData = 36,
StartUsingItem = 37,
InternalUpdate = 38,
Count = 39,
DropItem,
StartSleeping,
StopSleeping,
Respawn,
StartJump,
StartSprinting,
StopSprinting,
StartSneaking,
StopSneaking,
CreativeDestroyBlock,
ChangeDimensionAck,
StartGliding,
StopGliding,
DenyDestroyBlock,
CrackBlock,
ChangeSkin,
UpdatedEnchantingSeed,
StartSwimming,
StopSwimming,
StartSpinAttack,
StopSpinAttack,
InteractWithBlock,
PredictDestroyBlock,
ContinueDestroyBlock,
StartItemUseOn,
StopItemUseOn,
HandledTeleport,
MissedSwing,
StartCrawling,
StopCrawling,
StartFlying,
StopFlying,
ClientAckServerData,
StartUsingItem,
InternalUpdate,
Count,
}
impl TryFrom<i32> for Action {
impl TryFrom<i32> for PlayerActionType {
type Error = String;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
-1 => Ok(Self::Unknown),
0 => Ok(Self::StartBreak),
1 => Ok(Self::AbortBreak),
2 => Ok(Self::StopBreak),
0 => Ok(Self::StartDestroyBlock),
1 => Ok(Self::AbortDestroyBlock),
2 => Ok(Self::StopDestroyBlock),
3 => Ok(Self::GetUpdatedBlock),
4 => Ok(Self::DropItem),
5 => Ok(Self::StartSleeping),
6 => Ok(Self::StopSleeping),
7 => Ok(Self::Respawn),
8 => Ok(Self::Jump),
9 => Ok(Self::StartSprint),
10 => Ok(Self::StopSprint),
11 => Ok(Self::StartSneak),
12 => Ok(Self::StopSneak),
13 => Ok(Self::CreativePlayerDestroyBlock),
14 => Ok(Self::DimensionChangeAck),
15 => Ok(Self::StartGlide),
16 => Ok(Self::StopGlide),
17 => Ok(Self::BuildDenied),
18 => Ok(Self::CrackBreak),
8 => Ok(Self::StartJump),
9 => Ok(Self::StartSprinting),
10 => Ok(Self::StopSprinting),
11 => Ok(Self::StartSneaking),
12 => Ok(Self::StopSneaking),
13 => Ok(Self::CreativeDestroyBlock),
14 => Ok(Self::ChangeDimensionAck),
15 => Ok(Self::StartGliding),
16 => Ok(Self::StopGliding),
17 => Ok(Self::DenyDestroyBlock),
18 => Ok(Self::CrackBlock),
19 => Ok(Self::ChangeSkin),
20 => Ok(Self::SetEnchantmentSeed),
21 => Ok(Self::Swimming),
20 => Ok(Self::UpdatedEnchantingSeed),
21 => Ok(Self::StartSwimming),
22 => Ok(Self::StopSwimming),
23 => Ok(Self::StartSpinAttack),
24 => Ok(Self::StopSpinAttack),
25 => Ok(Self::InteractBlock),
25 => Ok(Self::InteractWithBlock),
26 => Ok(Self::PredictDestroyBlock),
27 => Ok(Self::ContinueDestroyBlock),
28 => Ok(Self::StartItemUseOn),
@@ -116,7 +118,7 @@ impl TryFrom<i32> for Action {
}
}
impl PacketRead for Action {
impl PacketRead for PlayerActionType {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
let action = VarInt::read(reader)?;

View File

@@ -1,15 +1,14 @@
// Last verified for v2169
use pumpkin_macros::packet;
use crate::{codec::var_uint::VarUInt, serial::PacketRead};
/// Sent by the Bedrock client when the player changes their active hotbar slot.
///
/// Packet ID: `48`
/// Ref: <https://mojang.github.io/bedrock-protocol-docs/html/PlayerHotbarPacket.html>
#[derive(PacketRead)]
#[packet(48)]
pub struct SPlayerHotbar {
pub selected_slot: VarUInt,
pub container_id: u8,
pub select_slot: bool,
pub should_select_slot: bool,
}

View File

@@ -1,3 +1,5 @@
// Last verified for v2169
use pumpkin_macros::packet;
use crate::{codec::var_int::VarInt, serial::PacketRead};
@@ -5,7 +7,6 @@ use crate::{codec::var_int::VarInt, serial::PacketRead};
#[derive(PacketRead, Debug)]
#[packet(69)]
pub struct SRequestChunkRadius {
// https://mojang.github.io/bedrock-protocol-docs/html/RequestChunkRadiusPacket.html
pub chunk_radius: VarInt,
pub max_radius: u8,
pub max_chunk_radius: u8,
}

View File

@@ -1,10 +1,12 @@
// Last verified for v2169
use pumpkin_macros::packet;
use crate::serial::PacketRead;
#[derive(PacketRead)]
#[packet(0xC1)]
#[packet(193)]
pub struct SRequestNetworkSettings {
#[serial(big_endian)]
pub protocol_version: i32,
pub client_network_version: i32,
}

View File

@@ -4,13 +4,13 @@ use crate::{codec::var_uint::VarUInt, serial::PacketRead};
use pumpkin_macros::packet;
#[packet(8)]
pub struct SResourcePackResponse {
pub struct SResourcePackClientResponse {
pub response: u8,
pub download_size: u16,
pub pack_ids: Vec<String>,
}
impl PacketRead for SResourcePackResponse {
impl PacketRead for SResourcePackClientResponse {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
let encoded_status = VarUInt::read(reader)?.0;
let response = encoded_status
@@ -47,7 +47,7 @@ impl PacketRead for SResourcePackResponse {
}
}
impl SResourcePackResponse {
impl SResourcePackClientResponse {
pub const STATUS_REFUSED: u8 = 1;
pub const STATUS_SEND_PACKS: u8 = 2;
pub const STATUS_HAVE_ALL_PACKS: u8 = 3;

View File

@@ -1,12 +1,69 @@
// Last verified for v2169
use std::io::{Error, ErrorKind, Read, Write};
use pumpkin_macros::packet;
use pumpkin_util::math::vector3::Vector3;
use crate::{bedrock::respawn::RespawnState, codec::var_ulong::VarULong, serial::PacketRead};
use crate::{
codec::var_ulong::VarULong,
serial::{PacketRead, PacketWrite},
};
#[derive(PacketRead)]
#[derive(PacketRead, PacketWrite)]
#[packet(45)]
pub struct SRespawn {
pub position: Vector3<f32>,
pub state: RespawnState,
pub player_runtime_id: VarULong,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum RespawnState {
SearchingForSpawn,
ReadyToSpawn,
ClientReadyToSpawn,
}
impl PacketRead for RespawnState {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
match u8::read(reader)? {
0 => Ok(Self::SearchingForSpawn),
1 => Ok(Self::ReadyToSpawn),
2 => Ok(Self::ClientReadyToSpawn),
state => Err(Error::new(
ErrorKind::InvalidData,
format!("invalid Bedrock respawn state {state}"),
)),
}
}
}
impl PacketWrite for RespawnState {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
(*self as u8).write(writer)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::serial::PacketRead;
#[test]
fn respawn_packet_roundtrip() {
let packet = SRespawn {
position: Vector3::new(1.5, 64.0, -2.25),
state: RespawnState::ReadyToSpawn,
player_runtime_id: VarULong(42),
};
let mut encoded = Vec::new();
packet.write(&mut encoded).unwrap();
let decoded = SRespawn::read(&mut encoded.as_slice()).unwrap();
assert_eq!(decoded.position, packet.position);
assert_eq!(decoded.state, packet.state);
assert_eq!(decoded.player_runtime_id.0, packet.player_runtime_id.0);
}
}

View File

@@ -1,8 +1,10 @@
// Last verified for v2169
use crate::{codec::var_ulong::VarULong, serial::PacketRead};
use pumpkin_macros::packet;
#[derive(PacketRead)]
#[packet(113)]
pub struct SSetLocalPlayerAsInitialized {
pub runtime_entity_id: VarULong,
pub player_id: VarULong,
}

View File

@@ -1,12 +1,20 @@
// Last verified for v2169
use crate::{codec::var_int::VarInt, serial::PacketRead};
use pumpkin_macros::packet;
#[derive(PacketRead)]
#[packet(307)]
pub struct SSetPlayerInventoryOptions {
// TODO: enum InventoryLeftTabIndex
pub left_inventory_tab: VarInt,
// TODO: enum InventoryRightTabIndex
pub right_inventory_tab: VarInt,
pub filtering: bool,
pub inventory_layout: VarInt,
pub crafting_layout: VarInt,
// TODO: enum InventoryLayout
pub layout_inv: VarInt,
// TODO: enum InventoryLayout
pub layout_craft: VarInt,
}

View File

@@ -5,19 +5,19 @@ use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ServerboundPacket {
/// Typically, the first packet sent by the client, which is used to authenticate the connection with the server.
Auth = 2,
Auth = 3,
/// This packet type represents a command issued by a client to the server. This can be a `ConCommand` such as `/kill <player>` or `/weather clear`.
/// The response will vary depending on the command issued.
ExecCommand = 3,
ExecCommand = 2,
}
impl ServerboundPacket {
#[must_use]
pub const fn from_i32(n: i32) -> Self {
pub const fn from_i32(n: i32) -> Option<Self> {
match n {
// 3 => Self::Auth,
2 => Self::ExecCommand,
_ => Self::Auth,
3 => Some(Self::Auth),
2 => Some(Self::ExecCommand),
_ => None,
}
}
}
@@ -25,8 +25,7 @@ impl ServerboundPacket {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// Server -> Client
pub enum ClientboundPacket {
/// This packet is a notification of the connection's current auth status. When the server receives an auth request, it will respond with an empty `SERVERDATA_RESPONSE_VALUE`,
/// followed immediately by a `SERVERDATA_AUTH_RESPONSE` indicating whether authentication succeeded or failed. Note that the status code is returned in the packet id field, so when pairing the response with the original auth request, you may need to look at the packet id of the `SERVERDATA_RESPONSE_VALUE`.
/// This packet is a notification of the connection's current auth status.
AuthResponse = 2,
/// A `SERVERDATA_RESPONSE` packet is the response to a `SERVERDATA_EXECCOMMAND` request.
Output = 0,
@@ -52,15 +51,17 @@ impl ClientboundPacket {
pub enum PacketError {
#[error("Invalid length")]
InvalidLength,
#[error("Failed to send packet")]
#[error("Failed to send packet: {0}")]
FailedSend(std::io::Error),
#[error("Missing packet null terminator")]
MissingNullTerminator,
#[error("Invalid packet string body")]
#[error("Invalid packet string body: {0}")]
InvalidBody(std::str::Utf8Error),
#[error("Unknown packet type: {0}")]
UnknownPacketType(i32),
}
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq)]
/// Serverbound packet
pub struct Packet {
id: i32,
@@ -107,6 +108,8 @@ impl Packet {
.map_err(|_| PacketError::InvalidLength)?,
);
let ptype = ServerboundPacket::from_i32(ty).ok_or(PacketError::UnknownPacketType(ty))?;
// Calculate body boundaries (starts after len, id, and ty -> 4+4+4 = 12)
// Ends 2 bytes before the total length (excluding the two trailing null bytes)
let body_start = 12;
@@ -122,11 +125,7 @@ impl Packet {
.into();
incoming.drain(0..total_packet_len);
Ok(Some(Self {
id,
ptype: ServerboundPacket::from_i32(ty),
body,
}))
Ok(Some(Self { id, ptype, body }))
}
#[must_use]

View File

@@ -5,10 +5,7 @@ use std::{
};
use pumpkin_nbt::{Nbt, NbtCompound};
use pumpkin_util::{
GameMode,
math::{position::BlockPos, vector2::Vector2, vector3::Vector3},
};
use pumpkin_util::math::{position::BlockPos, vector2::Vector2, vector3::Vector3};
use crate::{
codec::{var_int::VarInt, var_uint::VarUInt},
@@ -190,19 +187,6 @@ impl PacketWrite for SocketAddr {
}
}
impl PacketWrite for GameMode {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
VarInt(match self {
Self::Survival => 0,
Self::Creative => 1,
Self::Adventure => 2,
// I have no idea why
Self::Spectator => 6,
})
.write(writer)
}
}
impl PacketWrite for Cow<'_, str> {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
self.as_ref().write(writer)

View File

@@ -1,17 +1,24 @@
use crate::block::BlockBehaviour;
use crate::block::BlockFuture;
use crate::block::BlockMetadata;
use crate::block::CanPlaceAtArgs;
use crate::block::GetStateForNeighborUpdateArgs;
use crate::block::OnPlaceArgs;
use crate::block::blocks::abstract_wall_mounting::WallMountedBlock;
use pumpkin_data::Block;
use pumpkin_data::BlockDirection;
use pumpkin_data::BlockId;
use pumpkin_data::BlockStateId;
use pumpkin_data::FacingExt;
use pumpkin_data::block_properties::AmethystClusterLikeProperties;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::{
Block, BlockDirection, BlockId, BlockStateId, FacingExt,
block_properties::{AmethystClusterLikeProperties, BlockProperties},
};
use pumpkin_macros::pumpkin_block;
use pumpkin_world::world::BlockFlags;
use rand::RngExt;
use crate::block::{
BlockBehaviour, BlockFuture, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
OnPlaceArgs, RandomTickArgs, blocks::abstract_wall_mounting::WallMountedBlock,
};
const ALL_DIRECTIONS: [BlockDirection; 6] = [
BlockDirection::Down,
BlockDirection::Up,
BlockDirection::North,
BlockDirection::South,
BlockDirection::West,
BlockDirection::East,
];
pub struct AmethystBlock;
@@ -34,7 +41,7 @@ impl BlockBehaviour for AmethystBlock {
args.block.default_state.id,
args.block,
);
props.facing = args.direction.to_facing().opposite();
props.facing = args.direction.to_facing();
props.waterlogged = args.replacing.water_source();
props.to_state_id(args.block)
})
@@ -60,6 +67,69 @@ impl BlockBehaviour for AmethystBlock {
impl WallMountedBlock for AmethystBlock {
fn get_direction(&self, state_id: BlockStateId, block: &Block) -> BlockDirection {
let props = AmethystClusterLikeProperties::from_state_id(state_id, block);
props.facing.to_block_direction()
props.facing.to_block_direction().opposite()
}
}
#[pumpkin_block("minecraft:budding_amethyst")]
pub struct BuddingAmethystBlock;
impl BlockBehaviour for BuddingAmethystBlock {
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if rand::rng().random_range(0..5) == 0 {
let grow_direction = {
let mut rng = rand::rng();
ALL_DIRECTIONS[rng.random_range(0..ALL_DIRECTIONS.len())]
};
let grow_pos = args.position.offset(grow_direction.to_offset());
let (relative_block, relative_state) = args.world.get_block_and_state(&grow_pos);
let relative_state_id = relative_state.id;
let next_stage_and_water =
if can_cluster_grow_at_state(relative_block, relative_state_id) {
Some((&Block::SMALL_AMETHYST_BUD, relative_block == &Block::WATER))
} else if relative_block == &Block::SMALL_AMETHYST_BUD {
let props = AmethystClusterLikeProperties::from_state_id(
relative_state_id,
&Block::SMALL_AMETHYST_BUD,
);
(props.facing == grow_direction.to_facing())
.then_some((&Block::MEDIUM_AMETHYST_BUD, props.waterlogged))
} else if relative_block == &Block::MEDIUM_AMETHYST_BUD {
let props = AmethystClusterLikeProperties::from_state_id(
relative_state_id,
&Block::MEDIUM_AMETHYST_BUD,
);
(props.facing == grow_direction.to_facing())
.then_some((&Block::LARGE_AMETHYST_BUD, props.waterlogged))
} else if relative_block == &Block::LARGE_AMETHYST_BUD {
let props = AmethystClusterLikeProperties::from_state_id(
relative_state_id,
&Block::LARGE_AMETHYST_BUD,
);
(props.facing == grow_direction.to_facing())
.then_some((&Block::AMETHYST_CLUSTER, props.waterlogged))
} else {
None
};
if let Some((next_stage, waterlogged)) = next_stage_and_water {
let mut target_props = AmethystClusterLikeProperties::default(next_stage);
target_props.facing = grow_direction.to_facing();
target_props.waterlogged = waterlogged;
let target_state_id = target_props.to_state_id(next_stage);
args.world
.set_block_state(&grow_pos, target_state_id, BlockFlags::NOTIFY_ALL)
.await;
}
}
})
}
}
#[must_use]
pub fn can_cluster_grow_at_state(block: &Block, state_id: BlockStateId) -> bool {
block.default_state.is_air()
|| (block == &Block::WATER && state_id == Block::WATER.default_state.id)
}

View File

@@ -317,6 +317,40 @@ impl BlockBehaviour for ChestBlock {
#[pumpkin_block_from_tag("minecraft:copper_chests")]
pub struct CopperChestBlock;
impl
crate::block::blocks::weathering_copper::ChangeOverTimeBlock<
crate::block::blocks::weathering_copper::WeatherState,
> for CopperChestBlock
{
fn get_age(
&self,
block: &Block,
) -> Option<crate::block::blocks::weathering_copper::WeatherState> {
crate::block::blocks::weathering_copper::get_weather_state(block)
}
fn get_chance_modifier(
&self,
age: crate::block::blocks::weathering_copper::WeatherState,
) -> f32 {
crate::block::blocks::weathering_copper::get_chance_modifier(age)
}
fn get_next(&self, block: &Block) -> Option<&'static Block> {
crate::block::blocks::weathering_copper::get_next(block)
}
fn get_previous(&self, block: &Block) -> Option<&'static Block> {
crate::block::blocks::weathering_copper::get_previous(block)
}
fn get_first(&self, block: &Block) -> Option<&'static Block> {
crate::block::blocks::weathering_copper::get_first(block)
}
}
impl crate::block::blocks::weathering_copper::WeatheringCopper for CopperChestBlock {}
impl BlockBehaviour for CopperChestBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move { on_place_chest_impl(&args) })
@@ -363,8 +397,12 @@ impl BlockBehaviour for CopperChestBlock {
return;
}
// Try to oxidize the copper chest
try_oxidize_copper_chest(args.world, args.position, args.block, chest_props).await;
crate::block::blocks::weathering_copper::change_over_time(
args.world,
args.position,
args.block,
)
.await;
})
}
@@ -376,142 +414,6 @@ impl BlockBehaviour for CopperChestBlock {
}
}
/// Copper oxidation levels with their ordinal values
const COPPER_CHEST_OXIDATION: &[(&Block, &Block, u8)] = &[
(&Block::COPPER_CHEST, &Block::EXPOSED_COPPER_CHEST, 0),
(
&Block::EXPOSED_COPPER_CHEST,
&Block::WEATHERED_COPPER_CHEST,
1,
),
(
&Block::WEATHERED_COPPER_CHEST,
&Block::OXIDIZED_COPPER_CHEST,
2,
),
];
/// Get the oxidation level ordinal for a block (None if not oxidizable copper chest)
fn get_oxidation_level(block: &Block) -> Option<u8> {
// Check non-waxed variants
if block == &Block::COPPER_CHEST {
return Some(0);
}
if block == &Block::EXPOSED_COPPER_CHEST {
return Some(1);
}
if block == &Block::WEATHERED_COPPER_CHEST {
return Some(2);
}
if block == &Block::OXIDIZED_COPPER_CHEST {
return Some(3);
}
// Waxed variants don't oxidize
None
}
/// Try to oxidize a copper chest to its next oxidation level.
/// Uses vanilla's degradation algorithm with neighbor checking.
async fn try_oxidize_copper_chest(
world: &Arc<World>,
position: &BlockPos,
current_block: &Block,
chest_props: ChestLikeProperties,
) {
use rand::RngExt;
// Base chance per random tick: ~5.69%
const BASE_DEGRADATION_CHANCE: f32 = 0.056_888_89;
// First roll: only ~5.69% chance to even attempt oxidation
if rand::rng().random::<f32>() >= BASE_DEGRADATION_CHANCE {
return;
}
// Find the next oxidation level
let (next_block, current_level) = match COPPER_CHEST_OXIDATION
.iter()
.find(|(from, _, _)| *from == current_block)
{
Some((_, to, level)) => (*to, *level),
None => return, // Already fully oxidized or waxed
};
// Scan neighbors in 4-block Manhattan distance to calculate oxidation chance
let (same_level_count, higher_level_count) =
count_neighbor_oxidation_levels(world, position, current_level);
// If we found any neighbors at a LOWER level, oxidation is blocked
// (This is handled in count_neighbor_oxidation_levels by returning early)
// Calculate weighted probability: ((higher + 1) / (higher + same + 1))^2 * multiplier
let ratio =
(higher_level_count + 1) as f32 / (higher_level_count + same_level_count + 1) as f32;
// Multiplier is 0.75 for UNAFFECTED (level 0), 1.0 for others
let multiplier = if current_level == 0 { 0.75 } else { 1.0 };
let final_chance = ratio * ratio * multiplier;
if rand::rng().random::<f32>() >= final_chance {
return;
}
// Apply oxidation with same properties
let new_state_id = chest_props.to_state_id(next_block);
world
.set_block_state(position, new_state_id, BlockFlags::NOTIFY_LISTENERS)
.await;
}
/// Count copper blocks at same and higher oxidation levels within 4-block Manhattan distance.
/// Returns (same, higher) counts, or (0, 0) if a lower-level neighbor was found (blocking oxidation).
fn count_neighbor_oxidation_levels(
world: &Arc<World>,
center: &BlockPos,
current_level: u8,
) -> (i32, i32) {
use std::cmp::Ordering;
let mut same_level_count = 0i32;
let mut higher_level_count = 0i32;
// Iterate in a 4-block Manhattan distance (9x9x9 cube checked with distance filter)
for dx in -4i32..=4 {
for dy in -4i32..=4 {
for dz in -4i32..=4 {
let manhattan_dist = dx.abs() + dy.abs() + dz.abs();
if manhattan_dist > 4 || manhattan_dist == 0 {
continue;
}
let neighbor_pos = BlockPos(pumpkin_util::math::vector3::Vector3::new(
center.0.x + dx,
center.0.y + dy,
center.0.z + dz,
));
let neighbor_block = world.get_block(&neighbor_pos);
if let Some(neighbor_level) = get_oxidation_level(neighbor_block) {
match neighbor_level.cmp(&current_level) {
Ordering::Less => {
// Found a neighbor at lower oxidation level - block oxidation entirely
return (0, 0);
}
Ordering::Greater => {
higher_level_count += 1;
}
Ordering::Equal => {
same_level_count += 1;
}
}
}
}
}
}
(same_level_count, higher_level_count)
}
/// Trapped chests have the same behavior as wooden chests but also emit redstone power based on viewer count.
#[pumpkin_block("minecraft:trapped_chest")]
pub struct TrappedChestBlock;

View File

@@ -0,0 +1,209 @@
use std::sync::Arc;
use pumpkin_data::block_properties::{BlockProperties, NetherWartLikeProperties, blocks_movement};
use pumpkin_data::dimension::Dimension;
use pumpkin_data::{Block, BlockDirection, BlockId, BlockState, BlockStateId, Enchantment};
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::tick::TickPriority;
use pumpkin_world::world::BlockFlags;
use rand::RngExt;
use crate::block::{
BlockBehaviour, BlockFuture, BlockMetadata, BrokenArgs, OnNeighborUpdateArgs,
OnScheduledTickArgs, PlacedArgs, RandomTickArgs,
};
use crate::world::World;
/// Melts ice at the given position into water (or removes it in ultrawarm dimensions like the Nether).
pub async fn melt(world: &Arc<World>, position: &BlockPos) {
if world.dimension == Dimension::THE_NETHER {
world
.set_block_state(position, BlockStateId::AIR, BlockFlags::NOTIFY_ALL)
.await;
} else {
world
.set_block_state(
position,
Block::WATER.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
world.update_neighbors(position, None).await;
}
}
/// Returns whether the frosted ice block at `pos` has fewer adjacent frosted ice blocks than `limit`.
pub fn fewer_neighbors_than(world: &World, pos: &BlockPos, limit: usize) -> bool {
let mut count = 0;
for dir in BlockDirection::all() {
let neighbor_pos = pos.offset(dir.to_offset());
if world.get_block(&neighbor_pos) == &Block::FROSTED_ICE {
count += 1;
if count >= limit {
return false;
}
}
}
true
}
/// Slightly melts the frosted ice at `pos`.
///
/// Increments its age if `age < 3`, or completely melts it if `age >= 3`.
/// Returns `true` if it completely melted, or `false` if it just aged.
async fn slightly_melt(world: &Arc<World>, pos: &BlockPos, block: &Block, age: u8) -> bool {
if age < 3 {
let mut new_props = NetherWartLikeProperties::default(block);
new_props.r#age = age + 1;
world
.set_block_state(pos, new_props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await;
false
} else {
melt(world, pos).await;
true
}
}
pub struct IceBlock;
impl BlockMetadata for IceBlock {
fn ids() -> Box<[BlockId]> {
[BlockId::ICE].into()
}
}
impl BlockBehaviour for IceBlock {
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let held_item = args.player.inventory().held_item().await;
let has_silk_touch = held_item.get_enchantment_level(&Enchantment::SILK_TOUCH) > 0;
if !has_silk_touch {
if args.world.dimension == Dimension::THE_NETHER {
args.world
.set_block_state(args.position, BlockStateId::AIR, BlockFlags::NOTIFY_ALL)
.await;
return;
}
let below_pos = args.position.down();
let (below_block, below_state_id) = args.world.get_block_and_state_id(&below_pos);
let below_state = BlockState::from_id(below_state_id);
if blocks_movement(below_state, below_block.id)
|| below_state.is_liquid()
|| below_state.is_solid()
{
args.world
.set_block_state(
args.position,
Block::WATER.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
}
}
})
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let state = args.world.get_block_state(args.position);
let block_light = args.world.get_block_light_level(args.position).unwrap_or(0);
if block_light > (11u8.saturating_sub(state.opacity)) {
melt(args.world, args.position).await;
}
})
}
}
pub struct FrostedIceBlock;
impl BlockMetadata for FrostedIceBlock {
fn ids() -> Box<[BlockId]> {
[BlockId::FROSTED_ICE].into()
}
}
impl BlockBehaviour for FrostedIceBlock {
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let delay = rand::rng().random_range(60..=120);
args.world
.schedule_block_tick(args.block, *args.position, delay, TickPriority::Normal);
})
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let should_check_melt = rand::rng().random_range(0..3) == 0
|| fewer_neighbors_than(args.world, args.position, 4);
if should_check_melt {
let state_id = args.world.get_block_state_id(args.position);
let state = args.world.get_block_state(args.position);
let props = NetherWartLikeProperties::from_state_id(state_id, args.block);
let age = props.r#age;
let brightness = if args.world.dimension == Dimension::THE_END {
args.world.get_block_light_level(args.position).unwrap_or(0)
} else {
args.world.get_max_local_raw_brightness(args.position)
};
let threshold = 11u8.saturating_sub(age).saturating_sub(state.opacity);
if brightness > threshold
&& slightly_melt(args.world, args.position, args.block, age).await
{
for dir in BlockDirection::all() {
let neighbor_pos = args.position.offset(dir.to_offset());
let (neighbor_block, neighbor_state_id) =
args.world.get_block_and_state_id(&neighbor_pos);
if neighbor_block == &Block::FROSTED_ICE {
let neighbor_props = NetherWartLikeProperties::from_state_id(
neighbor_state_id,
neighbor_block,
);
if !slightly_melt(
args.world,
&neighbor_pos,
neighbor_block,
neighbor_props.r#age,
)
.await
{
let delay = rand::rng().random_range(20..=40);
args.world.schedule_block_tick(
neighbor_block,
neighbor_pos,
delay,
TickPriority::Normal,
);
}
}
}
return;
}
}
let delay = rand::rng().random_range(20..=40);
args.world
.schedule_block_tick(args.block, *args.position, delay, TickPriority::Normal);
})
}
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if args.source_block == &Block::FROSTED_ICE
&& fewer_neighbors_than(args.world, args.position, 2)
{
melt(args.world, args.position).await;
}
})
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
IceBlock.broken(args).await;
})
}
}

View File

@@ -0,0 +1,111 @@
use pumpkin_data::{
Block, BlockDirection, BlockStateId,
block_properties::{BlockProperties, OakLeavesLikeProperties},
tag,
tag::Taggable,
};
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::{
tick::TickPriority,
world::{BlockAccessor, BlockFlags},
};
use crate::block::{
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs,
RandomTickArgs,
};
pub const DECAY_DISTANCE: u8 = 7;
#[pumpkin_block_from_tag("minecraft:leaves")]
pub struct LeavesBlock;
#[must_use]
pub fn get_distance_at(block: &Block, state_id: BlockStateId) -> u8 {
if block.has_tag(&tag::Block::MINECRAFT_PREVENTS_NEARBY_LEAF_DECAY) {
0
} else if block.has_tag(&tag::Block::MINECRAFT_LEAVES) {
OakLeavesLikeProperties::from_state_id(state_id, block).distance
} else {
DECAY_DISTANCE
}
}
#[must_use]
pub fn update_distance(
world: &dyn BlockAccessor,
pos: &BlockPos,
mut props: OakLeavesLikeProperties,
) -> OakLeavesLikeProperties {
let mut min_distance = DECAY_DISTANCE;
for direction in BlockDirection::all() {
let neighbor_pos = pos.offset(direction.to_offset());
let (neighbor_block, neighbor_state) = world.get_block_and_state(&neighbor_pos);
let dist = get_distance_at(neighbor_block, neighbor_state.id).saturating_add(1);
min_distance = min_distance.min(dist);
if min_distance == 1 {
break;
}
}
props.distance = min_distance.min(DECAY_DISTANCE);
props
}
impl BlockBehaviour for LeavesBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props =
OakLeavesLikeProperties::from_state_id(args.block.default_state.id, args.block);
props.persistent = true;
props.waterlogged = args.replacing.water_source();
props = update_distance(args.world, args.position, props);
props.to_state_id(args.block)
})
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let neighbor_block = args.world.get_block(args.neighbor_position);
let distance_from_neighbor =
get_distance_at(neighbor_block, args.neighbor_state_id).saturating_add(1);
let current_props = OakLeavesLikeProperties::from_state_id(args.state_id, args.block);
if distance_from_neighbor != 1 || current_props.distance != distance_from_neighbor {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
})
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position);
let props = OakLeavesLikeProperties::from_state_id(state_id, args.block);
let updated_props = update_distance(&**args.world, args.position, props);
let new_state_id = updated_props.to_state_id(args.block);
if new_state_id != state_id {
args.world
.set_block_state(args.position, new_state_id, BlockFlags::NOTIFY_ALL)
.await;
}
})
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position);
let props = OakLeavesLikeProperties::from_state_id(state_id, args.block);
if !props.persistent && props.distance == DECAY_DISTANCE {
args.world
.break_block(args.position, None, BlockFlags::empty())
.await;
}
})
}
}

View File

@@ -43,6 +43,7 @@ pub mod iron_bars;
pub mod slabs;
pub mod stairs;
pub mod walls;
pub mod weathering_copper;
// Decorative & furnishings
pub mod banners;
@@ -73,6 +74,7 @@ pub mod carved_pumpkin;
pub mod dripstone;
pub mod farmland;
pub mod hay;
pub mod leaves;
pub mod logs;
pub mod mangrove_roots;
pub mod plant;
@@ -87,7 +89,9 @@ pub mod dirt_path;
pub mod dragon_egg;
pub mod falling;
pub mod grass_block;
pub mod ice;
pub mod infested;
pub mod nylium;
pub mod powder_snow;
pub mod rooted_dirt;
pub mod snow;

View File

@@ -1,28 +1,32 @@
use std::sync::Arc;
use std::sync::{Arc, atomic::Ordering};
use crate::block::{
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, OnEntityCollisionArgs,
OnStateReplacedArgs,
use pumpkin_data::{
Block, BlockDirection, BlockState, BlockStateId, Rotation,
block_properties::{Axis, BlockProperties, HorizontalAxis, NetherPortalLikeProperties},
dimension::Dimension,
entity::EntityType,
};
use crate::entity::EntityBase;
use crate::world::World;
use crate::world::portal::nether::NetherPortal;
use pumpkin_data::Block;
use pumpkin_data::BlockStateId;
use pumpkin_data::block_properties::{
Axis, BlockProperties, HorizontalAxis, NetherPortalLikeProperties,
};
use pumpkin_data::dimension::Dimension;
use pumpkin_data::entity::EntityType;
use pumpkin_macros::pumpkin_block;
use pumpkin_util::GameMode;
use pumpkin_util::{Difficulty, GameMode, math::vector3::Vector3};
use rand::RngExt;
use uuid::Uuid;
use crate::{
block::{
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, OnEntityCollisionArgs,
OnStateReplacedArgs, RandomTickArgs,
},
entity::{EntityBase, r#type::from_type},
world::{World, portal::nether::NetherPortal},
};
#[pumpkin_block("minecraft:nether_portal")]
pub struct NetherPortalBlock;
impl NetherPortalBlock {
/// Gets the portal delay time based on entity type and gamemode
fn get_portal_time(world: &Arc<World>, entity: &dyn EntityBase) -> u32 {
#[must_use]
pub fn get_portal_time(world: &Arc<World>, entity: &dyn EntityBase) -> u32 {
let entity_type = entity.get_entity().entity_type;
let level_info = world.level_info.load();
match entity_type.id {
@@ -68,6 +72,62 @@ impl BlockBehaviour for NetherPortalBlock {
})
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let level_info = args.world.level_info.load();
let difficulty = level_info.difficulty;
if !level_info.game_rules.spawn_monsters
|| !level_info.game_rules.spawn_mobs
|| difficulty == Difficulty::Peaceful
|| (args.world.dimension != Dimension::OVERWORLD
&& args.world.dimension != Dimension::OVERWORLD_CAVES)
{
return;
}
let difficulty_id = difficulty as u32;
let roll = rand::rng().random_range(0..2000);
if roll >= difficulty_id {
return;
}
let player_close = args
.world
.get_closest_player(args.position.to_centered_f64(), 128.0)
.is_some();
if !player_close {
return;
}
let mut bottom_pos = *args.position;
while args.world.get_block(&bottom_pos) == &Block::NETHER_PORTAL {
bottom_pos = bottom_pos.down();
}
if args
.world
.get_block_state(&bottom_pos)
.is_side_solid(BlockDirection::Up)
{
let spawn_pos = Vector3::new(
bottom_pos.0.x as f64 + 0.5,
(bottom_pos.0.y + 1) as f64,
bottom_pos.0.z as f64 + 0.5,
);
let mob = from_type(
&EntityType::ZOMBIFIED_PIGLIN,
spawn_pos,
args.world,
Uuid::new_v4(),
);
mob.get_entity()
.portal_cooldown
.store(300, Ordering::Relaxed);
args.world.spawn_entity(mob).await;
}
})
}
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let target_world =
@@ -102,4 +162,24 @@ impl BlockBehaviour for NetherPortalBlock {
poi_storage.remove(args.position);
})
}
fn rotate(
&self,
block: &Block,
state_id: BlockStateId,
rotation: Rotation,
) -> &'static BlockState {
match rotation {
Rotation::Clockwise90 | Rotation::CounterClockwise90 => {
let mut props = NetherPortalLikeProperties::from_state_id(state_id, block);
props.axis = match props.axis {
HorizontalAxis::X => HorizontalAxis::Z,
HorizontalAxis::Z => HorizontalAxis::X,
};
let new_state_id = props.to_state_id(block);
BlockState::from_id(new_state_id)
}
_ => BlockState::from_id(state_id),
}
}
}

View File

@@ -0,0 +1,300 @@
use std::sync::Arc;
use pumpkin_data::tag::Taggable;
use pumpkin_data::{Block, tag};
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::world::BlockFlags;
use rand::RngExt;
use crate::block::{BlockBehaviour, BlockFuture, BonemealArgs, RandomTickArgs};
use crate::world::World;
#[pumpkin_block_from_tag("minecraft:nylium")]
pub struct NyliumBlock;
impl NyliumBlock {
fn can_be_nylium(world: &World, pos: &BlockPos) -> bool {
let above_pos = pos.up();
if !world.is_loaded(&above_pos) {
return true;
}
let above_state = world.get_block_state(&above_pos);
above_state.opacity < 15
}
}
impl BlockBehaviour for NyliumBlock {
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !Self::can_be_nylium(args.world, args.position) {
args.world
.set_block_state(
args.position,
Block::NETHERRACK.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
}
})
}
fn is_valid_bonemeal_target(&self, args: BonemealArgs<'_>) -> bool {
let above = args.position.up();
args.world.is_in_height_limit(above.0.y)
&& args.world.is_loaded(&above)
&& args.world.get_block_state(&above).is_air()
}
fn is_bonemeal_success(&self, _args: BonemealArgs<'_>) -> bool {
true
}
fn perform_bonemeal<'a>(&'a self, args: BonemealArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let world = args.world;
let block = args.block;
let above_pos = args.position.up();
if block == &Block::CRIMSON_NYLIUM {
place_crimson_vegetation(world, &above_pos).await;
} else if block == &Block::WARPED_NYLIUM {
place_warped_vegetation(world, &above_pos).await;
place_nether_sprouts(world, &above_pos).await;
if rand::rng().random_range(0..8) == 0 {
place_twisting_vines(world, &above_pos).await;
}
}
})
}
}
async fn place_crimson_vegetation(world: &Arc<World>, origin: &BlockPos) {
for _ in 0..9 {
let dx = rand::rng().random_range(0..3) - rand::rng().random_range(0..3);
let dy = rand::rng().random_range(0..1) - rand::rng().random_range(0..1);
let dz = rand::rng().random_range(0..3) - rand::rng().random_range(0..3);
let target_pos = origin.add(dx, dy, dz);
if !world.is_in_height_limit(target_pos.0.y)
|| !world.is_loaded(&target_pos)
|| !world.get_block_state(&target_pos).is_air()
{
continue;
}
let below_pos = target_pos.down();
if !world.is_loaded(&below_pos) {
continue;
}
let below_block = world.get_block(&below_pos);
if !below_block.has_tag(&tag::Block::MINECRAFT_NYLIUM) {
continue;
}
let roll = rand::rng().random_range(0..99);
let placed_block = if roll < 87 {
&Block::CRIMSON_ROOTS
} else if roll < 98 {
&Block::CRIMSON_FUNGUS
} else {
&Block::WARPED_FUNGUS
};
let state = placed_block.default_state;
if !world.block_registry.can_place_at(
None,
Some(world),
world.as_ref(),
None,
placed_block,
state,
&target_pos,
None,
None,
) {
continue;
}
world
.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL)
.await;
}
}
async fn place_warped_vegetation(world: &Arc<World>, origin: &BlockPos) {
for _ in 0..9 {
let dx = rand::rng().random_range(0..3) - rand::rng().random_range(0..3);
let dy = rand::rng().random_range(0..1) - rand::rng().random_range(0..1);
let dz = rand::rng().random_range(0..3) - rand::rng().random_range(0..3);
let target_pos = origin.add(dx, dy, dz);
if !world.is_in_height_limit(target_pos.0.y)
|| !world.is_loaded(&target_pos)
|| !world.get_block_state(&target_pos).is_air()
{
continue;
}
let below_pos = target_pos.down();
if !world.is_loaded(&below_pos) {
continue;
}
let below_block = world.get_block(&below_pos);
if !below_block.has_tag(&tag::Block::MINECRAFT_NYLIUM) {
continue;
}
let roll = rand::rng().random_range(0..100);
let placed_block = if roll < 85 {
&Block::WARPED_ROOTS
} else if roll < 86 {
&Block::CRIMSON_ROOTS
} else if roll < 99 {
&Block::WARPED_FUNGUS
} else {
&Block::CRIMSON_FUNGUS
};
let state = placed_block.default_state;
if !world.block_registry.can_place_at(
None,
Some(world),
world.as_ref(),
None,
placed_block,
state,
&target_pos,
None,
None,
) {
continue;
}
world
.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL)
.await;
}
}
async fn place_nether_sprouts(world: &Arc<World>, origin: &BlockPos) {
for _ in 0..9 {
let dx = rand::rng().random_range(0..3) - rand::rng().random_range(0..3);
let dy = rand::rng().random_range(0..1) - rand::rng().random_range(0..1);
let dz = rand::rng().random_range(0..3) - rand::rng().random_range(0..3);
let target_pos = origin.add(dx, dy, dz);
if !world.is_in_height_limit(target_pos.0.y)
|| !world.is_loaded(&target_pos)
|| !world.get_block_state(&target_pos).is_air()
{
continue;
}
let below_pos = target_pos.down();
if !world.is_loaded(&below_pos) {
continue;
}
let below_block = world.get_block(&below_pos);
if !below_block.has_tag(&tag::Block::MINECRAFT_NYLIUM) {
continue;
}
let placed_block = &Block::NETHER_SPROUTS;
let state = placed_block.default_state;
if !world.block_registry.can_place_at(
None,
Some(world),
world.as_ref(),
None,
placed_block,
state,
&target_pos,
None,
None,
) {
continue;
}
world
.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL)
.await;
}
}
async fn place_twisting_vines(world: &Arc<World>, origin: &BlockPos) {
for _ in 0..9 {
let dx = rand::rng().random_range(0..3) - rand::rng().random_range(0..3);
let dy = rand::rng().random_range(0..1) - rand::rng().random_range(0..1);
let dz = rand::rng().random_range(0..3) - rand::rng().random_range(0..3);
let mut target_pos = origin.add(dx, dy, dz);
if !find_twisting_vines_target_y(world, &mut target_pos) {
continue;
}
let mut height = rand::rng().random_range(0..2) + 1;
if rand::rng().random_range(0..6) == 0 {
height *= 2;
}
if rand::rng().random_range(0..10) == 0 {
height = 1;
}
let mut current_pos = target_pos;
for i in 0..height {
if !world.is_in_height_limit(current_pos.0.y)
|| !world.is_loaded(&current_pos)
|| !world.get_block_state(&current_pos).is_air()
{
break;
}
let is_top = i == height - 1
|| !world.is_in_height_limit(current_pos.up().0.y)
|| !world.is_loaded(&current_pos.up())
|| !world.get_block_state(&current_pos.up()).is_air();
if is_top {
world
.set_block_state(
&current_pos,
Block::TWISTING_VINES.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
break;
}
world
.set_block_state(
&current_pos,
Block::TWISTING_VINES_PLANT.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
current_pos = current_pos.up();
}
}
}
fn find_twisting_vines_target_y(world: &World, pos: &mut BlockPos) -> bool {
let mut current = *pos;
for _ in 0..1 {
if world.is_loaded(&current) && world.get_block_state(&current).is_air() {
let below = current.down();
if world.is_loaded(&below) {
let block_below = world.get_block(&below);
if block_below == &Block::WARPED_NYLIUM
|| block_below == &Block::WARPED_WART_BLOCK
|| block_below == &Block::TWISTING_VINES
|| block_below == &Block::TWISTING_VINES_PLANT
{
*pos = current;
return true;
}
}
}
current = current.down();
}
false
}

View File

@@ -1,18 +1,37 @@
use std::sync::Arc;
use pumpkin_data::{
Block, BlockDirection, BlockStateId,
block_properties::{BlockProperties, ChorusFlowerLikeProperties},
tag::{self, Taggable},
world::WorldEvent,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use pumpkin_world::{
tick::TickPriority,
world::{BlockAccessor, BlockFlags},
};
use rand::RngExt;
use crate::block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnScheduledTickArgs,
use super::chorus_plant;
use crate::{
block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
OnScheduledTickArgs, RandomTickArgs,
},
world::World,
};
pub const DEAD_AGE: u8 = 5;
const HORIZONTAL_DIRECTIONS: [BlockDirection; 4] = [
BlockDirection::North,
BlockDirection::South,
BlockDirection::West,
BlockDirection::East,
];
#[pumpkin_block("minecraft:chorus_flower")]
pub struct ChorusFlowerBlock;
@@ -43,10 +62,157 @@ impl BlockBehaviour for ChorusFlowerBlock {
}
})
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let above = args.position.up();
let max_y = args.world.dimension.min_y + args.world.dimension.height - 1;
if args.world.get_block(&above).default_state.is_air() && above.0.y <= max_y {
let state_id = args.world.get_block_state_id(args.position);
let state_props = ChorusFlowerLikeProperties::from_state_id(state_id, args.block);
let current_age = state_props.age;
if current_age < DEAD_AGE {
let mut grow_upwards = false;
let mut pillar_on_support_block = false;
let below_pos = args.position.down();
let (below_block, _) = args.world.get_block_and_state(&below_pos);
if below_block.has_tag(&tag::Block::MINECRAFT_SUPPORTS_CHORUS_FLOWER) {
grow_upwards = true;
} else if below_block == &Block::CHORUS_PLANT {
let mut height = 1;
for _ in 0..4 {
let test_pos = args.position.offset(Vector3::new(0, -(height + 1), 0));
let (test_block, _) = args.world.get_block_and_state(&test_pos);
if test_block != &Block::CHORUS_PLANT {
if test_block.has_tag(&tag::Block::MINECRAFT_SUPPORTS_CHORUS_FLOWER)
{
pillar_on_support_block = true;
}
break;
}
height += 1;
}
let max_chance = if pillar_on_support_block { 5 } else { 4 };
if height < 2 || height <= rand::rng().random_range(0..max_chance) {
grow_upwards = true;
}
} else if below_block.default_state.is_air() {
grow_upwards = true;
}
let above_2 = args.position.offset(Vector3::new(0, 2, 0));
if grow_upwards
&& all_neighbors_empty(args.world.as_ref(), &above, None)
&& args.world.get_block(&above_2).default_state.is_air()
{
let plant_state_id = chorus_plant::get_state_with_connections(
args.world.as_ref(),
&Block::CHORUS_PLANT,
args.position,
);
args.world
.set_block_state(args.position, plant_state_id, BlockFlags::NOTIFY_ALL)
.await;
place_grown_flower(args.world, &above, current_age).await;
} else if current_age < 4 {
let mut num_branch_attempts = rand::rng().random_range(0..4);
if pillar_on_support_block {
num_branch_attempts += 1;
}
let mut created_branch = false;
for _ in 0..num_branch_attempts {
let direction = HORIZONTAL_DIRECTIONS[rand::rng().random_range(0..4)];
let target = args.position.offset(direction.to_offset());
let target_below = target.down();
if args.world.get_block(&target).default_state.is_air()
&& args.world.get_block(&target_below).default_state.is_air()
&& all_neighbors_empty(
args.world.as_ref(),
&target,
Some(direction.opposite()),
)
{
place_grown_flower(args.world, &target, current_age + 1).await;
created_branch = true;
}
}
if created_branch {
let plant_state_id = chorus_plant::get_state_with_connections(
args.world.as_ref(),
&Block::CHORUS_PLANT,
args.position,
);
args.world
.set_block_state(
args.position,
plant_state_id,
BlockFlags::NOTIFY_ALL,
)
.await;
} else {
place_dead_flower(args.world, args.position).await;
}
} else {
place_dead_flower(args.world, args.position).await;
}
}
}
})
}
}
fn can_survive(block_accessor: &dyn BlockAccessor, pos: &BlockPos) -> bool {
let block_below = block_accessor.get_block(&pos.down());
pub async fn place_grown_flower(world: &Arc<World>, pos: &BlockPos, age: u8) {
let mut props = ChorusFlowerLikeProperties::default(&Block::CHORUS_FLOWER);
props.age = age;
world
.set_block_state(
pos,
props.to_state_id(&Block::CHORUS_FLOWER),
BlockFlags::NOTIFY_ALL,
)
.await;
world.sync_world_event(WorldEvent::SoundChorusGrow, *pos, 0);
}
pub async fn place_dead_flower(world: &Arc<World>, pos: &BlockPos) {
let mut props = ChorusFlowerLikeProperties::default(&Block::CHORUS_FLOWER);
props.age = DEAD_AGE;
world
.set_block_state(
pos,
props.to_state_id(&Block::CHORUS_FLOWER),
BlockFlags::NOTIFY_ALL,
)
.await;
world.sync_world_event(WorldEvent::SoundChorusDeath, *pos, 0);
}
#[must_use]
pub fn all_neighbors_empty(
world: &dyn BlockAccessor,
pos: &BlockPos,
ignore: Option<BlockDirection>,
) -> bool {
for direction in HORIZONTAL_DIRECTIONS {
if Some(direction) != ignore {
let neighbor_pos = pos.offset(direction.to_offset());
if !world.get_block(&neighbor_pos).default_state.is_air() {
return false;
}
}
}
true
}
#[must_use]
pub fn can_survive(block_accessor: &dyn BlockAccessor, pos: &BlockPos) -> bool {
let (block_below, _) = block_accessor.get_block_and_state(&pos.down());
if block_below == &Block::CHORUS_PLANT
|| block_below.has_tag(&tag::Block::MINECRAFT_SUPPORTS_CHORUS_FLOWER)
@@ -54,21 +220,21 @@ fn can_survive(block_accessor: &dyn BlockAccessor, pos: &BlockPos) -> bool {
return true;
}
if !block_below.is_air() {
if !block_below.default_state.is_air() {
return false;
}
// Below is air: the flower is the tip of a horizontal branch.
// Exactly one horizontal neighbor must be a chorus plant stem.
// Exactly one horizontal neighbor must be a chorus plant stem, and other horizontal neighbors must be air.
let mut plant_count = 0u32;
for dir in BlockDirection::horizontal() {
for dir in HORIZONTAL_DIRECTIONS {
let neighbor = block_accessor.get_block(&pos.offset(dir.to_offset()));
if neighbor == &Block::CHORUS_PLANT {
plant_count += 1;
if plant_count > 1 {
return false;
}
} else if !neighbor.is_air() {
} else if !neighbor.default_state.is_air() {
return false;
}
}

View File

@@ -76,7 +76,8 @@ impl BlockBehaviour for ChorusPlantBlock {
}
}
fn get_state_with_connections(
#[must_use]
pub fn get_state_with_connections(
block_accessor: &dyn BlockAccessor,
block: &Block,
pos: &BlockPos,

View File

@@ -0,0 +1,150 @@
use pumpkin_data::block_properties::{BlockProperties, CocoaLikeProperties, HorizontalFacing};
use pumpkin_data::tag::Taggable;
use pumpkin_data::{
Block, BlockState, BlockStateId, FacingExt, HorizontalFacingExt, Mirror, Rotation, tag,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::world::{BlockAccessor, BlockFlags};
use crate::block::{
BlockBehaviour, BlockFuture, BonemealArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
OnPlaceArgs, RandomTickArgs,
};
use crate::entity::EntityBase;
pub const MAX_AGE: u8 = 2;
type CocoaProperties = CocoaLikeProperties;
#[pumpkin_block("minecraft:cocoa")]
pub struct CocoaBlock;
impl CocoaBlock {
#[must_use]
pub fn can_survive(
world: &dyn BlockAccessor,
pos: &BlockPos,
facing: HorizontalFacing,
) -> bool {
let support_pos = pos.offset(facing.to_offset());
let block = world.get_block(&support_pos);
block.has_tag(&tag::Block::MINECRAFT_SUPPORTS_COCOA)
}
}
impl BlockBehaviour for CocoaBlock {
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
let state_id = args.block_accessor.get_block_state_id(args.position);
if state_id != Block::AIR.default_state.id {
let props = CocoaProperties::from_state_id(state_id, args.block);
return Self::can_survive(args.block_accessor, args.position, props.facing);
}
for facing in [
HorizontalFacing::North,
HorizontalFacing::South,
HorizontalFacing::West,
HorizontalFacing::East,
] {
if Self::can_survive(args.block_accessor, args.position, facing) {
return true;
}
}
false
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = CocoaProperties::default(args.block);
props.age = 0;
let directions = args.player.get_entity().get_entity_facing_order();
for dir in directions {
if let Some(facing) = dir.to_horizontal_facing()
&& Self::can_survive(args.world, args.position, facing)
{
props.facing = facing;
return props.to_state_id(args.block);
}
}
Block::AIR.default_state.id
})
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let props = CocoaProperties::from_state_id(args.state_id, args.block);
if args.direction == props.facing.to_block_direction()
&& !Self::can_survive(args.world, args.position, props.facing)
{
return Block::AIR.default_state.id;
}
args.state_id
})
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if rand::random::<u8>().is_multiple_of(5) {
let state_id = args.world.get_block_state_id(args.position);
let mut props = CocoaProperties::from_state_id(state_id, args.block);
if props.age < MAX_AGE {
props.age += 1;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
}
}
})
}
fn is_valid_bonemeal_target(&self, args: BonemealArgs<'_>) -> bool {
let props = CocoaProperties::from_state_id(args.state_id, args.block);
props.age < MAX_AGE
}
fn is_bonemeal_success(&self, _args: BonemealArgs<'_>) -> bool {
true
}
fn perform_bonemeal<'a>(&'a self, args: BonemealArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let mut props = CocoaProperties::from_state_id(args.state_id, args.block);
if props.age < MAX_AGE {
props.age += 1;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
}
})
}
fn rotate(
&self,
block: &Block,
state_id: BlockStateId,
rotation: Rotation,
) -> &'static BlockState {
let mut props = CocoaProperties::from_state_id(state_id, block);
props.facing = rotation.rotate_horizontal(props.facing);
BlockState::from_id(props.to_state_id(block))
}
fn mirror(&self, block: &Block, state_id: BlockStateId, mirror: Mirror) -> &'static BlockState {
let mut props = CocoaProperties::from_state_id(state_id, block);
props.facing = mirror.mirror_horizontal(props.facing);
BlockState::from_id(props.to_state_id(block))
}
}

View File

@@ -0,0 +1,189 @@
use std::sync::Arc;
use pumpkin_data::{
Block, BlockId, BlockStateId,
dimension::Dimension,
effect::StatusEffect,
entity::EntityType,
particle::Particle,
sound::{Sound, SoundCategory},
};
use pumpkin_util::{
Difficulty,
math::{position::BlockPos, vector3::Vector3},
};
use pumpkin_world::{tick::TickPriority, world::BlockFlags};
use rand::RngExt;
use crate::{
block::{
BlockBehaviour, BlockFuture, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
OnEntityCollisionArgs, OnScheduledTickArgs, RandomTickArgs, blocks::plant::PlantBlockBase,
},
world::World,
};
const EYEBLOSSOM_XZ_RANGE: i32 = 3;
const EYEBLOSSOM_Y_RANGE: i32 = 2;
pub struct EyeblossomBlock;
impl BlockMetadata for EyeblossomBlock {
fn ids() -> Box<[BlockId]> {
Box::new([BlockId::OPEN_EYEBLOSSOM, BlockId::CLOSED_EYEBLOSSOM])
}
}
impl BlockBehaviour for EyeblossomBlock {
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
.await
})
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !<Self as PlantBlockBase>::can_place_at(self, args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty())
.await;
return;
}
let was_open = args.block == &Block::OPEN_EYEBLOSSOM;
if try_changing_state(args.world, args.block, args.position).await {
let sound = if was_open {
Sound::BlockEyeblossomClose
} else {
Sound::BlockEyeblossomOpen
};
args.world.play_sound(
sound,
SoundCategory::Blocks,
&args.position.to_centered_f64(),
);
}
})
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let was_open = args.block == &Block::OPEN_EYEBLOSSOM;
if try_changing_state(args.world, args.block, args.position).await {
let sound = if was_open {
Sound::BlockEyeblossomCloseLong
} else {
Sound::BlockEyeblossomOpenLong
};
args.world.play_sound(
sound,
SoundCategory::Blocks,
&args.position.to_centered_f64(),
);
}
})
}
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if args.world.level_info.load().difficulty == Difficulty::Peaceful {
return;
}
if args.entity.get_entity().entity_type == &EntityType::BEE
&& let Some(living_entity) = args.entity.get_living_entity()
{
let effect = pumpkin_data::potion::Effect {
effect_type: &StatusEffect::POISON,
duration: 25,
amplifier: 0,
ambient: false,
show_particles: true,
show_icon: true,
blend: true,
};
living_entity.add_effect(effect).await;
}
})
}
}
impl PlantBlockBase for EyeblossomBlock {}
pub async fn try_changing_state(world: &Arc<World>, current_block: &Block, pos: &BlockPos) -> bool {
let is_open = current_block == &Block::OPEN_EYEBLOSSOM;
let should_be_open = if world.dimension == Dimension::OVERWORLD
|| world.dimension == Dimension::OVERWORLD_CAVES
{
world.level_time.lock().await.is_night()
} else {
is_open
};
if should_be_open == is_open {
return false;
}
let new_block = if is_open {
&Block::CLOSED_EYEBLOSSOM
} else {
&Block::OPEN_EYEBLOSSOM
};
world
.set_block_state(pos, new_block.default_state.id, BlockFlags::NOTIFY_ALL)
.await;
world.spawn_particle(
pos.to_centered_f64(),
Vector3::new(0.0, 0.0, 0.0),
0.0,
1,
Particle::Trail,
);
let mut rng = rand::rng();
for dx in -EYEBLOSSOM_XZ_RANGE..=EYEBLOSSOM_XZ_RANGE {
for dy in -EYEBLOSSOM_Y_RANGE..=EYEBLOSSOM_Y_RANGE {
for dz in -EYEBLOSSOM_XZ_RANGE..=EYEBLOSSOM_XZ_RANGE {
if dx == 0 && dy == 0 && dz == 0 {
continue;
}
let nearby_pos = pos.offset(Vector3::new(dx, dy, dz));
let nearby_block = world.get_block(&nearby_pos);
if nearby_block == current_block {
let dist_sqr = (dx * dx + dy * dy + dz * dz) as f64;
let distance = dist_sqr.sqrt();
let min_delay = (distance * 5.0) as u8;
let max_delay = (distance * 10.0) as u8;
let delay = if min_delay >= max_delay {
min_delay
} else {
rng.random_range(min_delay..=max_delay)
};
world.schedule_block_tick(
current_block,
nearby_pos,
delay.max(1),
TickPriority::Normal,
);
}
}
}
}
true
}

Some files were not shown because too many files have changed in this diff Show More