diff --git a/README.md b/README.md
index 256e42638..86c709b84 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,9 @@
and customizable experience. It prioritizes performance and player enjoyment while adhering to the core mechanics of the game.
-
+
+ Your browser does not support the video tag.
+
diff --git a/assets/pumpkin_chunk_loading.webm b/assets/pumpkin_chunk_loading.webm
new file mode 100644
index 000000000..91f189482
Binary files /dev/null and b/assets/pumpkin_chunk_loading.webm differ
diff --git a/assets/pumpkin_chunk_loading.webp b/assets/pumpkin_chunk_loading.webp
deleted file mode 100644
index a28912c55..000000000
Binary files a/assets/pumpkin_chunk_loading.webp and /dev/null differ
diff --git a/pumpkin-config/src/chunk.rs b/pumpkin-config/src/chunk.rs
index 8463ac0bd..0d24a9a3a 100644
--- a/pumpkin-config/src/chunk.rs
+++ b/pumpkin-config/src/chunk.rs
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
/// Configuration for chunk storage format.
///
/// Supports multiple chunk formats, currently `Anvil` and `Linear`.
-#[derive(Deserialize, Default, Serialize, Clone)]
+#[derive(Deserialize, Serialize, Clone)]
#[serde(tag = "type")]
pub enum ChunkConfig {
/// Standard Anvil chunk storage.
@@ -16,10 +16,15 @@ pub enum ChunkConfig {
Linear,
/// Pumpkin's own optimized world format.
#[serde(rename = "pump")]
- #[default]
Pump,
}
+impl Default for ChunkConfig {
+ fn default() -> Self {
+ Self::Anvil(AnvilChunkConfig::default())
+ }
+}
+
/// Configuration for Anvil chunk storage.
#[derive(Deserialize, Serialize, Default, Clone)]
#[serde(default)]
diff --git a/pumpkin-config/src/lib.rs b/pumpkin-config/src/lib.rs
index 57ce9cae5..9d87d7b79 100644
--- a/pumpkin-config/src/lib.rs
+++ b/pumpkin-config/src/lib.rs
@@ -5,7 +5,6 @@ use pumpkin_util::{Difficulty, GameMode, PermissionLvl, random};
use recipe::RecipeConfig;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
-use std::net::SocketAddr;
use std::path::PathBuf;
use std::{fs, num::NonZeroU8, path::Path};
use tracing::{debug, warn};
@@ -20,7 +19,9 @@ pub mod resource_pack;
pub use chat::ChatConfig;
pub use commands::CommandsConfig;
pub use networking::auth::AuthenticationConfig;
+pub use networking::bedrock::BedrockConfig;
pub use networking::compression::CompressionConfig;
+pub use networking::java::JavaConfig;
pub use networking::lan_broadcast::LANBroadcastConfig;
pub use networking::rcon::RCONConfig;
pub use plugins::PluginsConfig;
@@ -64,6 +65,48 @@ impl LoadConfiguration for PumpkinConfig {
fn validate(&self) {
self.basic.validate();
self.advanced.validate();
+
+ let min_vd = NonZeroU8::new(2).unwrap();
+ let max_vd = NonZeroU8::new(64).unwrap();
+
+ // Validate Java
+ assert!(
+ self.advanced.networking.java.view_distance >= min_vd,
+ "Java View distance must be at least 2"
+ );
+ assert!(
+ self.advanced.networking.java.view_distance <= max_vd,
+ "Java View distance must be less than 64"
+ );
+ if self.advanced.networking.java.online_mode {
+ assert!(
+ self.advanced.networking.java.encryption,
+ "When online mode is enabled, encryption must be enabled"
+ );
+ }
+
+ // Validate Bedrock
+ assert!(
+ self.advanced.networking.bedrock.view_distance >= min_vd,
+ "Bedrock View distance must be at least 2"
+ );
+ assert!(
+ self.advanced.networking.bedrock.view_distance <= max_vd,
+ "Bedrock View distance must be less than 64"
+ );
+ if self.advanced.networking.bedrock.online_mode {
+ assert!(
+ self.advanced.networking.bedrock.encryption,
+ "When online mode is enabled, bedrock_encryption must be enabled"
+ );
+ }
+
+ if self.basic.allow_chat_reports {
+ assert!(
+ self.advanced.networking.java.online_mode,
+ "When allow_chat_reports is enabled, java.online_mode must be enabled"
+ );
+ }
}
}
@@ -110,24 +153,8 @@ pub struct AdvancedConfiguration {
#[derive(Serialize, Deserialize)]
#[serde(default)]
pub struct BasicConfiguration {
- /// Whether Java Edition Clients are Accepted.
- pub java_edition: bool,
- /// The address and port to which the Java Edition server will bind.
- pub java_edition_address: SocketAddr,
- /// Whether Bedrock Edition Clients are Accepted.
- pub bedrock_edition: bool,
- /// Whether Bedrock Edition Clients are Accepted.
- pub bedrock_edition_address: SocketAddr,
- /// Whether packet encryption is enabled for Bedrock Edition.
- pub bedrock_encryption: bool,
/// The seed for the world generation.
pub seed: Seed,
- /// The maximum number of players allowed on the server. Specifying `0` disables the limit.
- pub max_players: u32,
- /// The maximum view distance for players.
- pub view_distance: NonZeroU8,
- /// The maximum simulated view distance.
- pub simulation_distance: NonZeroU8,
/// The default game difficulty.
pub default_difficulty: Difficulty,
/// The op level assigned by the /op command.
@@ -138,12 +165,6 @@ pub struct BasicConfiguration {
pub allow_end: bool,
/// Whether the server is in hardcore mode.
pub hardcore: bool,
- /// Whether online mode is enabled. Requires valid Minecraft accounts.
- pub online_mode: bool,
- /// Whether packet encryption is enabled. Required when online mode is enabled.
- pub encryption: bool,
- /// Message of the Day; the server's description displayed on the status screen.
- pub motd: String,
/// The server's ticks per second.
pub tps: f32,
/// The default gamemode for players.
@@ -169,23 +190,12 @@ pub struct BasicConfiguration {
impl Default for BasicConfiguration {
fn default() -> Self {
Self {
- java_edition: true,
- java_edition_address: "0.0.0.0:25565".parse().unwrap(),
- bedrock_edition: true,
- bedrock_edition_address: "0.0.0.0:19132".parse().unwrap(),
- bedrock_encryption: true,
seed: Seed(random::get_seed()),
- max_players: 1000,
- view_distance: NonZeroU8::new(16).unwrap(),
- simulation_distance: NonZeroU8::new(10).unwrap(),
default_difficulty: Difficulty::Normal,
op_permission_level: PermissionLvl::Four,
allow_nether: true,
allow_end: true,
hardcore: false,
- online_mode: true,
- encryption: true,
- motd: "A blazingly fast Pumpkin server!".to_string(),
tps: 20.0,
default_gamemode: GameMode::Survival,
force_gamemode: false,
@@ -207,35 +217,7 @@ impl BasicConfiguration {
PathBuf::from(&self.default_level_name)
}
- pub fn validate(&self) {
- let min = NonZeroU8::new(2).unwrap();
- let max = NonZeroU8::new(64).unwrap();
-
- assert!(
- self.view_distance.ge(&min),
- "View distance must be at least 2"
- );
- assert!(
- self.view_distance.le(&max),
- "View distance must be less than 64"
- );
- if self.online_mode {
- assert!(
- self.encryption,
- "When online mode is enabled, encryption must be enabled"
- );
- assert!(
- self.bedrock_encryption,
- "When online mode is enabled, bedrock_encryption must be enabled"
- );
- }
- if self.allow_chat_reports {
- assert!(
- self.online_mode,
- "When allow_chat_reports is enabled, online_mode must be enabled"
- );
- }
- }
+ pub const fn validate(&self) {}
}
impl AdvancedConfiguration {
diff --git a/pumpkin-config/src/networking/bedrock.rs b/pumpkin-config/src/networking/bedrock.rs
new file mode 100644
index 000000000..bd888f2cd
--- /dev/null
+++ b/pumpkin-config/src/networking/bedrock.rs
@@ -0,0 +1,72 @@
+use crate::CompressionConfig;
+use serde::{Deserialize, Serialize};
+use std::net::SocketAddr;
+use std::num::NonZeroU8;
+
+/// Configuration for Bedrock authentication.
+#[derive(Deserialize, Serialize, Clone)]
+#[serde(default)]
+pub struct BedrockAuthenticationConfig {
+ /// Whether Xbox Live authentication is enabled/enforced.
+ pub enabled: bool,
+ /// Optional custom authentication/discovery URL.
+ pub url: Option,
+ /// Connection timeout in milliseconds.
+ pub connect_timeout: u32,
+ /// Read timeout in milliseconds.
+ pub read_timeout: u32,
+}
+
+impl Default for BedrockAuthenticationConfig {
+ fn default() -> Self {
+ Self {
+ enabled: true,
+ url: None,
+ connect_timeout: 5000,
+ read_timeout: 5000,
+ }
+ }
+}
+
+/// Configuration for Bedrock Edition client connections.
+#[derive(Deserialize, Serialize, Clone)]
+#[serde(default)]
+pub struct BedrockConfig {
+ /// Whether Bedrock Edition Clients are Accepted.
+ pub enabled: bool,
+ /// Whether Bedrock Edition Clients are Accepted.
+ pub address: SocketAddr,
+ /// Whether packet encryption is enabled for Bedrock Edition.
+ pub encryption: bool,
+ /// Whether online mode is enabled.
+ pub online_mode: bool,
+ /// The maximum number of players allowed on the server. Specifying `0` disables the limit.
+ pub max_players: u32,
+ /// The maximum view distance for players.
+ pub view_distance: NonZeroU8,
+ /// The maximum simulated view distance.
+ pub simulation_distance: NonZeroU8,
+ /// Bedrock Edition packet compression settings.
+ pub compression: CompressionConfig,
+ /// Message of the Day; the server's description displayed on the status screen.
+ pub motd: String,
+ /// Bedrock Edition authentication settings.
+ pub authentication: BedrockAuthenticationConfig,
+}
+
+impl Default for BedrockConfig {
+ fn default() -> Self {
+ Self {
+ enabled: true,
+ address: "0.0.0.0:19132".parse().unwrap(),
+ encryption: true,
+ online_mode: true,
+ max_players: 1000,
+ view_distance: NonZeroU8::new(16).unwrap(),
+ simulation_distance: NonZeroU8::new(10).unwrap(),
+ compression: CompressionConfig::default(),
+ motd: "A blazingly fast Pumpkin server!".to_string(),
+ authentication: BedrockAuthenticationConfig::default(),
+ }
+ }
+}
diff --git a/pumpkin-config/src/networking/compression.rs b/pumpkin-config/src/networking/compression.rs
index 15dfc436f..7a8df596b 100644
--- a/pumpkin-config/src/networking/compression.rs
+++ b/pumpkin-config/src/networking/compression.rs
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
/// Configuration for packet compression.
///
/// Controls whether network packet compression is enabled and the compression parameters.
-#[derive(Deserialize, Serialize)]
+#[derive(Deserialize, Serialize, Clone)]
#[serde(default)]
pub struct CompressionConfig {
/// Whether compression is enabled.
diff --git a/pumpkin-config/src/networking/java.rs b/pumpkin-config/src/networking/java.rs
new file mode 100644
index 000000000..10f45264e
--- /dev/null
+++ b/pumpkin-config/src/networking/java.rs
@@ -0,0 +1,47 @@
+use crate::{AuthenticationConfig, CompressionConfig};
+use serde::{Deserialize, Serialize};
+use std::net::SocketAddr;
+use std::num::NonZeroU8;
+
+/// Configuration for Java Edition client connections.
+#[derive(Deserialize, Serialize, Clone)]
+#[serde(default)]
+pub struct JavaConfig {
+ /// Whether Java Edition Clients are Accepted.
+ pub enabled: bool,
+ /// The address and port to which the Java Edition server will bind.
+ pub address: SocketAddr,
+ /// Whether packet encryption is enabled. Required when online mode is enabled.
+ pub encryption: bool,
+ /// Whether online mode is enabled. Requires valid Minecraft accounts.
+ pub online_mode: bool,
+ /// The maximum number of players allowed on the server. Specifying `0` disables the limit.
+ pub max_players: u32,
+ /// The maximum view distance for players.
+ pub view_distance: NonZeroU8,
+ /// The maximum simulated view distance.
+ pub simulation_distance: NonZeroU8,
+ /// Java Edition packet compression settings.
+ pub compression: CompressionConfig,
+ /// Message of the Day; the server's description displayed on the status screen.
+ pub motd: String,
+ /// Authentication settings for client connections.
+ pub authentication: AuthenticationConfig,
+}
+
+impl Default for JavaConfig {
+ fn default() -> Self {
+ Self {
+ enabled: true,
+ address: "0.0.0.0:25565".parse().unwrap(),
+ encryption: true,
+ online_mode: true,
+ max_players: 1000,
+ view_distance: NonZeroU8::new(16).unwrap(),
+ simulation_distance: NonZeroU8::new(10).unwrap(),
+ compression: CompressionConfig::default(),
+ motd: "A blazingly fast Pumpkin server!".to_string(),
+ authentication: AuthenticationConfig::default(),
+ }
+ }
+}
diff --git a/pumpkin-config/src/networking/mod.rs b/pumpkin-config/src/networking/mod.rs
index 91e906c11..5da592ebf 100644
--- a/pumpkin-config/src/networking/mod.rs
+++ b/pumpkin-config/src/networking/mod.rs
@@ -1,13 +1,16 @@
-use auth::AuthenticationConfig;
use proxy::ProxyConfig;
use query::QueryConfig;
use rcon::RCONConfig;
use serde::{Deserialize, Serialize};
-use crate::{CompressionConfig, LANBroadcastConfig};
+use crate::LANBroadcastConfig;
+use bedrock::BedrockConfig;
+use java::JavaConfig;
pub mod auth;
+pub mod bedrock;
pub mod compression;
+pub mod java;
pub mod lan_broadcast;
pub mod proxy;
pub mod query;
@@ -18,19 +21,18 @@ pub mod rcon;
/// Covers authentication, query, RCON, proxying, packet compression,
/// and LAN broadcast behaviour.
#[derive(Deserialize, Serialize, Default)]
+#[serde(default)]
pub struct NetworkingConfig {
- /// Authentication settings for client connections.
- pub authentication: AuthenticationConfig,
/// Query protocol settings for server status requests.
pub query: QueryConfig,
/// RCON (remote console) configuration.
pub rcon: RCONConfig,
/// Proxy-related networking settings.
pub proxy: ProxyConfig,
- /// Java Edition packet compression settings.
- pub java_compression: CompressionConfig,
- /// Bedrock Edition packet compression settings.
- pub bedrock_compression: CompressionConfig,
/// LAN broadcast settings.
pub lan_broadcast: LANBroadcastConfig,
+ /// Java Edition configuration settings.
+ pub java: JavaConfig,
+ /// Bedrock Edition configuration settings.
+ pub bedrock: BedrockConfig,
}
diff --git a/pumpkin-protocol/src/bedrock/client/raknet/unconnected_pong.rs b/pumpkin-protocol/src/bedrock/client/raknet/unconnected_pong.rs
index ae88063dd..a8ad14f5e 100644
--- a/pumpkin-protocol/src/bedrock/client/raknet/unconnected_pong.rs
+++ b/pumpkin-protocol/src/bedrock/client/raknet/unconnected_pong.rs
@@ -26,7 +26,7 @@ impl PacketWrite for CUnconnectedPong {
pub struct ServerInfo {
/// (BE or MCEE for Education Edition)
pub edition: &'static str,
- pub motd_line_1: &'static str,
+ pub motd_line_1: String,
pub protocol_version: u32,
pub version_name: &'static str,
pub player_count: i32,
diff --git a/pumpkin-protocol/src/bedrock/client/set_actor_data.rs b/pumpkin-protocol/src/bedrock/client/set_actor_data.rs
index c96a20143..1e1c69980 100644
--- a/pumpkin-protocol/src/bedrock/client/set_actor_data.rs
+++ b/pumpkin-protocol/src/bedrock/client/set_actor_data.rs
@@ -85,6 +85,7 @@ impl PacketWrite for EntityMetadata {
}
}
+#[derive(Clone)]
pub enum MetadataValue {
Byte(i8),
Short(i16),
diff --git a/pumpkin-protocol/src/bedrock/network_item.rs b/pumpkin-protocol/src/bedrock/network_item.rs
index 596dcac52..57208f125 100644
--- a/pumpkin-protocol/src/bedrock/network_item.rs
+++ b/pumpkin-protocol/src/bedrock/network_item.rs
@@ -3,7 +3,7 @@ use std::num::NonZeroI32;
use pumpkin_data::item::{BedrockItem, JavaToBedrockItemMapping};
use pumpkin_data::item_stack::ItemStack;
-use pumpkin_nbt::Nbt;
+use pumpkin_nbt::{Nbt, deserializer::NbtReadHelperBedrock};
use crate::{
codec::{var_int::VarInt, var_uint::VarUInt},
@@ -54,12 +54,45 @@ impl PacketRead for NetworkItemDescriptor {
let mut user_data = vec![0u8; user_data_len as usize];
buf.read_exact(&mut user_data)?;
+ let mut nbt_data = Nbt::default();
+ let mut place_on_blocks = Vec::new();
+ let mut destroy_blocks = Vec::new();
+ let mut shield_blocking_tick = 0;
+
+ if !user_data.is_empty() {
+ let mut cursor = std::io::Cursor::new(user_data);
+ let nbt_version = i16::read(&mut cursor)?;
+ if nbt_version == -1 {
+ let _version = i8::read(&mut cursor)?;
+ let mut nbt_reader = NbtReadHelperBedrock::new(&mut cursor);
+ nbt_data = Nbt::read(&mut nbt_reader)
+ .map_err(|e| Error::new(std::io::ErrorKind::InvalidData, e))?;
+ }
+
+ let place_on_len = VarUInt::read(&mut cursor)?.0;
+ for _ in 0..place_on_len {
+ place_on_blocks.push(String::read(&mut cursor)?);
+ }
+
+ let destroy_len = VarUInt::read(&mut cursor)?.0;
+ for _ in 0..destroy_len {
+ destroy_blocks.push(String::read(&mut cursor)?);
+ }
+
+ if id.0 == (BedrockItem::SHIELD.id as i32) {
+ shield_blocking_tick = i64::read(&mut cursor)?;
+ }
+ }
+
Ok(Self {
id,
stack_size,
aux_value,
block_runtime_id,
- ..Default::default()
+ nbt_data,
+ place_on_blocks,
+ destroy_blocks,
+ shield_blocking_tick,
})
}
}
@@ -217,13 +250,46 @@ impl PacketRead for ItemStackWrapper {
let mut user_data = vec![0u8; user_data_len as usize];
buf.read_exact(&mut user_data)?;
+ let mut nbt_data = Nbt::default();
+ let mut place_on_blocks = Vec::new();
+ let mut destroy_blocks = Vec::new();
+ let mut shield_blocking_tick = 0;
+
+ if !user_data.is_empty() {
+ let mut cursor = std::io::Cursor::new(user_data);
+ let nbt_version = i16::read(&mut cursor)?;
+ if nbt_version == -1 {
+ let _version = i8::read(&mut cursor)?;
+ let mut nbt_reader = NbtReadHelperBedrock::new(&mut cursor);
+ nbt_data = Nbt::read(&mut nbt_reader)
+ .map_err(|e| Error::new(std::io::ErrorKind::InvalidData, e))?;
+ }
+
+ let place_on_len = VarUInt::read(&mut cursor)?.0;
+ for _ in 0..place_on_len {
+ place_on_blocks.push(String::read(&mut cursor)?);
+ }
+
+ let destroy_len = VarUInt::read(&mut cursor)?.0;
+ for _ in 0..destroy_len {
+ destroy_blocks.push(String::read(&mut cursor)?);
+ }
+
+ if id == (BedrockItem::SHIELD.id) {
+ shield_blocking_tick = i64::read(&mut cursor)?;
+ }
+ }
+
Ok(Self {
id,
stack_size,
aux_value,
block_runtime_id,
+ nbt_data,
+ place_on_blocks,
+ destroy_blocks,
+ shield_blocking_tick,
net_id,
- ..Default::default()
})
}
}
diff --git a/pumpkin-protocol/src/java/client/play/entity_metadata.rs b/pumpkin-protocol/src/java/client/play/entity_metadata.rs
index 4e68e3150..dcd8afecf 100644
--- a/pumpkin-protocol/src/java/client/play/entity_metadata.rs
+++ b/pumpkin-protocol/src/java/client/play/entity_metadata.rs
@@ -58,7 +58,7 @@ impl ClientPacket for CSetEntityMetadata {
pub struct Metadata {
pub index: TrackedId,
pub r#type: MetaDataType,
- value: T,
+ pub value: T,
}
impl Metadata {
diff --git a/pumpkin-util/src/jwt/mod.rs b/pumpkin-util/src/jwt/mod.rs
index 53bd20964..7b198bdf5 100644
--- a/pumpkin-util/src/jwt/mod.rs
+++ b/pumpkin-util/src/jwt/mod.rs
@@ -227,8 +227,24 @@ pub const OIDC_AUDIENCE: &str = "api://auth-minecraft-services/multiplayer";
pub const OIDC_DISCOVERY_URL: &str =
"https://client.discovery.minecraft-services.net/api/v1.0/discovery/MinecraftPE/builds/1.0.0.0";
-pub fn fetch_oidc_jwks() -> Result<(String, Jwks), AuthError> {
- let discovery: Value = ureq::get(OIDC_DISCOVERY_URL)
+pub fn fetch_oidc_jwks(
+ discovery_url: Option<&str>,
+ connect_timeout_ms: u32,
+ read_timeout_ms: u32,
+) -> Result<(String, Jwks), AuthError> {
+ let url = discovery_url.unwrap_or(OIDC_DISCOVERY_URL);
+ let config = ureq::Agent::config_builder()
+ .timeout_connect(Some(std::time::Duration::from_millis(
+ connect_timeout_ms as u64,
+ )))
+ .timeout_recv_response(Some(std::time::Duration::from_millis(
+ read_timeout_ms as u64,
+ )))
+ .build();
+ let agent: ureq::Agent = config.into();
+
+ let discovery: Value = agent
+ .get(url)
.call()
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?
.body_mut()
@@ -248,7 +264,8 @@ pub fn fetch_oidc_jwks() -> Result<(String, Jwks), AuthError> {
.ok_or_else(|| AuthError::PublicKeyBuild("Discovery missing serviceUri".into()))?;
let openid_config_url = format!("{service_uri}/.well-known/openid-configuration");
- let openid_config: Value = ureq::get(&openid_config_url)
+ let openid_config: Value = agent
+ .get(&openid_config_url)
.call()
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?
.body_mut()
@@ -269,7 +286,8 @@ pub fn fetch_oidc_jwks() -> Result<(String, Jwks), AuthError> {
.ok_or_else(|| AuthError::PublicKeyBuild("OpenID config missing issuer".into()))?
.to_string();
- let jwks: Jwks = ureq::get(jwks_uri)
+ let jwks: Jwks = agent
+ .get(jwks_uri)
.call()
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?
.body_mut()
diff --git a/pumpkin-world/benches/chunk_gen.rs b/pumpkin-world/benches/chunk_gen.rs
index 6c02ac669..dc5af0433 100644
--- a/pumpkin-world/benches/chunk_gen.rs
+++ b/pumpkin-world/benches/chunk_gen.rs
@@ -5,7 +5,7 @@ use pumpkin_data::dimension::Dimension;
use pumpkin_util::world_seed::Seed;
use pumpkin_world::ProtoChunk;
use pumpkin_world::chunk_system::{Cache, Chunk, StagedChunkEnum, generate_single_chunk};
-use pumpkin_world::generation::generator::VanillaGenerator;
+use pumpkin_world::generation::generator::WorldGenerator;
use pumpkin_world::generation::get_world_gen;
use pumpkin_world::world::WorldPortalExt;
use std::hint::black_box;
@@ -13,7 +13,6 @@ use std::sync::Arc;
const SEED: Seed = Seed(42);
-// Stub portal — allows all block placements, skips mob spawning.
struct BlockRegistry;
impl WorldPortalExt for BlockRegistry {
fn can_place_at(
@@ -54,11 +53,13 @@ impl WorldPortalExt for BlockRegistry {
}
}
-/// Build a cache containing chunk 0, 0 at the center so that features
-/// and lighting stages get a 3×3 cache.
+fn make_world_gen() -> Box {
+ get_world_gen(SEED, Dimension::OVERWORLD, false, Vec::new(), String::new())
+}
+
fn setup_cache(
target_stage: StagedChunkEnum,
- world_gen: &VanillaGenerator,
+ world_gen: &WorldGenerator,
block_registry: &dyn WorldPortalExt,
) -> Cache {
let radius = target_stage.get_direct_radius();
@@ -97,10 +98,9 @@ fn setup_cache(
cache
}
-/// Full pipeline
fn bench_full_chunk_generation(c: &mut Criterion) {
let dimension = Dimension::OVERWORLD;
- let world_gen = get_world_gen(SEED, dimension.clone());
+ let world_gen = make_world_gen();
let block_registry = Arc::new(BlockRegistry);
c.bench_function("full_chunk_generation", |b| {
@@ -118,9 +118,8 @@ fn bench_full_chunk_generation(c: &mut Criterion) {
});
}
-/// Per-phase benchmarks
fn bench_biomes_generation(c: &mut Criterion) {
- let world_gen = get_world_gen(SEED, Dimension::OVERWORLD);
+ let world_gen = make_world_gen();
let block_registry = Arc::new(BlockRegistry);
c.bench_function("biomes_generation", |b| {
@@ -141,7 +140,7 @@ fn bench_biomes_generation(c: &mut Criterion) {
}
fn bench_structure_starts_generation(c: &mut Criterion) {
- let world_gen = get_world_gen(SEED, Dimension::OVERWORLD);
+ let world_gen = make_world_gen();
let block_registry = Arc::new(BlockRegistry);
c.bench_function("structure_starts_generation", |b| {
@@ -168,7 +167,7 @@ fn bench_structure_starts_generation(c: &mut Criterion) {
}
fn bench_structure_references_generation(c: &mut Criterion) {
- let world_gen = get_world_gen(SEED, Dimension::OVERWORLD);
+ let world_gen = make_world_gen();
let block_registry = Arc::new(BlockRegistry);
c.bench_function("structure_references_generation", |b| {
@@ -195,7 +194,7 @@ fn bench_structure_references_generation(c: &mut Criterion) {
}
fn bench_noise_generation(c: &mut Criterion) {
- let world_gen = get_world_gen(SEED, Dimension::OVERWORLD);
+ let world_gen = make_world_gen();
let block_registry = Arc::new(BlockRegistry);
c.bench_function("noise_generation", |b| {
@@ -216,7 +215,7 @@ fn bench_noise_generation(c: &mut Criterion) {
}
fn bench_surface_generation(c: &mut Criterion) {
- let world_gen = get_world_gen(SEED, Dimension::OVERWORLD);
+ let world_gen = make_world_gen();
let block_registry = Arc::new(BlockRegistry);
c.bench_function("surface_generation", |b| {
@@ -243,7 +242,7 @@ fn bench_surface_generation(c: &mut Criterion) {
}
fn bench_carvers_generation(c: &mut Criterion) {
- let world_gen = get_world_gen(SEED, Dimension::OVERWORLD);
+ let world_gen = make_world_gen();
let block_registry = Arc::new(BlockRegistry);
c.bench_function("carvers_generation", |b| {
@@ -270,7 +269,7 @@ fn bench_carvers_generation(c: &mut Criterion) {
}
fn bench_features_generation(c: &mut Criterion) {
- let world_gen = get_world_gen(SEED, Dimension::OVERWORLD);
+ let world_gen = make_world_gen();
let block_registry = Arc::new(BlockRegistry);
c.bench_function("features_generation", |b| {
@@ -297,7 +296,7 @@ fn bench_features_generation(c: &mut Criterion) {
}
fn bench_lighting_generation(c: &mut Criterion) {
- let world_gen = get_world_gen(SEED, Dimension::OVERWORLD);
+ let world_gen = make_world_gen();
let block_registry = Arc::new(BlockRegistry);
c.bench_function("lighting_generation", |b| {
diff --git a/pumpkin-world/benches/chunk_gen_concurrent.rs b/pumpkin-world/benches/chunk_gen_concurrent.rs
index 473b4ce9a..e913f186e 100644
--- a/pumpkin-world/benches/chunk_gen_concurrent.rs
+++ b/pumpkin-world/benches/chunk_gen_concurrent.rs
@@ -63,7 +63,13 @@ fn bench_concurrent_chunk_generation(c: &mut Criterion) {
.build()
.expect("Failed to build rayon thread pool");
- let world_gen = Arc::new(get_world_gen(SEED, Dimension::OVERWORLD));
+ let world_gen = Arc::new(get_world_gen(
+ SEED,
+ Dimension::OVERWORLD,
+ false,
+ Vec::new(),
+ String::new(),
+ ));
let block_registry = Arc::new(BlockRegistry);
let bench_name = format!(
diff --git a/pumpkin-world/src/biome/mod.rs b/pumpkin-world/src/biome/mod.rs
index 26683c9fe..d17bea7d9 100644
--- a/pumpkin-world/src/biome/mod.rs
+++ b/pumpkin-world/src/biome/mod.rs
@@ -79,7 +79,7 @@ mod test {
#[test]
fn wide_area_surface() {
- use crate::generation::generator::{GeneratorInit, VanillaGenerator};
+ use crate::generation::generator::{GeneratorInit, VanillaGenerator, WorldGenerator};
use crate::generation::noise::router::multi_noise_sampler::{
MultiNoiseSampler, MultiNoiseSamplerBuilderOptions,
};
@@ -96,13 +96,19 @@ mod test {
read_data_from_file!("../../assets/biome_no_blend_no_beard_0.json");
let seed = 0;
- let generator = VanillaGenerator::new(Seed(seed as u64), Dimension::OVERWORLD);
+ let world_gen = WorldGenerator::Noise(Box::new(VanillaGenerator::new(
+ Seed(seed as u64),
+ Dimension::OVERWORLD,
+ )));
+ let WorldGenerator::Noise(generator) = &world_gen else {
+ unreachable!()
+ };
for data in expected_data {
let chunk_x = data.x;
let chunk_z = data.z;
- let mut chunk = ProtoChunk::new(chunk_x, chunk_z, &generator);
+ let mut chunk = ProtoChunk::new(chunk_x, chunk_z, &world_gen);
// Create MultiNoiseSampler for populate_biomes
diff --git a/pumpkin-world/src/biome/multi_noise.rs b/pumpkin-world/src/biome/multi_noise.rs
index ca85253ae..26989fb68 100644
--- a/pumpkin-world/src/biome/multi_noise.rs
+++ b/pumpkin-world/src/biome/multi_noise.rs
@@ -45,7 +45,7 @@ mod test {
#[test]
fn sample_value() {
- use crate::generation::generator::{GeneratorInit, VanillaGenerator};
+ use crate::generation::generator::{GeneratorInit, VanillaGenerator, WorldGenerator};
use crate::generation::noise::router::multi_noise_sampler::{
MultiNoiseSampler, MultiNoiseSamplerBuilderOptions,
};
@@ -59,9 +59,16 @@ mod test {
let chunk_x = 0;
let chunk_z = 0;
- let generator = VanillaGenerator::new(Seed(seed as u64), Dimension::OVERWORLD);
+ let generator = Box::new(VanillaGenerator::new(
+ Seed(seed as u64),
+ Dimension::OVERWORLD,
+ ));
+ let world_gen = WorldGenerator::Noise(generator);
+ let WorldGenerator::Noise(generator) = &world_gen else {
+ unreachable!()
+ };
- let _chunk = ProtoChunk::new(chunk_x, chunk_z, &generator);
+ let _chunk = ProtoChunk::new(chunk_x, chunk_z, &world_gen);
let start_x = chunk_pos::start_block_x(chunk_x);
let start_z = chunk_pos::start_block_z(chunk_z);
diff --git a/pumpkin-world/src/chunk/format/anvil.rs b/pumpkin-world/src/chunk/format/anvil.rs
index 21cebd50b..e126eda68 100644
--- a/pumpkin-world/src/chunk/format/anvil.rs
+++ b/pumpkin-world/src/chunk/format/anvil.rs
@@ -922,7 +922,7 @@ mod tests {
let _ = env_logger::try_init();
- let generator = get_world_gen(Seed(0), Dimension::Overworld);
+ let generator = get_world_gen(Seed(0), Dimension::Overworld, false, Vec::new(), String::new());
let temp_dir = TempDir::new().unwrap();
let level_folder = LevelFolder {
@@ -1198,7 +1198,7 @@ mod tests {
let _ = env_logger::try_init();
- let generator = get_world_gen(Seed(0), Dimension::Overworld);
+ let generator = get_world_gen(Seed(0), Dimension::Overworld, false, Vec::new(), String::new());
let temp_dir = TempDir::new().unwrap();
let level_folder = LevelFolder {
diff --git a/pumpkin-world/src/chunk/format/mod.rs b/pumpkin-world/src/chunk/format/mod.rs
index 04b79195f..c76ca16c8 100644
--- a/pumpkin-world/src/chunk/format/mod.rs
+++ b/pumpkin-world/src/chunk/format/mod.rs
@@ -78,9 +78,16 @@ impl ChunkData {
chunk_data: &[u8],
position: Vector2,
) -> Result {
- let chunk_data =
+ let is_named = chunk_data.len() >= 3
+ && chunk_data[0] == 0x0a
+ && chunk_data[1] == 0x00
+ && chunk_data[2] == 0x00;
+ let chunk_data = if is_named {
+ pumpkin_nbt::from_bytes::(std::io::Cursor::new(chunk_data))
+ } else {
pumpkin_nbt::from_bytes_unnamed::(std::io::Cursor::new(chunk_data))
- .map_err(|e| ChunkParsingError::ErrorDeserializingChunk(e.to_string()))?;
+ }
+ .map_err(|e| ChunkParsingError::ErrorDeserializingChunk(e.to_string()))?;
if chunk_data.x_pos != position.x || chunk_data.z_pos != position.y {
return Err(ChunkParsingError::ErrorDeserializingChunk(format!(
@@ -222,7 +229,7 @@ impl ChunkData {
};
let mut result = Vec::new();
- pumpkin_nbt::to_bytes_unnamed(&nbt_ref, &mut result)
+ pumpkin_nbt::to_bytes(&nbt_ref, &mut result)
.map_err(ChunkSerializingError::ErrorSerializingChunk)?;
Ok(result.into())
@@ -272,9 +279,16 @@ impl ChunkEntityData {
chunk_data: &[u8],
position: Vector2,
) -> Result {
- let chunk_entity_data =
+ let is_named = chunk_data.len() >= 3
+ && chunk_data[0] == 0x0a
+ && chunk_data[1] == 0x00
+ && chunk_data[2] == 0x00;
+ let chunk_entity_data = if is_named {
+ pumpkin_nbt::from_bytes::(std::io::Cursor::new(chunk_data))
+ } else {
pumpkin_nbt::from_bytes_unnamed::(std::io::Cursor::new(chunk_data))
- .map_err(|e| ChunkParsingError::ErrorDeserializingChunk(e.to_string()))?;
+ }
+ .map_err(|e| ChunkParsingError::ErrorDeserializingChunk(e.to_string()))?;
if chunk_entity_data.position[0] != position.x
|| chunk_entity_data.position[1] != position.y
@@ -304,7 +318,7 @@ impl ChunkEntityData {
};
let mut result = Vec::new();
- pumpkin_nbt::to_bytes_unnamed(&nbt, &mut result)
+ pumpkin_nbt::to_bytes(&nbt, &mut result)
.map_err(ChunkSerializingError::ErrorSerializingChunk)?;
Ok(result.into())
}
@@ -476,12 +490,13 @@ struct ChunkNbt {
status: ChunkStatus,
#[serde(rename = "sections")]
sections: Vec,
+ #[serde(default)]
heightmaps: ChunkHeightmaps,
- #[serde(rename = "block_ticks")]
+ #[serde(rename = "block_ticks", default)]
block_ticks: Vec>,
- #[serde(rename = "fluid_ticks")]
+ #[serde(rename = "fluid_ticks", default)]
fluid_ticks: Vec>,
- #[serde(rename = "block_entities")]
+ #[serde(rename = "block_entities", default)]
block_entities: Vec,
#[serde(rename = "isLightOn", default)]
light_correct: bool,
diff --git a/pumpkin-world/src/chunk_system/generation.rs b/pumpkin-world/src/chunk_system/generation.rs
index b779000a3..ae0cc9218 100644
--- a/pumpkin-world/src/chunk_system/generation.rs
+++ b/pumpkin-world/src/chunk_system/generation.rs
@@ -1,7 +1,7 @@
use pumpkin_data::dimension::Dimension;
use crate::ProtoChunk;
-use crate::generation::generator::VanillaGenerator;
+use crate::generation::generator::WorldGenerator;
use crate::world::WorldPortalExt;
use pumpkin_config::lighting::LightingEngineConfig;
@@ -10,7 +10,7 @@ use super::{Cache, Chunk, StagedChunkEnum};
pub fn generate_single_chunk(
_dimension: &Dimension,
_biome_mixer_seed: i64,
- generator: &VanillaGenerator,
+ generator: &WorldGenerator,
block_registry: &dyn WorldPortalExt,
chunk_x: i32,
chunk_z: i32,
@@ -117,13 +117,13 @@ mod tests {
let dimension = Dimension::OVERWORLD;
let seed = Seed(42);
let block_registry = Arc::new(BlockRegistry);
- let world_gen = get_world_gen(seed, dimension.clone());
- let biome_mixer_seed = hash_seed(world_gen.random_config.seed);
+ let world_gen = get_world_gen(seed, dimension.clone(), false, Vec::new(), String::new());
+ let biome_mixer_seed = hash_seed(world_gen.seed());
let _ = generate_single_chunk(
&dimension,
biome_mixer_seed,
- &world_gen,
+ &*world_gen,
block_registry.as_ref(),
0,
0,
@@ -136,13 +136,13 @@ mod tests {
let dimension = Dimension::OVERWORLD;
let seed = Seed(1_782_124_772_053_846_960);
let block_registry = Arc::new(BlockRegistry);
- let world_gen = get_world_gen(seed, dimension.clone());
- let biome_mixer_seed = hash_seed(world_gen.random_config.seed);
+ let world_gen = get_world_gen(seed, dimension.clone(), false, Vec::new(), String::new());
+ let biome_mixer_seed = hash_seed(world_gen.seed());
let chunk = generate_single_chunk(
&dimension,
biome_mixer_seed,
- &world_gen,
+ &*world_gen,
block_registry.as_ref(),
31,
-12,
diff --git a/pumpkin-world/src/chunk_system/generation_cache.rs b/pumpkin-world/src/chunk_system/generation_cache.rs
index ac797f024..acf1f200f 100644
--- a/pumpkin-world/src/chunk_system/generation_cache.rs
+++ b/pumpkin-world/src/chunk_system/generation_cache.rs
@@ -350,7 +350,7 @@ impl Cache {
pub fn advance(
&mut self,
stage: StagedChunkEnum,
- generator: &generator::VanillaGenerator,
+ generator: &generator::WorldGenerator,
block_registry: &dyn WorldPortalExt,
lighting_config: &LightingEngineConfig,
) {
@@ -362,35 +362,74 @@ impl Cache {
}
match stage {
StagedChunkEnum::Empty => panic!("empty stage"),
- StagedChunkEnum::StructureStart => {
- self.chunks[mid]
- .get_proto_chunk_mut()
- .set_structure_starts(generator);
- }
- StagedChunkEnum::StructureReferences => {
- self.chunks[mid]
- .get_proto_chunk_mut()
- .set_structure_references(generator);
- }
- StagedChunkEnum::Biomes => self.chunks[mid]
- .get_proto_chunk_mut()
- .step_to_biomes(generator),
- StagedChunkEnum::Noise => self.chunks[mid]
- .get_proto_chunk_mut()
- .step_to_noise(generator),
- StagedChunkEnum::Surface => self.chunks[mid]
- .get_proto_chunk_mut()
- .step_to_surface(generator),
- StagedChunkEnum::Carvers => self.chunks[mid]
- .get_proto_chunk_mut()
- .step_to_carvers(generator),
- StagedChunkEnum::Features => {
- ProtoChunk::generate_features_and_structure(
- self,
- block_registry,
- &generator.random_config,
- );
- }
+ StagedChunkEnum::StructureStart => match generator {
+ generator::WorldGenerator::Noise(noise_gen) => {
+ self.chunks[mid]
+ .get_proto_chunk_mut()
+ .set_structure_starts(noise_gen);
+ }
+ generator::WorldGenerator::Flat(_) => {}
+ },
+ StagedChunkEnum::StructureReferences => match generator {
+ generator::WorldGenerator::Noise(noise_gen) => {
+ self.chunks[mid]
+ .get_proto_chunk_mut()
+ .set_structure_references(noise_gen);
+ }
+ generator::WorldGenerator::Flat(_) => {}
+ },
+ StagedChunkEnum::Biomes => match generator {
+ generator::WorldGenerator::Noise(noise_gen) => {
+ self.chunks[mid]
+ .get_proto_chunk_mut()
+ .step_to_biomes(noise_gen);
+ }
+ generator::WorldGenerator::Flat(flat_gen) => {
+ flat_gen.step_to_biomes(self.chunks[mid].get_proto_chunk_mut());
+ }
+ },
+ StagedChunkEnum::Noise => match generator {
+ generator::WorldGenerator::Noise(noise_gen) => {
+ self.chunks[mid]
+ .get_proto_chunk_mut()
+ .step_to_noise(noise_gen);
+ }
+ generator::WorldGenerator::Flat(flat_gen) => {
+ flat_gen.step_to_noise(self.chunks[mid].get_proto_chunk_mut());
+ }
+ },
+ StagedChunkEnum::Surface => match generator {
+ generator::WorldGenerator::Noise(noise_gen) => {
+ self.chunks[mid]
+ .get_proto_chunk_mut()
+ .step_to_surface(noise_gen);
+ }
+ generator::WorldGenerator::Flat(flat_gen) => {
+ flat_gen.step_to_surface(self.chunks[mid].get_proto_chunk_mut());
+ }
+ },
+ StagedChunkEnum::Carvers => match generator {
+ generator::WorldGenerator::Noise(noise_gen) => {
+ self.chunks[mid]
+ .get_proto_chunk_mut()
+ .step_to_carvers(noise_gen);
+ }
+ generator::WorldGenerator::Flat(flat_gen) => {
+ flat_gen.step_to_carvers(self.chunks[mid].get_proto_chunk_mut());
+ }
+ },
+ StagedChunkEnum::Features => match generator {
+ generator::WorldGenerator::Noise(noise_gen) => {
+ ProtoChunk::generate_features_and_structure(
+ self,
+ block_registry,
+ &noise_gen.random_config,
+ );
+ }
+ generator::WorldGenerator::Flat(_) => {
+ self.chunks[mid].get_proto_chunk_mut().stage = StagedChunkEnum::Features;
+ }
+ },
StagedChunkEnum::Lighting => {
let mut engine = crate::lighting::LightEngine::new();
engine.initialize_light(self, lighting_config);
@@ -410,7 +449,7 @@ impl Cache {
let chunk = self.chunks[mid].get_proto_chunk_mut();
debug_assert_eq!(chunk.stage, StagedChunkEnum::Spawn);
chunk.stage = StagedChunkEnum::Full;
- self.chunks[mid].upgrade_to_level_chunk(&generator.dimension, lighting_config);
+ self.chunks[mid].upgrade_to_level_chunk(generator.dimension(), lighting_config);
}
StagedChunkEnum::None => {}
}
diff --git a/pumpkin-world/src/chunk_system/schedule.rs b/pumpkin-world/src/chunk_system/schedule.rs
index ea90f0c54..b724e5be9 100644
--- a/pumpkin-world/src/chunk_system/schedule.rs
+++ b/pumpkin-world/src/chunk_system/schedule.rs
@@ -1272,7 +1272,7 @@ impl GenerationSchedule {
let send_chunk = self.send_chunk.clone();
let level = level.clone();
let settings =
- GenerationSettings::from_dimension(&level.world_gen.dimension);
+ GenerationSettings::from_dimension(level.world_gen.dimension());
pool.spawn(move || {
let result = crate::chunk_system::worker_logic::run_generation(
diff --git a/pumpkin-world/src/chunk_system/worker_logic.rs b/pumpkin-world/src/chunk_system/worker_logic.rs
index a7060cc10..b7e0a9781 100644
--- a/pumpkin-world/src/chunk_system/worker_logic.rs
+++ b/pumpkin-world/src/chunk_system/worker_logic.rs
@@ -179,7 +179,10 @@ pub async fn io_write_work(recv: AsyncRx>, level: Arc vec.push((pos, chunk)),
Chunk::Proto(chunk) => {
let mut temp = Chunk::Proto(chunk);
- temp.upgrade_to_level_chunk(&level.world_gen.dimension, &level.lighting_config);
+ temp.upgrade_to_level_chunk(
+ level.world_gen.dimension(),
+ &level.lighting_config,
+ );
let Chunk::Level(chunk) = temp else { panic!() };
vec.push((pos, chunk));
}
@@ -262,7 +265,7 @@ pub fn generation_work(
send: &crossfire::compat::MTx<(ChunkPos, RecvChunk)>,
level: &Arc,
) {
- let settings = GenerationSettings::from_dimension(&level.world_gen.dimension);
+ let settings = GenerationSettings::from_dimension(level.world_gen.dimension());
loop {
let Ok((pos, cache, stage)) = recv.recv() else {
diff --git a/pumpkin-world/src/generation/carver/mod.rs b/pumpkin-world/src/generation/carver/mod.rs
index 7752058d8..a001b3f7f 100644
--- a/pumpkin-world/src/generation/carver/mod.rs
+++ b/pumpkin-world/src/generation/carver/mod.rs
@@ -365,11 +365,14 @@ fn with_carve_run_options(
) where
F: FnOnce(&mut CarveRun<'_, '_>),
{
- use crate::generation::generator::{GeneratorInit, VanillaGenerator};
+ use crate::generation::generator::{GeneratorInit, VanillaGenerator, WorldGenerator};
use pumpkin_util::world_seed::Seed;
- let generator = VanillaGenerator::new(Seed(42), dimension);
- let mut chunk = ProtoChunk::new(0, 0, &generator);
+ let world_gen = WorldGenerator::Noise(Box::new(VanillaGenerator::new(Seed(42), dimension)));
+ let WorldGenerator::Noise(generator) = &world_gen else {
+ unreachable!()
+ };
+ let mut chunk = ProtoChunk::new(0, 0, &world_gen);
let start_x = crate::generation::positions::chunk_pos::start_block_x(chunk.x);
let start_z = crate::generation::positions::chunk_pos::start_block_z(chunk.z);
diff --git a/pumpkin-world/src/generation/generator/flat.rs b/pumpkin-world/src/generation/generator/flat.rs
new file mode 100644
index 000000000..37c1263cb
--- /dev/null
+++ b/pumpkin-world/src/generation/generator/flat.rs
@@ -0,0 +1,70 @@
+use super::FlatLayer;
+use crate::chunk_system::StagedChunkEnum;
+use crate::generation::Seed;
+use crate::generation::positions::chunk_pos::{start_block_x, start_block_z};
+use crate::generation::proto_chunk::ProtoChunk;
+use pumpkin_data::Block;
+use pumpkin_data::dimension::Dimension;
+
+pub struct FlatGenerator {
+ pub seed: u64,
+ pub dimension: Dimension,
+ pub layers: Vec,
+ pub biome: String,
+}
+
+impl FlatGenerator {
+ #[must_use]
+ pub const fn new(
+ seed: Seed,
+ dimension: Dimension,
+ layers: Vec,
+ biome: String,
+ ) -> Self {
+ Self {
+ seed: seed.0,
+ dimension,
+ layers,
+ biome,
+ }
+ }
+
+ pub fn step_to_biomes(&self, chunk: &mut ProtoChunk) {
+ let clean_biome = self.biome.strip_prefix("minecraft:").unwrap_or(&self.biome);
+ let biome_id = pumpkin_data::chunk::Biome::from_name(clean_biome)
+ .map_or(pumpkin_data::chunk::Biome::PLAINS.id, |b| b.id);
+ chunk.flat_biome_map.fill(biome_id);
+ chunk.stage = StagedChunkEnum::Biomes;
+ }
+
+ pub fn step_to_noise(&self, chunk: &mut ProtoChunk) {
+ let start_x = start_block_x(chunk.x);
+ let start_z = start_block_z(chunk.z);
+ for x in 0..16 {
+ for z in 0..16 {
+ let mut current_y = chunk.bottom_y() as i32;
+ for layer in &self.layers {
+ let block = Block::from_name(&layer.block);
+ let state = block.map_or(Block::AIR.default_state, |b| b.default_state);
+ for _ in 0..layer.height {
+ if current_y < chunk.bottom_y() as i32 + chunk.height() as i32 {
+ chunk.set_block_state(start_x + x, current_y, start_z + z, state);
+ current_y += 1;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ }
+ chunk.stage = StagedChunkEnum::Noise;
+ }
+
+ pub const fn step_to_surface(&self, chunk: &mut ProtoChunk) {
+ chunk.stage = StagedChunkEnum::Surface;
+ }
+
+ pub const fn step_to_carvers(&self, chunk: &mut ProtoChunk) {
+ chunk.stage = StagedChunkEnum::Carvers;
+ }
+}
diff --git a/pumpkin-world/src/generation/generator/mod.rs b/pumpkin-world/src/generation/generator/mod.rs
index 669d2b596..dc4ec55cb 100644
--- a/pumpkin-world/src/generation/generator/mod.rs
+++ b/pumpkin-world/src/generation/generator/mod.rs
@@ -18,6 +18,47 @@ pub trait GeneratorInit {
use pumpkin_data::structures::{StructurePlacementCalculator, StructureSet};
use rustc_hash::FxHashMap;
+pub mod flat;
+
+#[derive(Clone, Debug)]
+pub struct FlatLayer {
+ pub block: String,
+ pub height: i32,
+}
+
+pub enum WorldGenerator {
+ Noise(Box),
+ Flat(flat::FlatGenerator),
+}
+
+impl WorldGenerator {
+ #[must_use]
+ pub const fn dimension(&self) -> &Dimension {
+ match self {
+ Self::Noise(noise_gen) => &noise_gen.dimension,
+ Self::Flat(flat_gen) => &flat_gen.dimension,
+ }
+ }
+
+ #[must_use]
+ pub const fn seed(&self) -> u64 {
+ match self {
+ Self::Noise(noise_gen) => noise_gen.random_config.seed,
+ Self::Flat(flat_gen) => flat_gen.seed,
+ }
+ }
+
+ #[must_use]
+ pub const fn global_structure_cache(
+ &self,
+ ) -> Option<&crate::generation::structure::placement::GlobalStructureCache> {
+ match self {
+ Self::Noise(noise_gen) => Some(&noise_gen.global_structure_cache),
+ Self::Flat(_) => None,
+ }
+ }
+}
+
pub struct VanillaGenerator {
pub random_config: GlobalRandomConfig,
pub base_router: ProtoNoiseRouters,
diff --git a/pumpkin-world/src/generation/mod.rs b/pumpkin-world/src/generation/mod.rs
index 12b2a7cf3..35766d674 100644
--- a/pumpkin-world/src/generation/mod.rs
+++ b/pumpkin-world/src/generation/mod.rs
@@ -25,9 +25,22 @@ use pumpkin_util::{
};
#[must_use]
-pub fn get_world_gen(seed: Seed, dimension: Dimension) -> Box {
- // TODO decide which WorldGenerator to pick based on config.
- Box::new(VanillaGenerator::new(seed, dimension))
+pub fn get_world_gen(
+ seed: Seed,
+ dimension: Dimension,
+ is_flat: bool,
+ flat_layers: Vec,
+ flat_biome: String,
+) -> Box {
+ if is_flat {
+ Box::new(generator::WorldGenerator::Flat(
+ generator::flat::FlatGenerator::new(seed, dimension, flat_layers, flat_biome),
+ ))
+ } else {
+ Box::new(generator::WorldGenerator::Noise(Box::new(
+ VanillaGenerator::new(seed, dimension),
+ )))
+ }
}
pub struct GlobalRandomConfig {
diff --git a/pumpkin-world/src/generation/proto_chunk.rs b/pumpkin-world/src/generation/proto_chunk.rs
index 46a8b4027..4363ad4e4 100644
--- a/pumpkin-world/src/generation/proto_chunk.rs
+++ b/pumpkin-world/src/generation/proto_chunk.rs
@@ -176,17 +176,28 @@ impl TerrainCache {
impl ProtoChunk {
#[must_use]
- pub fn new(x: i32, z: i32, generator: &super::generator::VanillaGenerator) -> Self {
- let dimension = &generator.dimension;
+ pub fn new(x: i32, z: i32, generator: &super::generator::WorldGenerator) -> Self {
+ let dimension = generator.dimension();
let height = dimension.logical_height as u16;
let section_count = (height as usize) / 16;
+ let default_block = match generator {
+ super::generator::WorldGenerator::Noise(noise_gen) => noise_gen.default_block,
+ super::generator::WorldGenerator::Flat(_) => Block::AIR.default_state,
+ };
+ let biome_mixer_seed = match generator {
+ super::generator::WorldGenerator::Noise(noise_gen) => noise_gen.biome_mixer_seed,
+ super::generator::WorldGenerator::Flat(flat_gen) => {
+ crate::biome::hash_seed(flat_gen.seed)
+ }
+ };
+
let default_heightmap = [i16::MIN; CHUNK_AREA];
Self {
x,
z,
- default_block: generator.default_block,
- biome_mixer_seed: generator.biome_mixer_seed,
+ default_block,
+ biome_mixer_seed,
flat_block_map: vec![BlockStateId::AIR; CHUNK_AREA * height as usize]
.into_boxed_slice(),
flat_biome_map: vec![
@@ -225,7 +236,7 @@ impl ProtoChunk {
#[must_use]
pub fn from_chunk_data(
chunk_data: &ChunkData,
- generator: &super::generator::VanillaGenerator,
+ generator: &super::generator::WorldGenerator,
) -> Self {
let mut proto_chunk = Self::new(chunk_data.x, chunk_data.z, generator);
@@ -518,7 +529,6 @@ impl ProtoChunk {
#[expect(clippy::too_many_lines)]
pub fn step_to_noise(&mut self, generator: &super::generator::VanillaGenerator) {
debug_assert_eq!(self.stage, StagedChunkEnum::StructureReferences);
-
let settings = generator.settings;
let generation_shape = &settings.shape;
let horizontal_cell_count = CHUNK_DIM / generation_shape.horizontal_cell_block_count();
@@ -717,7 +727,6 @@ impl ProtoChunk {
pub fn step_to_carvers(&mut self, generator: &super::generator::VanillaGenerator) {
debug_assert_eq!(self.stage, StagedChunkEnum::Surface);
-
super::carver::carve(self, generator);
self.stage = StagedChunkEnum::Carvers;
diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs
index e598de91e..44816d9a6 100644
--- a/pumpkin-world/src/level.rs
+++ b/pumpkin-world/src/level.rs
@@ -1,7 +1,7 @@
use crate::chunk::format::linear::LinearV2File;
use crate::chunk::format::pump::PumpFile;
use crate::chunk_system::{ChunkListener, ChunkLoading, GenerationSchedule, LevelChannel};
-use crate::generation::generator::VanillaGenerator;
+use crate::generation::generator::WorldGenerator;
use crate::lighting::DynamicLightEngine;
use crate::{
chunk::{
@@ -77,7 +77,7 @@ pub struct Level {
pub chunk_saver: Arc>,
entity_saver: Arc>,
- pub world_gen: Arc,
+ pub world_gen: Arc,
/// Handles runtime lighting updates
pub light_engine: DynamicLightEngine,
@@ -125,6 +125,7 @@ pub struct LevelFolder {
impl Level {
#[must_use]
+ #[expect(clippy::too_many_lines)]
pub fn from_root_folder(
level_config: &LevelConfig,
root_folder: PathBuf,
@@ -132,11 +133,18 @@ impl Level {
dimension: Dimension,
gen_pool: Option>,
) -> Arc {
- let (namespace, name) = match dimension.minecraft_name.split_once(':') {
- Some((ns, n)) => (ns, n),
- None => ("minecraft", dimension.minecraft_name),
+ let dim_folder = if dimension.minecraft_name == Dimension::OVERWORLD.minecraft_name
+ || dimension.minecraft_name == Dimension::THE_NETHER.minecraft_name
+ || dimension.minecraft_name == Dimension::THE_END.minecraft_name
+ {
+ root_folder.clone()
+ } else {
+ let (namespace, name) = match dimension.minecraft_name.split_once(':') {
+ Some((ns, n)) => (ns, n),
+ None => ("minecraft", dimension.minecraft_name),
+ };
+ root_folder.join("dimensions").join(namespace).join(name)
};
- let dim_folder = root_folder.join("dimensions").join(namespace).join(name);
let region_folder = dim_folder.join("region");
let entities_folder = dim_folder.join("entities");
@@ -154,8 +162,62 @@ impl Level {
poi_folder,
});
+ let main_folder = if dimension.minecraft_name == Dimension::OVERWORLD.minecraft_name {
+ level_folder.root_folder.clone()
+ } else {
+ level_folder
+ .root_folder
+ .parent()
+ .unwrap_or(&level_folder.root_folder)
+ .to_path_buf()
+ };
+
+ let mut is_flat = false;
+ let mut flat_layers = Vec::new();
+ let mut flat_biome = "minecraft:plains".to_string();
+
+ if let Some(wgs) = crate::world_info::data_files::read_world_gen_settings(&main_folder)
+ && let Some(dim_settings) = wgs.dimensions.get(dimension.minecraft_name)
+ && dim_settings.generator.generator_type == "minecraft:flat"
+ {
+ is_flat = true;
+ if let Some(crate::world_info::GeneratorSettings::Compound(nbt)) =
+ &dim_settings.generator.settings
+ {
+ if let Some(pumpkin_nbt::tag::NbtTag::String(b)) = nbt.child_tags.get("biome") {
+ flat_biome = b.to_string();
+ }
+ if let Some(pumpkin_nbt::tag::NbtTag::List(list)) = nbt.child_tags.get("layers") {
+ for tag in list {
+ if let pumpkin_nbt::tag::NbtTag::Compound(layer_compound) = tag {
+ let mut block = "minecraft:air".to_string();
+ let mut height = 1;
+ if let Some(pumpkin_nbt::tag::NbtTag::String(bl)) =
+ layer_compound.child_tags.get("block")
+ {
+ block = bl.to_string();
+ }
+ if let Some(pumpkin_nbt::tag::NbtTag::Int(h)) =
+ layer_compound.child_tags.get("height")
+ {
+ height = *h;
+ }
+ flat_layers
+ .push(crate::generation::generator::FlatLayer { block, height });
+ }
+ }
+ }
+ }
+ }
+
let seed = Seed(seed as u64);
- let world_gen = get_world_gen(seed, dimension).into();
+ let world_gen: Arc = Arc::from(get_world_gen(
+ seed,
+ dimension,
+ is_flat,
+ flat_layers,
+ flat_biome,
+ ));
let chunk_saver: Arc> = match &level_config.chunk {
ChunkConfig::Linear => Arc::new(ChunkFileManager::>::new(())),
diff --git a/pumpkin-world/src/lib.rs b/pumpkin-world/src/lib.rs
index b26ec39fe..c719d3ca1 100644
--- a/pumpkin-world/src/lib.rs
+++ b/pumpkin-world/src/lib.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::unreachable)]
+
use pumpkin_data::{
Block, BlockState, chunk_gen_settings::GenerationSettings, dimension::Dimension,
};
@@ -57,15 +59,21 @@ pub fn bench_create_and_populate_noise(
_terrain_cache: &TerrainCache,
_default_block: &'static BlockState,
) {
- use crate::generation::generator::{GeneratorInit, VanillaGenerator};
+ use crate::generation::generator::{GeneratorInit, VanillaGenerator, WorldGenerator};
use crate::generation::noise::router::surface_height_sampler::{
SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions,
};
use crate::generation::proto_chunk::StandardChunkFluidLevelSampler;
use pumpkin_util::world_seed::Seed;
- let generator = VanillaGenerator::new(Seed(random_config.seed), Dimension::OVERWORLD);
- let mut chunk = ProtoChunk::new(0, 0, &generator);
+ let world_gen = WorldGenerator::Noise(Box::new(VanillaGenerator::new(
+ Seed(random_config.seed),
+ Dimension::OVERWORLD,
+ )));
+ let WorldGenerator::Noise(generator) = &world_gen else {
+ unreachable!()
+ };
+ let mut chunk = ProtoChunk::new(0, 0, &world_gen);
// Create noise sampler and other required components
let settings = generator.settings;
@@ -119,7 +127,7 @@ pub fn bench_create_and_populate_noise(
);
chunk.populate_noise(
- &generator,
+ generator,
&mut noise_sampler,
&generator.random_config.ore_random_deriver,
&mut surface_height_estimate_sampler,
@@ -133,15 +141,21 @@ pub fn bench_create_and_populate_biome(
_terrain_cache: &TerrainCache,
_default_block: &'static BlockState,
) {
- use crate::generation::generator::{GeneratorInit, VanillaGenerator};
+ use crate::generation::generator::{GeneratorInit, VanillaGenerator, WorldGenerator};
use crate::generation::noise::router::multi_noise_sampler::{
MultiNoiseSampler, MultiNoiseSamplerBuilderOptions,
};
use crate::generation::{biome_coords, positions::chunk_pos};
use pumpkin_util::world_seed::Seed;
- let generator = VanillaGenerator::new(Seed(random_config.seed), Dimension::OVERWORLD);
- let mut chunk = ProtoChunk::new(0, 0, &generator);
+ let world_gen = WorldGenerator::Noise(Box::new(VanillaGenerator::new(
+ Seed(random_config.seed),
+ Dimension::OVERWORLD,
+ )));
+ let WorldGenerator::Noise(generator) = &world_gen else {
+ unreachable!()
+ };
+ let mut chunk = ProtoChunk::new(0, 0, &world_gen);
// Create multi-noise sampler
let start_x = chunk_pos::start_block_x(0);
@@ -159,7 +173,7 @@ pub fn bench_create_and_populate_biome(
let mut multi_noise_sampler =
MultiNoiseSampler::generate(&generator.base_router.multi_noise, &multi_noise_config);
- chunk.populate_biomes(&generator, &mut multi_noise_sampler);
+ chunk.populate_biomes(generator, &mut multi_noise_sampler);
}
pub fn bench_create_and_populate_noise_with_surface(
@@ -169,7 +183,7 @@ pub fn bench_create_and_populate_noise_with_surface(
_terrain_cache: &TerrainCache,
_default_block: &'static BlockState,
) {
- use crate::generation::generator::{GeneratorInit, VanillaGenerator};
+ use crate::generation::generator::{GeneratorInit, VanillaGenerator, WorldGenerator};
use crate::generation::noise::router::{
multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions},
surface_height_sampler::{
@@ -179,8 +193,14 @@ pub fn bench_create_and_populate_noise_with_surface(
use crate::generation::proto_chunk::StandardChunkFluidLevelSampler;
use pumpkin_util::world_seed::Seed;
- let generator = VanillaGenerator::new(Seed(random_config.seed), Dimension::OVERWORLD);
- let mut chunk = ProtoChunk::new(0, 0, &generator);
+ let world_gen = WorldGenerator::Noise(Box::new(VanillaGenerator::new(
+ Seed(random_config.seed),
+ Dimension::OVERWORLD,
+ )));
+ let WorldGenerator::Noise(generator) = &world_gen else {
+ unreachable!()
+ };
+ let mut chunk = ProtoChunk::new(0, 0, &world_gen);
// Create all required components
let settings = generator.settings;
@@ -241,12 +261,12 @@ pub fn bench_create_and_populate_noise_with_surface(
&surface_config,
);
- chunk.populate_biomes(&generator, &mut multi_noise_sampler);
+ chunk.populate_biomes(generator, &mut multi_noise_sampler);
chunk.populate_noise(
- &generator,
+ generator,
&mut noise_sampler,
&generator.random_config.ore_random_deriver,
&mut surface_height_estimate_sampler,
);
- chunk.build_surface(&generator, &mut surface_height_estimate_sampler);
+ chunk.build_surface(generator, &mut surface_height_estimate_sampler);
}
diff --git a/pumpkin-world/src/poi/mod.rs b/pumpkin-world/src/poi/mod.rs
index c12a6cc7c..b9498a114 100644
--- a/pumpkin-world/src/poi/mod.rs
+++ b/pumpkin-world/src/poi/mod.rs
@@ -177,7 +177,7 @@ impl PoiRegion {
/// Compress chunk data to bytes
fn compress_chunk_data(chunk_data: &PoiChunkData) -> std::io::Result> {
let mut uncompressed = Vec::new();
- pumpkin_nbt::to_bytes_unnamed(chunk_data, &mut uncompressed)
+ pumpkin_nbt::to_bytes(chunk_data, &mut uncompressed)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
@@ -191,8 +191,16 @@ impl PoiRegion {
let mut uncompressed = Vec::new();
decoder.read_to_end(&mut uncompressed)?;
- pumpkin_nbt::from_bytes_unnamed(Cursor::new(uncompressed))
- .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))
+ let is_named = uncompressed.len() >= 3
+ && uncompressed[0] == 0x0a
+ && uncompressed[1] == 0x00
+ && uncompressed[2] == 0x00;
+ if is_named {
+ pumpkin_nbt::from_bytes(Cursor::new(uncompressed))
+ } else {
+ pumpkin_nbt::from_bytes_unnamed(Cursor::new(uncompressed))
+ }
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))
}
pub fn save(&mut self, path: &Path) -> std::io::Result<()> {
diff --git a/pumpkin-world/src/world_info/anvil.rs b/pumpkin-world/src/world_info/anvil.rs
index 23331284a..9bd52ab20 100644
--- a/pumpkin-world/src/world_info/anvil.rs
+++ b/pumpkin-world/src/world_info/anvil.rs
@@ -12,6 +12,12 @@ use serde::{Deserialize, Serialize};
use crate::world_info::{
MAXIMUM_SUPPORTED_LEVEL_VERSION, MAXIMUM_SUPPORTED_WORLD_DATA_VERSION,
MINIMUM_SUPPORTED_LEVEL_VERSION, MINIMUM_SUPPORTED_WORLD_DATA_VERSION,
+ data_files::{
+ minecraft_data_dir, read_game_rules, read_wandering_trader, read_weather,
+ read_world_clocks, read_world_gen_settings, write_custom_boss_events_stub,
+ write_game_rules, write_scheduled_events_stub, write_wandering_trader, write_weather,
+ write_world_clocks, write_world_gen_settings,
+ },
};
use super::{LevelData, WorldInfoError, WorldInfoReader, WorldInfoWriter};
@@ -89,10 +95,43 @@ impl WorldInfoReader for AnvilLevelInfo {
check_file_data_version(&buf)?;
check_file_level_version(&buf)?;
- let info = pumpkin_nbt::from_bytes::(Cursor::new(buf))
+ let mut info = pumpkin_nbt::from_bytes::(Cursor::new(buf))
.map_err(|e| WorldInfoError::DeserializationError(e.to_string()))?;
- // TODO: check version
+ // game_rules.dat – prefer the new file; fall back to level.dat values
+ if minecraft_data_dir(level_folder)
+ .join("game_rules.dat")
+ .exists()
+ {
+ info.data.game_rules = read_game_rules(level_folder);
+ }
+
+ // world_gen_settings.dat
+ if let Some(wgs) = read_world_gen_settings(level_folder) {
+ info.data.world_gen_settings = wgs;
+ }
+
+ // world_clocks.dat – read the overworld day_time
+ if minecraft_data_dir(level_folder)
+ .join("world_clocks.dat")
+ .exists()
+ {
+ let clocks = read_world_clocks(level_folder);
+ if let Some(overworld) = clocks.clocks.get("minecraft:overworld") {
+ info.data.day_time = overworld.total_ticks;
+ }
+ }
+
+ // weather.dat
+ if minecraft_data_dir(level_folder)
+ .join("weather.dat")
+ .exists()
+ {
+ let weather = read_weather(level_folder);
+ info.data.clear_weather_time = weather.clear_weather_time;
+ }
+
+ // (wandering_trader.dat is not part of LevelData; stored separately when needed)
Ok(info.data)
}
@@ -112,20 +151,76 @@ impl WorldInfoWriter for AnvilLevelInfo {
level_data.last_played = since_the_epoch.as_millis() as i64;
let level = LevelDat { data: level_data };
- // open file
+ // ── Write level.dat ───────────────────────────────────────────────────
let path = level_folder.join(LEVEL_DAT_FILE_NAME);
let world_info_file = File::create(path)?;
- // write compressed data into file
let compression_writer = GzEncoder::new(world_info_file, Compression::best());
// TODO: Proper error handling
pumpkin_nbt::to_bytes(&level, compression_writer)
.expect("Failed to write level.dat to disk");
+
+ let data_version = info.data_version;
+
+ // ── Write data/minecraft/*.dat files ─────────────────────────────────
+
+ // game_rules.dat
+ if let Err(e) = write_game_rules(level_folder, &info.game_rules, data_version) {
+ error!("Failed to write game_rules.dat: {e}");
+ }
+
+ // world_gen_settings.dat
+ if let Err(e) =
+ write_world_gen_settings(level_folder, &info.world_gen_settings, data_version)
+ {
+ error!("Failed to write world_gen_settings.dat: {e}");
+ }
+
+ // world_clocks.dat – persist the overworld day_time; preserve other
+ let mut clocks = read_world_clocks(level_folder);
+ clocks.data_version = data_version;
+ clocks
+ .clocks
+ .entry("minecraft:overworld".to_string())
+ .and_modify(|c| c.total_ticks = info.day_time)
+ .or_insert(crate::world_info::data_files::DimensionClock {
+ total_ticks: info.day_time,
+ });
+
+ if let Err(e) = write_world_clocks(level_folder, &clocks) {
+ error!("Failed to write world_clocks.dat: {e}");
+ }
+
+ // weather.dat
+ let mut weather = read_weather(level_folder);
+ weather.clear_weather_time = info.clear_weather_time;
+ weather.data_version = data_version;
+ if let Err(e) = write_weather(level_folder, &weather) {
+ error!("Failed to write weather.dat: {e}");
+ }
+
+ // wandering_trader.dat (stub / load-save)
+ let mut wandering_trader = read_wandering_trader(level_folder);
+ wandering_trader.data_version = data_version;
+ if let Err(e) = write_wandering_trader(level_folder, &wandering_trader) {
+ error!("Failed to write wandering_trader.dat: {e}");
+ }
+
+ // custom_boss_events.dat
+ if let Err(e) = write_custom_boss_events_stub(level_folder, data_version) {
+ error!("Failed to write custom_boss_events.dat: {e}");
+ }
+
+ // scheduled_events.dat
+ if let Err(e) = write_scheduled_events_stub(level_folder, data_version) {
+ error!("Failed to write scheduled_events.dat: {e}");
+ }
+
Ok(())
}
}
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct LevelDat {
// This tag contains all the level data.
#[serde(rename = "Data")]
@@ -267,7 +362,13 @@ mod test {
let level_dat_again: LevelDat =
from_bytes(Cursor::new(serialized)).expect("Failed to decode from bytes");
- assert_eq!(level_dat_again, *LEVEL_DAT);
+ let mut expected = (*LEVEL_DAT).clone();
+ expected.data.game_rules = GameRuleRegistry::default();
+ expected.data.world_gen_settings = WorldGenSettings::default();
+ expected.data.day_time = 0;
+ expected.data.clear_weather_time = 0;
+
+ assert_eq!(level_dat_again, expected);
}
#[test]
diff --git a/pumpkin-world/src/world_info/data_files.rs b/pumpkin-world/src/world_info/data_files.rs
new file mode 100644
index 000000000..2c539dfa0
--- /dev/null
+++ b/pumpkin-world/src/world_info/data_files.rs
@@ -0,0 +1,417 @@
+use std::{
+ fs::{self, File},
+ io::BufWriter,
+ path::{Path, PathBuf},
+};
+
+use pumpkin_data::game_rules::{GameRule, GameRuleRegistry, GameRuleValue};
+use pumpkin_nbt::{
+ compound::NbtCompound,
+ nbt_compress::{from_gzip_bytes, read_gzip_compound_tag, to_gzip_bytes},
+ tag::NbtTag,
+};
+use serde::{Deserialize, Serialize};
+use tracing::warn;
+
+use crate::world_info::{WorldGenSettings, WorldInfoError};
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct DataFileRoot {
+ #[serde(rename = "data")]
+ pub data: T,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct WeatherData {
+ #[serde(rename = "rain_time", default)]
+ pub rain_time: i32,
+ #[serde(rename = "raining", default)]
+ pub raining: bool,
+ #[serde(rename = "thundering", default)]
+ pub thundering: bool,
+ #[serde(rename = "thunder_time", default)]
+ pub thunder_time: i32,
+ #[serde(rename = "clear_weather_time", default)]
+ pub clear_weather_time: i32,
+ #[serde(rename = "DataVersion", default)]
+ pub data_version: i32,
+}
+
+impl Default for WeatherData {
+ fn default() -> Self {
+ Self {
+ rain_time: 0,
+ raining: false,
+ thundering: false,
+ thunder_time: 0,
+ clear_weather_time: -1,
+ data_version: 0,
+ }
+ }
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
+pub struct WorldGenSettingsData {
+ #[serde(flatten)]
+ pub settings: WorldGenSettings,
+ #[serde(rename = "DataVersion", default)]
+ pub data_version: i32,
+ #[serde(rename = "bonus_chest", default)]
+ pub bonus_chest: bool,
+ #[serde(rename = "generate_structures", default = "default_true")]
+ pub generate_structures: bool,
+}
+
+const fn default_true() -> bool {
+ true
+}
+
+impl WorldGenSettingsData {
+ #[must_use]
+ pub const fn new(settings: WorldGenSettings, data_version: i32) -> Self {
+ Self {
+ settings,
+ data_version,
+ bonus_chest: false,
+ generate_structures: true,
+ }
+ }
+}
+
+#[derive(Clone, PartialEq, Eq, Debug, Default)]
+pub struct DimensionClock {
+ pub total_ticks: i64,
+}
+
+#[derive(Clone, PartialEq, Eq, Debug, Default)]
+pub struct WorldClocksData {
+ pub clocks: std::collections::HashMap,
+ pub data_version: i32,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct WanderingTraderData {
+ #[serde(rename = "spawn_delay", default = "default_wandering_trader_delay")]
+ pub spawn_delay: i32,
+ #[serde(rename = "spawn_chance", default = "default_wandering_trader_chance")]
+ pub spawn_chance: i32,
+ #[serde(rename = "DataVersion", default)]
+ pub data_version: i32,
+}
+
+const fn default_wandering_trader_delay() -> i32 {
+ 24_000
+}
+const fn default_wandering_trader_chance() -> i32 {
+ 25
+}
+
+impl Default for WanderingTraderData {
+ fn default() -> Self {
+ Self {
+ spawn_delay: default_wandering_trader_delay(),
+ spawn_chance: default_wandering_trader_chance(),
+ data_version: 0,
+ }
+ }
+}
+
+#[must_use]
+pub fn minecraft_data_dir(level_folder: &Path) -> PathBuf {
+ level_folder.join("data").join("minecraft")
+}
+
+/// Ensures the `/data/minecraft/` directory exists.
+pub fn ensure_minecraft_data_dir(level_folder: &Path) -> Result {
+ let dir = minecraft_data_dir(level_folder);
+ fs::create_dir_all(&dir)?;
+ Ok(dir)
+}
+
+pub fn read_weather(level_folder: &Path) -> WeatherData {
+ let path = minecraft_data_dir(level_folder).join("weather.dat");
+ if !path.exists() {
+ return WeatherData::default();
+ }
+ match File::open(&path) {
+ Ok(f) => match from_gzip_bytes::, _>(f) {
+ Ok(root) => root.data,
+ Err(e) => {
+ warn!("Failed to deserialize weather.dat, using defaults: {e}");
+ WeatherData::default()
+ }
+ },
+ Err(e) => {
+ warn!("Failed to open weather.dat, using defaults: {e}");
+ WeatherData::default()
+ }
+ }
+}
+
+pub fn write_weather(level_folder: &Path, data: &WeatherData) -> Result<(), WorldInfoError> {
+ let dir = ensure_minecraft_data_dir(level_folder)?;
+ let path = dir.join("weather.dat");
+ let file = File::create(&path)?;
+ let root = DataFileRoot { data: data.clone() };
+ to_gzip_bytes(&root, BufWriter::new(file))
+ .map_err(|e| WorldInfoError::SerializationError(e.to_string()))
+}
+
+pub fn read_world_gen_settings(level_folder: &Path) -> Option {
+ let path = minecraft_data_dir(level_folder).join("world_gen_settings.dat");
+ if !path.exists() {
+ return None;
+ }
+ match File::open(&path) {
+ Ok(f) => match from_gzip_bytes::, _>(f) {
+ Ok(root) => Some(root.data.settings),
+ Err(e) => {
+ warn!("Failed to deserialize world_gen_settings.dat: {e}");
+ None
+ }
+ },
+ Err(e) => {
+ warn!("Failed to open world_gen_settings.dat: {e}");
+ None
+ }
+ }
+}
+
+pub fn write_world_gen_settings(
+ level_folder: &Path,
+ settings: &WorldGenSettings,
+ data_version: i32,
+) -> Result<(), WorldInfoError> {
+ let dir = ensure_minecraft_data_dir(level_folder)?;
+ let path = dir.join("world_gen_settings.dat");
+ let file = File::create(&path)?;
+ let data = WorldGenSettingsData::new(settings.clone(), data_version);
+ let root = DataFileRoot { data };
+ to_gzip_bytes(&root, BufWriter::new(file))
+ .map_err(|e| WorldInfoError::SerializationError(e.to_string()))
+}
+
+#[must_use]
+pub fn game_rules_to_nbt(rules: &GameRuleRegistry, data_version: i32) -> NbtCompound {
+ let mut inner = NbtCompound::new();
+ for rule in GameRule::all() {
+ let key = format!("minecraft:{rule}");
+ match rules.get(rule) {
+ GameRuleValue::Bool(b) => inner.put(&key, NbtTag::Byte(i8::from(*b))),
+ GameRuleValue::Int(i) => inner.put(&key, NbtTag::Int(*i as i32)),
+ }
+ }
+ inner.put_int("DataVersion", data_version);
+
+ let mut root = NbtCompound::new();
+ root.put_compound("data", inner);
+ root
+}
+
+pub fn game_rules_from_nbt(root: &NbtCompound) -> GameRuleRegistry {
+ let mut registry = GameRuleRegistry::default();
+
+ let Some(inner) = root.get_compound("data") else {
+ warn!("game_rules.dat missing 'data' compound, using defaults");
+ return registry;
+ };
+
+ for rule in GameRule::all() {
+ let key = format!("minecraft:{rule}");
+ match registry.get_mut(rule) {
+ GameRuleValue::Bool(b) => {
+ if let Some(v) = inner.get_byte(&key) {
+ *b = v != 0;
+ }
+ }
+ GameRuleValue::Int(i) => {
+ if let Some(v) = inner.get_int(&key) {
+ *i = i64::from(v);
+ }
+ }
+ }
+ }
+
+ registry
+}
+
+pub fn read_game_rules(level_folder: &Path) -> GameRuleRegistry {
+ let path = minecraft_data_dir(level_folder).join("game_rules.dat");
+ if !path.exists() {
+ return GameRuleRegistry::default();
+ }
+
+ match File::open(&path) {
+ Ok(f) => match read_gzip_compound_tag(f) {
+ Ok(compound) => game_rules_from_nbt(&compound),
+ Err(e) => {
+ warn!("Failed to parse game_rules.dat: {e}");
+ GameRuleRegistry::default()
+ }
+ },
+ Err(e) => {
+ warn!("Failed to open game_rules.dat: {e}");
+ GameRuleRegistry::default()
+ }
+ }
+}
+
+pub fn write_game_rules(
+ level_folder: &Path,
+ rules: &GameRuleRegistry,
+ data_version: i32,
+) -> Result<(), WorldInfoError> {
+ let dir = ensure_minecraft_data_dir(level_folder)?;
+ let path = dir.join("game_rules.dat");
+
+ let compound = game_rules_to_nbt(rules, data_version);
+ let file = File::create(&path)?;
+
+ pumpkin_nbt::nbt_compress::write_gzip_compound_tag(compound, file)
+ .map_err(|e| WorldInfoError::SerializationError(e.to_string()))
+}
+
+pub fn read_world_clocks(level_folder: &Path) -> WorldClocksData {
+ let path = minecraft_data_dir(level_folder).join("world_clocks.dat");
+ if !path.exists() {
+ return WorldClocksData::default();
+ }
+
+ match File::open(&path) {
+ Ok(f) => match read_gzip_compound_tag(f) {
+ Ok(compound) => world_clocks_from_nbt(&compound),
+ Err(e) => {
+ warn!("Failed to parse world_clocks.dat: {e}");
+ WorldClocksData::default()
+ }
+ },
+ Err(e) => {
+ warn!("Failed to open world_clocks.dat: {e}");
+ WorldClocksData::default()
+ }
+ }
+}
+
+fn world_clocks_from_nbt(root: &NbtCompound) -> WorldClocksData {
+ let mut result = WorldClocksData::default();
+
+ let Some(inner) = root.get_compound("data") else {
+ return result;
+ };
+
+ result.data_version = inner.get_int("DataVersion").unwrap_or(0);
+
+ for (key, tag) in &inner.child_tags {
+ if key.as_ref() == "DataVersion" {
+ continue;
+ }
+ if let NbtTag::Compound(dim_compound) = tag {
+ let total_ticks = dim_compound.get_long("total_ticks").unwrap_or(0);
+ result
+ .clocks
+ .insert(key.to_string(), DimensionClock { total_ticks });
+ }
+ }
+
+ result
+}
+
+pub fn write_world_clocks(
+ level_folder: &Path,
+ clocks: &WorldClocksData,
+) -> Result<(), WorldInfoError> {
+ let dir = ensure_minecraft_data_dir(level_folder)?;
+ let path = dir.join("world_clocks.dat");
+
+ let mut inner = NbtCompound::new();
+ for (dim_name, clock) in &clocks.clocks {
+ let mut dim_compound = NbtCompound::new();
+ dim_compound.put_long("total_ticks", clock.total_ticks);
+ inner.put_compound(dim_name, dim_compound);
+ }
+ inner.put_int("DataVersion", clocks.data_version);
+
+ let mut root = NbtCompound::new();
+ root.put_compound("data", inner);
+
+ let file = File::create(&path)?;
+
+ pumpkin_nbt::nbt_compress::write_gzip_compound_tag(root, file)
+ .map_err(|e| WorldInfoError::SerializationError(e.to_string()))
+}
+
+pub fn read_wandering_trader(level_folder: &Path) -> WanderingTraderData {
+ let path = minecraft_data_dir(level_folder).join("wandering_trader.dat");
+ if !path.exists() {
+ return WanderingTraderData::default();
+ }
+ match File::open(&path) {
+ Ok(f) => match from_gzip_bytes::, _>(f) {
+ Ok(root) => root.data,
+ Err(e) => {
+ warn!("Failed to deserialize wandering_trader.dat, using defaults: {e}");
+ WanderingTraderData::default()
+ }
+ },
+ Err(e) => {
+ warn!("Failed to open wandering_trader.dat: {e}");
+ WanderingTraderData::default()
+ }
+ }
+}
+
+pub fn write_wandering_trader(
+ level_folder: &Path,
+ data: &WanderingTraderData,
+) -> Result<(), WorldInfoError> {
+ let dir = ensure_minecraft_data_dir(level_folder)?;
+ let path = dir.join("wandering_trader.dat");
+ let file = File::create(&path)?;
+ let root = DataFileRoot { data: data.clone() };
+ to_gzip_bytes(&root, BufWriter::new(file))
+ .map_err(|e| WorldInfoError::SerializationError(e.to_string()))
+}
+
+pub fn write_custom_boss_events_stub(
+ level_folder: &Path,
+ data_version: i32,
+) -> Result<(), WorldInfoError> {
+ let dir = ensure_minecraft_data_dir(level_folder)?;
+ let path = dir.join("custom_boss_events.dat");
+ // Only create if absent; actual boss-bar persistence lives elsewhere.
+ if path.exists() {
+ return Ok(());
+ }
+
+ let mut inner = NbtCompound::new();
+ inner.put_int("DataVersion", data_version);
+ let mut root = NbtCompound::new();
+ root.put_compound("data", inner);
+
+ let file = File::create(&path)?;
+
+ pumpkin_nbt::nbt_compress::write_gzip_compound_tag(root, file)
+ .map_err(|e| WorldInfoError::SerializationError(e.to_string()))
+}
+
+pub fn write_scheduled_events_stub(
+ level_folder: &Path,
+ data_version: i32,
+) -> Result<(), WorldInfoError> {
+ let dir = ensure_minecraft_data_dir(level_folder)?;
+ let path = dir.join("scheduled_events.dat");
+ if path.exists() {
+ return Ok(());
+ }
+
+ let mut inner = NbtCompound::new();
+ inner.put("events", NbtTag::List(vec![]));
+ inner.put_int("DataVersion", data_version);
+ let mut root = NbtCompound::new();
+ root.put_compound("data", inner);
+
+ let file = File::create(&path)?;
+
+ pumpkin_nbt::nbt_compress::write_gzip_compound_tag(root, file)
+ .map_err(|e| WorldInfoError::SerializationError(e.to_string()))
+}
diff --git a/pumpkin-world/src/world_info/mod.rs b/pumpkin-world/src/world_info/mod.rs
index 3df962c4a..20e493261 100644
--- a/pumpkin-world/src/world_info/mod.rs
+++ b/pumpkin-world/src/world_info/mod.rs
@@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
use thiserror::Error;
pub mod anvil;
+pub mod data_files;
// Constraint: disk biome palette serialization changed in 1.21.5
pub const MINIMUM_SUPPORTED_WORLD_DATA_VERSION: i32 = 4435; // 1.21.9
@@ -48,21 +49,14 @@ pub struct LevelData {
pub border_warning_blocks: f64,
#[serde(default = "default_border_warning_time")]
pub border_warning_time: f64,
- #[serde(rename = "clearWeatherTime", default)]
- pub clear_weather_time: i32,
#[serde(default = "default_data_packs")]
pub data_packs: DataPacks,
pub data_version: i32,
- #[serde(default)]
- pub day_time: i64,
#[serde(with = "serde_enum_as_integer", default = "default_difficulty")]
pub difficulty: Difficulty,
#[serde(default)]
pub difficulty_locked: bool,
#[serde(default)]
- pub game_rules: GameRuleRegistry,
- pub world_gen_settings: WorldGenSettings,
- #[serde(default)]
pub last_played: i64,
#[serde(default = "default_level_name")]
pub level_name: String,
@@ -82,6 +76,26 @@ pub struct LevelData {
pub level_version: i32,
#[serde(rename = "map_id", default)]
pub map_id: i32,
+
+ // These are NOT serialized to level.dat, but are still deserialized from it if present.
+ // They are loaded and saved by AnvilLevelInfo via the data_files module.
+ /// Game rules – persisted to `data/minecraft/game_rules.dat`.
+ #[serde(skip_serializing, default)]
+ pub game_rules: GameRuleRegistry,
+
+ /// World generation settings – persisted to `data/minecraft/world_gen_settings.dat`.
+ #[serde(skip_serializing, default)]
+ pub world_gen_settings: WorldGenSettings,
+
+ /// In-game time of day (overworld dimension clock).
+ /// Persisted to `data/minecraft/world_clocks.dat`.
+ #[serde(skip_serializing, default)]
+ pub day_time: i64,
+
+ /// Remaining ticks of forced-clear weather.
+ /// Persisted to `data/minecraft/weather.dat`.
+ #[serde(rename = "clearWeatherTime", skip_serializing, default)]
+ pub clear_weather_time: i32,
}
const DEFAULT_BORDER_DAMAGE_PER_BLOCK: f64 = 0.2;
@@ -134,6 +148,13 @@ pub struct WorldGenSettings {
pub dimensions: Dimensions,
}
+impl Default for WorldGenSettings {
+ fn default() -> Self {
+ // Use seed 0 as placeholder; actual seed comes from config or world_gen_settings.dat
+ Self::new(pumpkin_util::world_seed::Seed(0))
+ }
+}
+
pub type Dimensions = HashMap;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Dimension {
@@ -144,7 +165,8 @@ pub struct Dimension {
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Generator {
- pub settings: GeneratorSettings,
+ #[serde(default)]
+ pub settings: Option,
#[serde(default)]
pub biome_source: Option,
#[serde(rename = "type")]
@@ -227,7 +249,9 @@ impl WorldGenSettings {
"minecraft:overworld".to_string(),
Dimension {
generator: Generator {
- settings: GeneratorSettings::Reference("minecraft:overworld".to_string()),
+ settings: Some(GeneratorSettings::Reference(
+ "minecraft:overworld".to_string(),
+ )),
biome_source: Some(BiomeSource::WithPreset {
preset: "minecraft:overworld".to_string(),
biome_type: "minecraft:multi_noise".to_string(),
@@ -241,7 +265,7 @@ impl WorldGenSettings {
"minecraft:the_nether".to_string(),
Dimension {
generator: Generator {
- settings: GeneratorSettings::Reference("minecraft:nether".to_string()),
+ settings: Some(GeneratorSettings::Reference("minecraft:nether".to_string())),
biome_source: Some(BiomeSource::WithPreset {
preset: "minecraft:nether".to_string(),
biome_type: "minecraft:multi_noise".to_string(),
@@ -255,7 +279,7 @@ impl WorldGenSettings {
"minecraft:the_end".to_string(),
Dimension {
generator: Generator {
- settings: GeneratorSettings::Reference("minecraft:end".to_string()),
+ settings: Some(GeneratorSettings::Reference("minecraft:end".to_string())),
biome_source: Some(BiomeSource::Simple {
biome_type: "minecraft:the_end".to_string(),
}),
@@ -310,14 +334,10 @@ impl LevelData {
border_size_lerp_time: 0,
border_warning_blocks: DEFAULT_BORDER_WARNING_BLOCKS,
border_warning_time: DEFAULT_BORDER_WARNING_TIME,
- clear_weather_time: -1,
data_packs: default_data_packs(),
data_version: MAXIMUM_SUPPORTED_WORLD_DATA_VERSION,
- day_time: 0,
difficulty: DEFAULT_DIFFICULTY,
difficulty_locked: false,
- game_rules: GameRuleRegistry::default(),
- world_gen_settings: WorldGenSettings::new(seed),
last_played: -1,
level_name: DEFAULT_LEVEL_NAME.to_string(),
spawn_x: 0,
@@ -328,6 +348,11 @@ impl LevelData {
world_version: WorldVersion::default(),
level_version: MAXIMUM_SUPPORTED_LEVEL_VERSION,
map_id: 0,
+ // fields now in data/minecraft/*.dat
+ game_rules: GameRuleRegistry::default(),
+ world_gen_settings: WorldGenSettings::new(seed),
+ day_time: 0,
+ clear_weather_time: -1,
}
}
@@ -345,6 +370,8 @@ pub enum WorldInfoError {
InfoNotFound,
#[error("Deserialization error: {0}")]
DeserializationError(String),
+ #[error("Serialization error: {0}")]
+ SerializationError(String),
#[error("Unsupported world data version: {0}")]
UnsupportedDataVersion(i32),
#[error("Unsupported world level version: {0}")]
diff --git a/pumpkin/src/block/blocks/redstone/dropper.rs b/pumpkin/src/block/blocks/redstone/dropper.rs
index 710e81d36..5f75a52ad 100644
--- a/pumpkin/src/block/blocks/redstone/dropper.rs
+++ b/pumpkin/src/block/blocks/redstone/dropper.rs
@@ -153,7 +153,6 @@ impl BlockBehaviour for DropperBlock {
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position) {
- // Safely extract the dropper instead of unwrap()
let Some(dropper) = block_entity.as_any().downcast_ref::()
else {
return;
diff --git a/pumpkin/src/command/args/gameprofile.rs b/pumpkin/src/command/args/gameprofile.rs
index 0560486c4..45ec10192 100644
--- a/pumpkin/src/command/args/gameprofile.rs
+++ b/pumpkin/src/command/args/gameprofile.rs
@@ -242,10 +242,10 @@ async fn resolve_profiles_from_token(
return Ok(vec![profile]);
}
- if server.basic_config.online_mode {
+ if server.advanced_config.networking.java.online_mode {
match lookup_profile_by_name(
raw_arg.value,
- &server.advanced_config.networking.authentication,
+ &server.advanced_config.networking.java.authentication,
) {
Ok(Some((uuid, resolved_name))) => {
server
diff --git a/pumpkin/src/command/argument_types/game_profile.rs b/pumpkin/src/command/argument_types/game_profile.rs
index 4c474439f..cbd670063 100644
--- a/pumpkin/src/command/argument_types/game_profile.rs
+++ b/pumpkin/src/command/argument_types/game_profile.rs
@@ -70,10 +70,10 @@ impl GameProfileResult {
return Ok(vec![profile]);
}
- if server.basic_config.online_mode {
+ if server.advanced_config.networking.java.online_mode {
return match lookup_profile_by_name(
name,
- &server.advanced_config.networking.authentication,
+ &server.advanced_config.networking.java.authentication,
) {
Ok(Some((uuid, resolved_name))) => {
server
diff --git a/pumpkin/src/command/commands/list.rs b/pumpkin/src/command/commands/list.rs
index d55a3fb4f..580ae3ae5 100644
--- a/pumpkin/src/command/commands/list.rs
+++ b/pumpkin/src/command/commands/list.rs
@@ -37,6 +37,23 @@ impl CommandExecutor for ListCommandExecutor {
ListMode::Uuids => get_player_names_and_ids(&players),
};
+ let max_players = context.source.output.as_player().map_or_else(
+ || context.server().advanced_config.networking.java.max_players,
+ |player| match player.client.as_ref() {
+ crate::net::ClientPlatform::Java(_) => {
+ context.server().advanced_config.networking.java.max_players
+ }
+ crate::net::ClientPlatform::Bedrock(_) => {
+ context
+ .server()
+ .advanced_config
+ .networking
+ .bedrock
+ .max_players
+ }
+ },
+ );
+
context
.source
.send_feedback(
@@ -45,9 +62,7 @@ impl CommandExecutor for ListCommandExecutor {
COMMANDS_LIST_PLAYERS,
[
TextComponent::text(players_len.to_string()),
- TextComponent::text(
- context.server().basic_config.max_players.to_string(),
- ),
+ TextComponent::text(max_players.to_string()),
list,
],
),
diff --git a/pumpkin/src/entity/ai/goal/beg.rs b/pumpkin/src/entity/ai/goal/beg.rs
index bde49528d..016b07c4a 100644
--- a/pumpkin/src/entity/ai/goal/beg.rs
+++ b/pumpkin/src/entity/ai/goal/beg.rs
@@ -51,14 +51,14 @@ impl BegGoal {
}
fn set_begging(mob: &dyn Mob, begging: bool) {
- mob.get_mob_entity()
- .living_entity
- .entity
- .send_meta_data(&[Metadata::new(
+ mob.get_mob_entity().living_entity.entity.send_meta_data(
+ &[Metadata::new(
TrackedData::INTERESTED_ID,
MetaDataType::BOOLEAN,
begging,
- )]);
+ )],
+ None,
+ );
}
}
diff --git a/pumpkin/src/entity/area_effect_cloud.rs b/pumpkin/src/entity/area_effect_cloud.rs
index 53f568196..c943cadf9 100644
--- a/pumpkin/src/entity/area_effect_cloud.rs
+++ b/pumpkin/src/entity/area_effect_cloud.rs
@@ -173,29 +173,35 @@ impl EntityBase for AreaEffectCloudEntity {
};
// Send initial particle and radius
- self.entity
- .send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
+ self.entity.send_meta_data(
+ &[pumpkin_protocol::java::client::play::Metadata::new(
pumpkin_data::tracked_data::TrackedData::PARTICLE,
pumpkin_data::meta_data_type::MetaDataType::PARTICLE,
&meta,
- )]);
+ )],
+ None,
+ );
- self.entity
- .send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
+ self.entity.send_meta_data(
+ &[pumpkin_protocol::java::client::play::Metadata::new(
pumpkin_data::tracked_data::TrackedData::RADIUS,
pumpkin_data::meta_data_type::MetaDataType::FLOAT,
radius,
- )]);
+ )],
+ None,
+ );
// Initial waiting flag
let wait_time = *self.wait_time.lock().await;
let is_waiting = 0 < wait_time;
- self.entity
- .send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
+ self.entity.send_meta_data(
+ &[pumpkin_protocol::java::client::play::Metadata::new(
pumpkin_data::tracked_data::TrackedData::WAITING,
pumpkin_data::meta_data_type::MetaDataType::BOOLEAN,
is_waiting,
- )]);
+ )],
+ None,
+ );
})
}
@@ -225,12 +231,14 @@ impl EntityBase for AreaEffectCloudEntity {
// When the waiting period ends, notify clients so they render full particles
if age == wait_time && wait_time > 0 {
- self.entity
- .send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
+ self.entity.send_meta_data(
+ &[pumpkin_protocol::java::client::play::Metadata::new(
pumpkin_data::tracked_data::TrackedData::WAITING,
pumpkin_data::meta_data_type::MetaDataType::BOOLEAN,
false,
- )]);
+ )],
+ None,
+ );
}
if age < wait_time {
@@ -251,12 +259,14 @@ impl EntityBase for AreaEffectCloudEntity {
// Send new radius
drop(radius);
- self.entity
- .send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
+ self.entity.send_meta_data(
+ &[pumpkin_protocol::java::client::play::Metadata::new(
pumpkin_data::tracked_data::TrackedData::RADIUS,
pumpkin_data::meta_data_type::MetaDataType::FLOAT,
current_radius,
- )]);
+ )],
+ None,
+ );
}
// Tick down reapplication map
@@ -380,13 +390,14 @@ impl EntityBase for AreaEffectCloudEntity {
drop(radius_lock);
// Send updated radius to clients
- self.entity.send_meta_data(&[
- pumpkin_protocol::java::client::play::Metadata::new(
+ self.entity.send_meta_data(
+ &[pumpkin_protocol::java::client::play::Metadata::new(
pumpkin_data::tracked_data::TrackedData::RADIUS,
pumpkin_data::meta_data_type::MetaDataType::FLOAT,
current_radius,
- ),
- ]);
+ )],
+ None,
+ );
}
// Apply duration-on-use (shorten lifespan)
diff --git a/pumpkin/src/entity/breath.rs b/pumpkin/src/entity/breath.rs
index e3047a4f6..b2d4eae9d 100644
--- a/pumpkin/src/entity/breath.rs
+++ b/pumpkin/src/entity/breath.rs
@@ -152,11 +152,21 @@ impl BreathManager {
pub fn send_air_supply(&self, player: &Player) {
let air = self.air_supply.load(Ordering::Relaxed).clamp(0, MAX_AIR);
- player.get_entity().send_meta_data(&[Metadata::new(
- TrackedData::AIR_SUPPLY_ID,
- MetaDataType::INT,
- VarInt(air),
- )]);
+ let mut bedrock_meta =
+ pumpkin_protocol::bedrock::client::set_actor_data::EntityMetadata::new();
+ bedrock_meta.set(
+ pumpkin_protocol::bedrock::client::set_actor_data::entity_data_key::AIR_SUPPLY,
+ pumpkin_protocol::bedrock::client::set_actor_data::MetadataValue::Short(air as i16),
+ );
+
+ player.get_entity().send_meta_data(
+ &[Metadata::new(
+ TrackedData::AIR_SUPPLY_ID,
+ MetaDataType::INT,
+ VarInt(air),
+ )],
+ Some(&bedrock_meta),
+ );
}
pub fn reset(&self, player: &Player) {
diff --git a/pumpkin/src/entity/decoration/end_crystal.rs b/pumpkin/src/entity/decoration/end_crystal.rs
index 342cd8308..ef7ed5fd4 100644
--- a/pumpkin/src/entity/decoration/end_crystal.rs
+++ b/pumpkin/src/entity/decoration/end_crystal.rs
@@ -22,11 +22,14 @@ impl EndCrystalEntity {
impl EndCrystalEntity {
pub fn set_show_bottom(&self, show_bottom: bool) {
- self.entity.send_meta_data(&[Metadata::new(
- TrackedData::SHOW_BOTTOM,
- MetaDataType::BOOLEAN,
- show_bottom,
- )]);
+ self.entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::SHOW_BOTTOM,
+ MetaDataType::BOOLEAN,
+ show_bottom,
+ )],
+ None,
+ );
}
}
diff --git a/pumpkin/src/entity/falling.rs b/pumpkin/src/entity/falling.rs
index 85cee8cbf..fa1c6e4d4 100644
--- a/pumpkin/src/entity/falling.rs
+++ b/pumpkin/src/entity/falling.rs
@@ -93,11 +93,14 @@ impl EntityBase for FallingEntity {
fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
Box::pin(async move {
- self.entity.send_meta_data(&[Metadata::new(
- TrackedData::START_POS,
- MetaDataType::BLOCK_POS,
- self.entity.block_pos.load(),
- )]);
+ self.entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::START_POS,
+ MetaDataType::BLOCK_POS,
+ self.entity.block_pos.load(),
+ )],
+ None,
+ );
})
}
diff --git a/pumpkin/src/entity/item.rs b/pumpkin/src/entity/item.rs
index 816652c3e..30657548d 100644
--- a/pumpkin/src/entity/item.rs
+++ b/pumpkin/src/entity/item.rs
@@ -470,11 +470,14 @@ impl EntityBase for ItemEntity {
fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
Box::pin(async {
- self.entity.send_meta_data(&[Metadata::new(
- TrackedData::ITEM,
- MetaDataType::ITEM_STACK,
- &ItemStackSerializer::from(self.item_stack.lock().await.clone()),
- )]);
+ self.entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::ITEM,
+ MetaDataType::ITEM_STACK,
+ &ItemStackSerializer::from(self.item_stack.lock().await.clone()),
+ )],
+ None,
+ );
})
}
diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs
index 610a044dd..8a588b9f1 100644
--- a/pumpkin/src/entity/living.rs
+++ b/pumpkin/src/entity/living.rs
@@ -237,11 +237,26 @@ impl LivingEntity {
b &= !index;
}
self.livings_flags.store(b, Ordering::Relaxed);
- self.entity.send_meta_data(&[Metadata::new(
- TrackedData::LIVING_ENTITY_FLAGS,
- MetaDataType::BYTE,
- b,
- )]);
+
+ let bedrock_meta = (flag == Self::USING_ITEM_FLAG).then(|| {
+ let mut meta = pumpkin_protocol::bedrock::client::set_actor_data::EntityMetadata::new();
+ meta.set_flag(
+ pumpkin_protocol::bedrock::client::set_actor_data::entity_data_key::FLAGS,
+ pumpkin_protocol::bedrock::client::set_actor_data::entity_data_flag::USING_ITEM
+ as u8,
+ value,
+ );
+ meta
+ });
+
+ self.entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::LIVING_ENTITY_FLAGS,
+ MetaDataType::BYTE,
+ b,
+ )],
+ bedrock_meta.as_ref(),
+ );
}
pub async fn clear_active_hand(&self) {
@@ -274,11 +289,14 @@ impl LivingEntity {
let clamped = health.max(0.0).min(max_health);
self.health.store(clamped);
// tell everyone entities health changed
- self.entity.send_meta_data(&[Metadata::new(
- TrackedData::HEALTH_ID,
- MetaDataType::FLOAT,
- clamped,
- )]);
+ self.entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::HEALTH_ID,
+ MetaDataType::FLOAT,
+ clamped,
+ )],
+ None,
+ );
}
/// Returns the current maximum health for this entity
@@ -328,8 +346,10 @@ impl LivingEntity {
// Send absorption metadata for players (visual yellow hearts)
if let Some(tracked_id) = self.player_absorption_id() {
- self.entity
- .send_meta_data(&[Metadata::new(tracked_id, MetaDataType::FLOAT, new_abs)]);
+ self.entity.send_meta_data(
+ &[Metadata::new(tracked_id, MetaDataType::FLOAT, new_abs)],
+ None,
+ );
}
}
@@ -1791,11 +1811,14 @@ impl LivingEntity {
// Clear any absorption
self.absorption.store(0.0);
// Send health metadata
- self.entity.send_meta_data(&[Metadata::new(
- TrackedData::HEALTH_ID,
- MetaDataType::FLOAT,
- max_health,
- )]);
+ self.entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::HEALTH_ID,
+ MetaDataType::FLOAT,
+ max_health,
+ )],
+ None,
+ );
self.reset_effects_and_attributes().await;
diff --git a/pumpkin/src/entity/mob/creeper.rs b/pumpkin/src/entity/mob/creeper.rs
index f6c2e36d7..4d2673152 100644
--- a/pumpkin/src/entity/mob/creeper.rs
+++ b/pumpkin/src/entity/mob/creeper.rs
@@ -86,14 +86,14 @@ impl CreeperEntity {
pub fn set_fuse_speed(&self, speed: i32) {
self.fuse_speed.store(speed, Ordering::Relaxed);
- self.mob_entity
- .living_entity
- .entity
- .send_meta_data(&[Metadata::new(
+ self.mob_entity.living_entity.entity.send_meta_data(
+ &[Metadata::new(
TrackedData::FUSE_ID,
MetaDataType::INTEGER,
VarInt(speed),
- )]);
+ )],
+ None,
+ );
}
async fn explode(&self) {
@@ -219,11 +219,14 @@ impl Mob for CreeperEntity {
);
self.ignited.store(true, Ordering::Relaxed);
- entity.send_meta_data(&[Metadata::new(
- TrackedData::IS_IGNITED,
- MetaDataType::BOOLEAN,
- true,
- )]);
+ entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::IS_IGNITED,
+ MetaDataType::BOOLEAN,
+ true,
+ )],
+ None,
+ );
if player.gamemode.load() != pumpkin_util::GameMode::Creative {
// TODO: Handle DamageResult::Broken to broadcast item break and update player slot.
diff --git a/pumpkin/src/entity/mob/enderman.rs b/pumpkin/src/entity/mob/enderman.rs
index dff700973..1f78fb453 100644
--- a/pumpkin/src/entity/mob/enderman.rs
+++ b/pumpkin/src/entity/mob/enderman.rs
@@ -282,14 +282,14 @@ impl EndermanEntity {
pub fn set_angry(&self, angry: bool) {
self.angry.store(angry, Ordering::Relaxed);
- self.mob_entity
- .living_entity
- .entity
- .send_meta_data(&[Metadata::new(
+ self.mob_entity.living_entity.entity.send_meta_data(
+ &[Metadata::new(
TrackedData::CREEPY,
MetaDataType::BOOLEAN,
angry,
- )]);
+ )],
+ None,
+ );
}
pub fn is_angry(&self) -> bool {
@@ -298,27 +298,27 @@ impl EndermanEntity {
pub fn set_provoked(&self, provoked: bool) {
self.provoked.store(provoked, Ordering::Relaxed);
- self.mob_entity
- .living_entity
- .entity
- .send_meta_data(&[Metadata::new(
+ self.mob_entity.living_entity.entity.send_meta_data(
+ &[Metadata::new(
TrackedData::STARED_AT,
MetaDataType::BOOLEAN,
provoked,
- )]);
+ )],
+ None,
+ );
}
pub fn set_carried_block(&self, block_state: Option) {
self.carried_block.store(block_state);
let value = block_state.map_or(VarInt(0), |id| VarInt(id.as_u16() as i32));
- self.mob_entity
- .living_entity
- .entity
- .send_meta_data(&[Metadata::new(
+ self.mob_entity.living_entity.entity.send_meta_data(
+ &[Metadata::new(
TrackedData::CARRY_STATE,
MetaDataType::OPTIONAL_BLOCK_STATE,
value,
- )]);
+ )],
+ None,
+ );
}
pub fn get_carried_block(&self) -> Option {
diff --git a/pumpkin/src/entity/mob/mod.rs b/pumpkin/src/entity/mob/mod.rs
index 1c72324b1..f04dcfadf 100644
--- a/pumpkin/src/entity/mob/mod.rs
+++ b/pumpkin/src/entity/mob/mod.rs
@@ -165,11 +165,14 @@ impl MobEntity {
if new_b != old_b {
self.mob_flags.store(new_b, Ordering::Relaxed);
- self.living_entity.entity.send_meta_data(&[Metadata::new(
- TrackedData::MOB_FLAGS_ID,
- MetaDataType::BYTE,
- new_b,
- )]);
+ self.living_entity.entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::MOB_FLAGS_ID,
+ MetaDataType::BYTE,
+ new_b,
+ )],
+ None,
+ );
}
}
@@ -493,11 +496,14 @@ pub trait Mob: EntityBase + Send + Sync {
let entity = self.get_entity();
let is_baby = entity.age.load(std::sync::atomic::Ordering::Relaxed) < 0;
if is_baby {
- entity.send_meta_data(&[Metadata::new(
- TrackedData::BABY_ID,
- MetaDataType::BOOLEAN,
- true,
- )]);
+ entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::BABY_ID,
+ MetaDataType::BOOLEAN,
+ true,
+ )],
+ None,
+ );
}
})
}
diff --git a/pumpkin/src/entity/mob/shulker.rs b/pumpkin/src/entity/mob/shulker.rs
index cf54ec62c..32f8f235b 100644
--- a/pumpkin/src/entity/mob/shulker.rs
+++ b/pumpkin/src/entity/mob/shulker.rs
@@ -110,11 +110,14 @@ impl ShulkerEntity {
fn set_attach_face(&self, face: BlockDirection) {
self.attach_face.store(face as u8, Ordering::Relaxed);
let entity = &self.mob_entity.living_entity.entity;
- entity.send_meta_data(&[Metadata::new(
- TrackedData::ATTACH_FACE_ID,
- MetaDataType::DIRECTION,
- VarInt(face as i32),
- )]);
+ entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::ATTACH_FACE_ID,
+ MetaDataType::DIRECTION,
+ VarInt(face as i32),
+ )],
+ None,
+ );
}
pub fn get_raw_peek(&self) -> u8 {
@@ -148,11 +151,14 @@ impl ShulkerEntity {
self.peek_amount.store(amount, Ordering::Relaxed);
- entity.send_meta_data(&[Metadata::new(
- TrackedData::PEEK_ID,
- MetaDataType::BYTE,
- amount,
- )]);
+ entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::PEEK_ID,
+ MetaDataType::BYTE,
+ amount,
+ )],
+ None,
+ );
}
pub fn is_closed(&self) -> bool {
diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs
index d7d38d8d0..4a2627f54 100644
--- a/pumpkin/src/entity/mod.rs
+++ b/pumpkin/src/entity/mod.rs
@@ -179,11 +179,16 @@ pub trait EntityBase: Send + Sync + NBTStorage + std::any::Any {
let is_baby = entity.age.load(Ordering::Relaxed) < 0;
if is_baby {
- entity.send_meta_data(&[Metadata::new(
- TrackedData::BABY_ID,
- MetaDataType::BOOLEAN,
- true,
- )]);
+ let mut bedrock_meta = EntityMetadata::new();
+ bedrock_meta.set_flag(entity_data_key::FLAGS, entity_data_flag::BABY as u8, true);
+ entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::BABY_ID,
+ MetaDataType::BOOLEAN,
+ true,
+ )],
+ Some(&bedrock_meta),
+ );
}
})
}
@@ -1015,20 +1020,59 @@ impl Entity {
/// Sets a custom name for the entity, typically used with nametags
pub fn set_custom_name(&self, name: TextComponent) {
self.custom_name.store(Arc::new(Some(name.clone())));
- self.send_meta_data(&[Metadata::new(
- TrackedData::CUSTOM_NAME,
- MetaDataType::OPTIONAL_TEXT_COMPONENT,
- Some(name),
- )]);
+ let mut bedrock_meta = EntityMetadata::new();
+ bedrock_meta.set(
+ entity_data_key::NAME,
+ MetadataValue::String(name.clone().get_text()),
+ );
+ let visible = self.custom_name_visible.load(Ordering::Relaxed);
+ bedrock_meta.set_flag(
+ entity_data_key::FLAGS,
+ entity_data_flag::SHOW_NAME as u8,
+ visible,
+ );
+ bedrock_meta.set_flag(
+ entity_data_key::FLAGS,
+ entity_data_flag::ALWAYS_SHOW_NAME as u8,
+ visible,
+ );
+ self.send_meta_data(
+ &[Metadata::new(
+ TrackedData::CUSTOM_NAME,
+ MetaDataType::OPTIONAL_TEXT_COMPONENT,
+ Some(name),
+ )],
+ Some(&bedrock_meta),
+ );
}
pub fn set_custom_name_visible(&self, visible: bool) {
self.custom_name_visible.store(visible, Ordering::Relaxed);
- self.send_meta_data(&[Metadata::new(
- TrackedData::CUSTOM_NAME_VISIBLE,
- MetaDataType::BOOLEAN,
+ let mut bedrock_meta = EntityMetadata::new();
+ if let Some(name) = &**self.custom_name.load() {
+ bedrock_meta.set(
+ entity_data_key::NAME,
+ MetadataValue::String(name.clone().get_text()),
+ );
+ }
+ bedrock_meta.set_flag(
+ entity_data_key::FLAGS,
+ entity_data_flag::SHOW_NAME as u8,
visible,
- )]);
+ );
+ bedrock_meta.set_flag(
+ entity_data_key::FLAGS,
+ entity_data_flag::ALWAYS_SHOW_NAME as u8,
+ visible,
+ );
+ self.send_meta_data(
+ &[Metadata::new(
+ TrackedData::CUSTOM_NAME_VISIBLE,
+ MetaDataType::BOOLEAN,
+ visible,
+ )],
+ Some(&bedrock_meta),
+ );
}
pub fn send_velocity(&self) {
@@ -2383,11 +2427,19 @@ impl Entity {
// Only update and send metadata if the value changed
if new_frozen_ticks != old_frozen_ticks {
self.frozen_ticks.store(new_frozen_ticks, Ordering::Relaxed);
- self.send_meta_data(&[Metadata::new(
- TrackedData::TICKS_FROZEN,
- MetaDataType::INTEGER,
- VarInt(new_frozen_ticks),
- )]);
+ let mut bedrock_meta = EntityMetadata::new();
+ bedrock_meta.set(
+ entity_data_key::FREEZING_EFFECT_STRENGTH,
+ MetadataValue::Float(new_frozen_ticks as f32),
+ );
+ self.send_meta_data(
+ &[Metadata::new(
+ TrackedData::TICKS_FROZEN,
+ MetaDataType::INTEGER,
+ VarInt(new_frozen_ticks),
+ )],
+ Some(&bedrock_meta),
+ );
}
// Vanilla parity: full-freeze damage is tick-phase based.
@@ -2639,11 +2691,14 @@ impl Entity {
self.flags.fetch_and(!mask, Ordering::Relaxed) & !mask
};
- self.send_meta_data(&[Metadata::new(
- TrackedData::SHARED_FLAGS_ID,
- MetaDataType::BYTE,
- new_je_flags,
- )]);
+ self.send_meta_data(
+ &[Metadata::new(
+ TrackedData::SHARED_FLAGS_ID,
+ MetaDataType::BYTE,
+ new_je_flags,
+ )],
+ None,
+ );
if let Some(bedrock_flag) = flag.to_bedrock() {
let (key, index) = if bedrock_flag >= 64 {
@@ -2710,25 +2765,52 @@ impl Entity {
.play_sound(sound, SoundCategory::Neutral, &self.pos.load());
}
- pub fn send_meta_data(&self, meta: &[Metadata]) {
+ pub fn send_meta_data(
+ &self,
+ meta: &[Metadata],
+ bedrock_meta: Option<&EntityMetadata>,
+ ) {
let world = self.world.load();
let chunk_pos = self.chunk_pos.load();
- for player in world.players.load().iter() {
- if let ClientPlatform::Java(client) = player.client.as_ref() {
- // Apply Chebyshev distance check
- let center = player.get_entity().chunk_pos.load();
- let view_distance = crate::world::chunker::get_view_distance(player).get() as i32;
- if is_within_view_distance(chunk_pos, center, view_distance) {
- let mut buf = Vec::new();
- for m in meta {
- m.write(&mut buf, &client.version.load()).unwrap();
+ for player in world.players.load().iter() {
+ match player.client.as_ref() {
+ ClientPlatform::Java(client) => {
+ // Apply Chebyshev distance check
+ let center = player.get_entity().chunk_pos.load();
+ let view_distance =
+ crate::world::chunker::get_view_distance(player).get() as i32;
+
+ if is_within_view_distance(chunk_pos, center, view_distance) {
+ let mut buf = Vec::new();
+ for m in meta {
+ m.write(&mut buf, &client.version.load()).unwrap();
+ }
+ buf.put_u8(255);
+ player.client.try_enqueue_packet(&CSetEntityMetadata::new(
+ self.entity_id.into(),
+ buf.into(),
+ ));
+ }
+ }
+ ClientPlatform::Bedrock(client) => {
+ if let Some(bedrock_meta) = bedrock_meta {
+ let center = player.get_entity().chunk_pos.load();
+ let view_distance =
+ crate::world::chunker::get_view_distance(player).get() as i32;
+
+ if is_within_view_distance(chunk_pos, center, view_distance) {
+ client.try_enqueue_packet(&CSetActorData {
+ actor_runtime_id: VarULong(self.entity_id as u64),
+ metadata: EntityMetadata(bedrock_meta.0.clone()),
+ synced_properties: PropertySyncData {
+ int_properties: std::collections::HashMap::new(),
+ float_properties: std::collections::HashMap::new(),
+ },
+ tick: VarULong(0),
+ });
+ }
}
- buf.put_u8(255);
- player.client.try_enqueue_packet(&CSetEntityMetadata::new(
- self.entity_id.into(),
- buf.into(),
- ));
}
}
}
@@ -2744,11 +2826,16 @@ impl Entity {
self.bounding_box.store(aabb);
self.entity_dimension.store(dimension);
let pose = pose as i32;
- self.send_meta_data(&[Metadata::new(
- TrackedData::POSE,
- MetaDataType::ENTITY_POSE,
- VarInt(pose),
- )]);
+ let mut bedrock_meta = EntityMetadata::new();
+ bedrock_meta.set(entity_data_key::POSE_INDEX, MetadataValue::Int(pose));
+ self.send_meta_data(
+ &[Metadata::new(
+ TrackedData::POSE,
+ MetaDataType::ENTITY_POSE,
+ VarInt(pose),
+ )],
+ Some(&bedrock_meta),
+ );
}
}
diff --git a/pumpkin/src/entity/passive/cat.rs b/pumpkin/src/entity/passive/cat.rs
index c963af14b..c5c850164 100644
--- a/pumpkin/src/entity/passive/cat.rs
+++ b/pumpkin/src/entity/passive/cat.rs
@@ -137,17 +137,23 @@ impl Mob for CatEntity {
let entity = self.get_entity();
let is_baby = entity.age.load(Ordering::Relaxed) < 0;
if is_baby {
- entity.send_meta_data(&[Metadata::new(
- TrackedData::BABY_ID,
- MetaDataType::BOOLEAN,
- true,
- )]);
+ entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::BABY_ID,
+ MetaDataType::BOOLEAN,
+ true,
+ )],
+ None,
+ );
}
- entity.send_meta_data(&[Metadata::new(
- TrackedData::CAT_VARIANT,
- MetaDataType::CAT_VARIANT,
- VarInt(self.variant.load(Ordering::Relaxed) as i32),
- )]);
+ entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::CAT_VARIANT,
+ MetaDataType::CAT_VARIANT,
+ VarInt(self.variant.load(Ordering::Relaxed) as i32),
+ )],
+ None,
+ );
})
}
}
diff --git a/pumpkin/src/entity/passive/chicken.rs b/pumpkin/src/entity/passive/chicken.rs
index f3cbcdeff..a0b18727e 100644
--- a/pumpkin/src/entity/passive/chicken.rs
+++ b/pumpkin/src/entity/passive/chicken.rs
@@ -131,17 +131,23 @@ impl Mob for ChickenEntity {
let entity = self.get_entity();
let is_baby = entity.age.load(Ordering::Relaxed) < 0;
if is_baby {
- entity.send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
- TrackedData::BABY_ID,
- MetaDataType::BOOLEAN,
- true,
- )]);
+ entity.send_meta_data(
+ &[pumpkin_protocol::java::client::play::Metadata::new(
+ TrackedData::BABY_ID,
+ MetaDataType::BOOLEAN,
+ true,
+ )],
+ None,
+ );
}
- entity.send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
- TrackedData::VARIANT,
- MetaDataType::CHICKEN_VARIANT,
- VarInt(self.variant.load(Ordering::Relaxed) as i32),
- )]);
+ entity.send_meta_data(
+ &[pumpkin_protocol::java::client::play::Metadata::new(
+ TrackedData::VARIANT,
+ MetaDataType::CHICKEN_VARIANT,
+ VarInt(self.variant.load(Ordering::Relaxed) as i32),
+ )],
+ None,
+ );
})
}
diff --git a/pumpkin/src/entity/passive/frog.rs b/pumpkin/src/entity/passive/frog.rs
index 633fb3bba..47dda3bad 100644
--- a/pumpkin/src/entity/passive/frog.rs
+++ b/pumpkin/src/entity/passive/frog.rs
@@ -103,17 +103,23 @@ impl Mob for FrogEntity {
let entity = self.get_entity();
let is_baby = entity.age.load(Ordering::Relaxed) < 0;
if is_baby {
- entity.send_meta_data(&[Metadata::new(
- TrackedData::BABY_ID,
- MetaDataType::BOOLEAN,
- true,
- )]);
+ entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::BABY_ID,
+ MetaDataType::BOOLEAN,
+ true,
+ )],
+ None,
+ );
}
- entity.send_meta_data(&[Metadata::new(
- TrackedData::VARIANT,
- MetaDataType::FROG_VARIANT,
- VarInt(self.variant.load(Ordering::Relaxed) as i32),
- )]);
+ entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::VARIANT,
+ MetaDataType::FROG_VARIANT,
+ VarInt(self.variant.load(Ordering::Relaxed) as i32),
+ )],
+ None,
+ );
})
}
}
diff --git a/pumpkin/src/entity/passive/sheep.rs b/pumpkin/src/entity/passive/sheep.rs
index ef21d3a2a..1751a7af5 100644
--- a/pumpkin/src/entity/passive/sheep.rs
+++ b/pumpkin/src/entity/passive/sheep.rs
@@ -80,14 +80,14 @@ impl SheepEntity {
fn set_packed_and_sync(&self, byte: u8) {
self.color_and_sheared.store(byte, Ordering::Relaxed);
- self.mob_entity
- .living_entity
- .entity
- .send_meta_data(&[Metadata::new(
+ self.mob_entity.living_entity.entity.send_meta_data(
+ &[Metadata::new(
TrackedData::WOOL_ID,
MetaDataType::BYTE,
byte as i8,
- )]);
+ )],
+ None,
+ );
}
pub fn set_color(&self, color: u8) {
diff --git a/pumpkin/src/entity/passive/villager/mod.rs b/pumpkin/src/entity/passive/villager/mod.rs
index 9a07ec22a..d87a92b24 100644
--- a/pumpkin/src/entity/passive/villager/mod.rs
+++ b/pumpkin/src/entity/passive/villager/mod.rs
@@ -155,11 +155,14 @@ impl VillagerEntity {
};
// Send initial metadata
- mob_arc.get_entity().send_meta_data(&[Metadata::new(
- TrackedData::VILLAGER_DATA,
- MetaDataType::VILLAGER_DATA,
- villager_data,
- )]);
+ mob_arc.get_entity().send_meta_data(
+ &[Metadata::new(
+ TrackedData::VILLAGER_DATA,
+ MetaDataType::VILLAGER_DATA,
+ villager_data,
+ )],
+ None,
+ );
mob_arc
}
@@ -210,11 +213,14 @@ impl VillagerEntity {
*villager_data = data;
old_profession
};
- self.get_entity().send_meta_data(&[Metadata::new(
- TrackedData::VILLAGER_DATA,
- MetaDataType::VILLAGER_DATA,
- data,
- )]);
+ self.get_entity().send_meta_data(
+ &[Metadata::new(
+ TrackedData::VILLAGER_DATA,
+ MetaDataType::VILLAGER_DATA,
+ data,
+ )],
+ None,
+ );
if old_profession != data.profession {
self.generate_trades(data.profession_enum(), data.level.0)
@@ -760,11 +766,14 @@ impl Mob for VillagerEntity {
if is_sleeping {
// Wake up if bed was broken
self.get_entity().set_pose(EntityPose::Standing);
- self.get_entity().send_meta_data(&[Metadata::new(
- TrackedData::SLEEPING_POS_ID,
- MetaDataType::OPTIONAL_BLOCK_POS,
- None::,
- )]);
+ self.get_entity().send_meta_data(
+ &[Metadata::new(
+ TrackedData::SLEEPING_POS_ID,
+ MetaDataType::OPTIONAL_BLOCK_POS,
+ None::,
+ )],
+ None,
+ );
}
}
}
@@ -857,11 +866,14 @@ impl Mob for VillagerEntity {
.await;
self.get_entity().set_pose(EntityPose::Sleeping);
- self.get_entity().send_meta_data(&[Metadata::new(
- TrackedData::SLEEPING_POS_ID,
- MetaDataType::OPTIONAL_BLOCK_POS,
- Some(home_pos),
- )]);
+ self.get_entity().send_meta_data(
+ &[Metadata::new(
+ TrackedData::SLEEPING_POS_ID,
+ MetaDataType::OPTIONAL_BLOCK_POS,
+ Some(home_pos),
+ )],
+ None,
+ );
}
}
}
@@ -877,11 +889,14 @@ impl Mob for VillagerEntity {
}
self.get_entity().set_pose(EntityPose::Standing);
- self.get_entity().send_meta_data(&[Metadata::new(
- TrackedData::SLEEPING_POS_ID,
- MetaDataType::OPTIONAL_BLOCK_POS,
- None::,
- )]);
+ self.get_entity().send_meta_data(
+ &[Metadata::new(
+ TrackedData::SLEEPING_POS_ID,
+ MetaDataType::OPTIONAL_BLOCK_POS,
+ None::,
+ )],
+ None,
+ );
}
}
diff --git a/pumpkin/src/entity/passive/wolf.rs b/pumpkin/src/entity/passive/wolf.rs
index 2d1cee189..912ceb9ef 100644
--- a/pumpkin/src/entity/passive/wolf.rs
+++ b/pumpkin/src/entity/passive/wolf.rs
@@ -129,17 +129,23 @@ impl Mob for WolfEntity {
let entity = self.get_entity();
let is_baby = entity.age.load(Ordering::Relaxed) < 0;
if is_baby {
- entity.send_meta_data(&[Metadata::new(
- TrackedData::BABY_ID,
- MetaDataType::BOOLEAN,
- true,
- )]);
+ entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::BABY_ID,
+ MetaDataType::BOOLEAN,
+ true,
+ )],
+ None,
+ );
}
- entity.send_meta_data(&[Metadata::new(
- TrackedData::VARIANT,
- MetaDataType::WOLF_VARIANT,
- VarInt(self.variant.load(Ordering::Relaxed) as i32),
- )]);
+ entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::VARIANT,
+ MetaDataType::WOLF_VARIANT,
+ VarInt(self.variant.load(Ordering::Relaxed) as i32),
+ )],
+ None,
+ );
})
}
}
diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs
index e2cc0779e..478624865 100644
--- a/pumpkin/src/entity/player.rs
+++ b/pumpkin/src/entity/player.rs
@@ -1592,11 +1592,14 @@ impl Player {
self.living_entity
.entity
.set_pos(bed_head_pos.to_f64().add_raw(0.5, 0.6875, 0.5));
- self.get_entity().send_meta_data(&[Metadata::new(
- TrackedData::SLEEPING_POS_ID,
- MetaDataType::OPTIONAL_BLOCK_POS,
- Some(bed_head_pos),
- )]);
+ self.get_entity().send_meta_data(
+ &[Metadata::new(
+ TrackedData::SLEEPING_POS_ID,
+ MetaDataType::OPTIONAL_BLOCK_POS,
+ Some(bed_head_pos),
+ )],
+ None,
+ );
self.get_entity().set_velocity(Vector3::default());
self.sleeping_since.store(Some(0));
@@ -1710,11 +1713,14 @@ impl Player {
self.living_entity.entity.set_pose(EntityPose::Standing);
self.living_entity.entity.set_pos(self.position());
- self.living_entity.entity.send_meta_data(&[Metadata::new(
- TrackedData::SLEEPING_POS_ID,
- MetaDataType::OPTIONAL_BLOCK_POS,
- None::,
- )]);
+ self.living_entity.entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::SLEEPING_POS_ID,
+ MetaDataType::OPTIONAL_BLOCK_POS,
+ None::,
+ )],
+ None,
+ );
self.set_stat(
statistics::StatisticCategory::Custom,
@@ -2952,18 +2958,21 @@ impl Player {
/// Send the player's skin layers and used hand to all players.
pub fn send_client_information(&self) {
let config = self.config.load();
- self.living_entity.entity.send_meta_data(&[
- Metadata::new(
- TrackedData::PLAYER_MODE_CUSTOMISATION,
- MetaDataType::BYTE,
- config.skin_parts,
- ),
- // Metadata::new(
- // TrackedData::DATA_MAIN_ARM_ID,
- // MetaDataType::ARM,
- // VarInt(config.main_hand as u8 as i32),
- // ),
- ]);
+ self.living_entity.entity.send_meta_data(
+ &[
+ Metadata::new(
+ TrackedData::PLAYER_MODE_CUSTOMISATION,
+ MetaDataType::BYTE,
+ config.skin_parts,
+ ),
+ // Metadata::new(
+ // TrackedData::DATA_MAIN_ARM_ID,
+ // MetaDataType::ARM,
+ // VarInt(config.main_hand as u8 as i32),
+ // ),
+ ],
+ None,
+ );
}
pub async fn can_harvest(&self, state: &BlockState, block: &'static Block) -> bool {
diff --git a/pumpkin/src/entity/projectile/egg.rs b/pumpkin/src/entity/projectile/egg.rs
index d144d745b..c5b44c727 100644
--- a/pumpkin/src/entity/projectile/egg.rs
+++ b/pumpkin/src/entity/projectile/egg.rs
@@ -73,11 +73,14 @@ impl EntityBase for EggEntity {
let stack = self.item_stack.read().await;
// Sync the item stack so the client renders the correct color/variant
- entity.send_meta_data(&[Metadata::new(
- TrackedData::ITEM_STACK,
- MetaDataType::ITEM_STACK,
- &ItemStackSerializer::from(stack.clone()),
- )]);
+ entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::ITEM_STACK,
+ MetaDataType::ITEM_STACK,
+ &ItemStackSerializer::from(stack.clone()),
+ )],
+ None,
+ );
})
}
diff --git a/pumpkin/src/entity/projectile/eye_of_ender.rs b/pumpkin/src/entity/projectile/eye_of_ender.rs
index d79cc7986..3a203e50a 100644
--- a/pumpkin/src/entity/projectile/eye_of_ender.rs
+++ b/pumpkin/src/entity/projectile/eye_of_ender.rs
@@ -172,11 +172,14 @@ impl EntityBase for EyeOfEnder {
fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
Box::pin(async {
- self.entity.send_meta_data(&[Metadata::new(
- TrackedData::ITEM,
- MetaDataType::ITEM_STACK,
- &ItemStackSerializer::from(self.item_stack.lock().await.clone()),
- )]);
+ self.entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::ITEM,
+ MetaDataType::ITEM_STACK,
+ &ItemStackSerializer::from(self.item_stack.lock().await.clone()),
+ )],
+ None,
+ );
})
}
diff --git a/pumpkin/src/entity/projectile/firework_rocket.rs b/pumpkin/src/entity/projectile/firework_rocket.rs
index 6477320b4..33e40aaf3 100644
--- a/pumpkin/src/entity/projectile/firework_rocket.rs
+++ b/pumpkin/src/entity/projectile/firework_rocket.rs
@@ -68,11 +68,14 @@ impl FireworkRocketEntity {
};
// Set shooter metadata
- rocket.entity.entity.send_meta_data(&[Metadata::new(
- TrackedData::ATTACHED_TO_TARGET,
- MetaDataType::OPTIONAL_INT,
- OptionalInt(Some(shooter.entity_id)),
- )]);
+ rocket.entity.entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::ATTACHED_TO_TARGET,
+ MetaDataType::OPTIONAL_INT,
+ OptionalInt(Some(shooter.entity_id)),
+ )],
+ None,
+ );
rocket
}
diff --git a/pumpkin/src/entity/projectile/fishing_bobber.rs b/pumpkin/src/entity/projectile/fishing_bobber.rs
index 44666380b..dae55b7bc 100644
--- a/pumpkin/src/entity/projectile/fishing_bobber.rs
+++ b/pumpkin/src/entity/projectile/fishing_bobber.rs
@@ -207,11 +207,14 @@ impl FishingBobberEntity {
if ebb.intersects(&search_box) {
self.hooked_entity_id
.store(cand.get_entity().entity_id, Ordering::Relaxed);
- entity.send_meta_data(&[Metadata::new(
- TrackedData::HOOKED_ENTITY,
- MetaDataType::INT,
- cand.get_entity().entity_id + 1,
- )]);
+ entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::HOOKED_ENTITY,
+ MetaDataType::INT,
+ cand.get_entity().entity_id + 1,
+ )],
+ None,
+ );
return;
}
}
diff --git a/pumpkin/src/entity/projectile/lingering_potion.rs b/pumpkin/src/entity/projectile/lingering_potion.rs
index b97a222ce..d34d1e4a5 100644
--- a/pumpkin/src/entity/projectile/lingering_potion.rs
+++ b/pumpkin/src/entity/projectile/lingering_potion.rs
@@ -69,13 +69,16 @@ impl EntityBase for LingeringPotionEntity {
let stack = self.item_stack.read().await;
// Sync the item stack so the client renders the correct potion type
- entity.send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
- pumpkin_data::tracked_data::TrackedData::ITEM_STACK,
- pumpkin_data::meta_data_type::MetaDataType::ITEM_STACK,
- &pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer::from(
- stack.clone(),
- ),
- )]);
+ entity.send_meta_data(
+ &[pumpkin_protocol::java::client::play::Metadata::new(
+ pumpkin_data::tracked_data::TrackedData::ITEM_STACK,
+ pumpkin_data::meta_data_type::MetaDataType::ITEM_STACK,
+ &pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer::from(
+ stack.clone(),
+ ),
+ )],
+ None,
+ );
})
}
diff --git a/pumpkin/src/entity/projectile/splash_potion.rs b/pumpkin/src/entity/projectile/splash_potion.rs
index 104d37cd9..7c797e327 100644
--- a/pumpkin/src/entity/projectile/splash_potion.rs
+++ b/pumpkin/src/entity/projectile/splash_potion.rs
@@ -107,13 +107,16 @@ impl EntityBase for SplashPotionEntity {
let stack = self.item_stack.read().await;
// Sync the item stack
- entity.send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
- pumpkin_data::tracked_data::TrackedData::ITEM_STACK,
- pumpkin_data::meta_data_type::MetaDataType::ITEM_STACK,
- &pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer::from(
- stack.clone(),
- ),
- )]);
+ entity.send_meta_data(
+ &[pumpkin_protocol::java::client::play::Metadata::new(
+ pumpkin_data::tracked_data::TrackedData::ITEM_STACK,
+ pumpkin_data::meta_data_type::MetaDataType::ITEM_STACK,
+ &pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer::from(
+ stack.clone(),
+ ),
+ )],
+ None,
+ );
})
}
diff --git a/pumpkin/src/entity/tnt.rs b/pumpkin/src/entity/tnt.rs
index e9a92baae..5c4c5f940 100644
--- a/pumpkin/src/entity/tnt.rs
+++ b/pumpkin/src/entity/tnt.rs
@@ -85,18 +85,21 @@ impl EntityBase for TNTEntity {
self.entity
.set_velocity(Vector3::new(-pos.sin() * 0.02, 0.2, -pos.cos() * 0.02));
- self.entity.send_meta_data(&[
- Metadata::new(
- TrackedData::FUSE_ID,
- MetaDataType::INTEGER,
- VarInt(self.fuse.load(Relaxed) as i32),
- ),
- Metadata::new(
- TrackedData::BLOCK_STATE_ID,
- MetaDataType::BLOCK_STATE,
- VarInt(i32::from(Block::TNT.default_state.id.as_u16())),
- ),
- ]);
+ self.entity.send_meta_data(
+ &[
+ Metadata::new(
+ TrackedData::FUSE_ID,
+ MetaDataType::INTEGER,
+ VarInt(self.fuse.load(Relaxed) as i32),
+ ),
+ Metadata::new(
+ TrackedData::BLOCK_STATE_ID,
+ MetaDataType::BLOCK_STATE,
+ VarInt(i32::from(Block::TNT.default_state.id.as_u16())),
+ ),
+ ],
+ None,
+ );
})
}
diff --git a/pumpkin/src/entity/vehicle/boat.rs b/pumpkin/src/entity/vehicle/boat.rs
index c28e6dc40..387626a1a 100644
--- a/pumpkin/src/entity/vehicle/boat.rs
+++ b/pumpkin/src/entity/vehicle/boat.rs
@@ -39,10 +39,13 @@ impl BoatEntity {
self.left_paddle_moving.store(left, Ordering::Relaxed);
self.right_paddle_moving.store(right, Ordering::Relaxed);
- self.vehicle.entity.send_meta_data(&[
- Metadata::new(TrackedData::ID_PADDLE_LEFT, MetaDataType::BOOLEAN, left),
- Metadata::new(TrackedData::ID_PADDLE_RIGHT, MetaDataType::BOOLEAN, right),
- ]);
+ self.vehicle.entity.send_meta_data(
+ &[
+ Metadata::new(TrackedData::ID_PADDLE_LEFT, MetaDataType::BOOLEAN, left),
+ Metadata::new(TrackedData::ID_PADDLE_RIGHT, MetaDataType::BOOLEAN, right),
+ ],
+ None,
+ );
}
fn send_wobble_metadata(&self) {
diff --git a/pumpkin/src/entity/vehicle/vehicle.rs b/pumpkin/src/entity/vehicle/vehicle.rs
index c26824a8a..142306587 100644
--- a/pumpkin/src/entity/vehicle/vehicle.rs
+++ b/pumpkin/src/entity/vehicle/vehicle.rs
@@ -64,23 +64,29 @@ impl VehicleEntity {
}
pub fn send_wobble_metadata(&self) {
- self.entity.send_meta_data(&[
- Metadata::new(
- TrackedData::ID_HURT,
- MetaDataType::INTEGER,
- self.get_hurt_time(),
- ),
- Metadata::new(
- TrackedData::ID_HURTDIR,
- MetaDataType::INTEGER,
- self.get_hurt_dir(),
- ),
- ]);
- self.entity.send_meta_data(&[Metadata::new(
- TrackedData::ID_DAMAGE,
- MetaDataType::FLOAT,
- self.get_damage(),
- )]);
+ self.entity.send_meta_data(
+ &[
+ Metadata::new(
+ TrackedData::ID_HURT,
+ MetaDataType::INTEGER,
+ self.get_hurt_time(),
+ ),
+ Metadata::new(
+ TrackedData::ID_HURTDIR,
+ MetaDataType::INTEGER,
+ self.get_hurt_dir(),
+ ),
+ ],
+ None,
+ );
+ self.entity.send_meta_data(
+ &[Metadata::new(
+ TrackedData::ID_DAMAGE,
+ MetaDataType::FLOAT,
+ self.get_damage(),
+ )],
+ None,
+ );
}
pub async fn kill_and_drop_self(&self) {
diff --git a/pumpkin/src/item/items/ender_eye.rs b/pumpkin/src/item/items/ender_eye.rs
index 3806ad4c3..19184c7a4 100644
--- a/pumpkin/src/item/items/ender_eye.rs
+++ b/pumpkin/src/item/items/ender_eye.rs
@@ -141,11 +141,13 @@ fn find_stronghold(world: &Arc, origin: BlockPos) -> Option {
let generator = &level.world_gen;
let seed = level.seed.0;
+ let global_cache = generator.global_structure_cache()?;
+
find_nearest_structure(
origin,
&[&StructureSet::get("strongholds").unwrap().placement],
100, // max search radius in chunks, matches vanilla default
seed as i64,
- &generator.global_structure_cache,
+ global_cache,
)
}
diff --git a/pumpkin/src/lib.rs b/pumpkin/src/lib.rs
index adca28979..60e795dab 100644
--- a/pumpkin/src/lib.rs
+++ b/pumpkin/src/lib.rs
@@ -231,8 +231,8 @@ impl PumpkinServer {
});
}
- let tcp_listener = if server.basic_config.java_edition {
- let address = server.basic_config.java_edition_address;
+ let tcp_listener = if server.advanced_config.networking.java.enabled {
+ let address = server.advanced_config.networking.java.address;
// Setup the TCP server socket.
let listener = match TcpListener::bind(address).await {
Ok(l) => l,
@@ -275,7 +275,7 @@ impl PumpkinServer {
let lan_broadcast = LANBroadcast::new(
&server.advanced_config.networking.lan_broadcast,
- &server.basic_config,
+ &server.advanced_config.networking.java.motd,
);
server.spawn_task(lan_broadcast.start(addr));
}
@@ -293,9 +293,9 @@ impl PumpkinServer {
});
};
- let udp_socket = if server.basic_config.bedrock_edition {
+ let udp_socket = if server.advanced_config.networking.bedrock.enabled {
Some(Arc::new(
- UdpSocket::bind(server.basic_config.bedrock_edition_address)
+ UdpSocket::bind(server.advanced_config.networking.bedrock.address)
.await
.expect("Failed to bind UDP Socket"),
))
diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs
index 10adc9c6e..313f69bb9 100644
--- a/pumpkin/src/main.rs
+++ b/pumpkin/src/main.rs
@@ -110,34 +110,34 @@ async fn main() {
.color_named(NamedColor::Gold)
.to_pretty_console()
);
- let basic_config = &pumpkin_server.server.basic_config;
+ let advanced_config = &pumpkin_server.server.advanced_config;
info!(
"Server is now running. Connect using port: {}{}{}",
- if basic_config.java_edition {
+ if advanced_config.networking.java.enabled {
format!(
"{} {}",
TextComponent::text("Java Edition:")
.color_named(NamedColor::Yellow)
.to_pretty_console(),
- TextComponent::text(format!("{}", basic_config.java_edition_address))
+ TextComponent::text(format!("{}", advanced_config.networking.java.address))
.color_named(NamedColor::DarkBlue)
.to_pretty_console()
)
} else {
TextComponent::text(String::new()).to_pretty_console()
},
- if basic_config.java_edition && basic_config.bedrock_edition {
+ if advanced_config.networking.java.enabled && advanced_config.networking.bedrock.enabled {
" | " // Separator if both are enabled
} else {
""
},
- if basic_config.bedrock_edition {
+ if advanced_config.networking.bedrock.enabled {
format!(
"{} {}",
TextComponent::text("Bedrock Edition:")
.color_named(NamedColor::Gold)
.to_pretty_console(),
- TextComponent::text(format!("{}", basic_config.bedrock_edition_address))
+ TextComponent::text(format!("{}", advanced_config.networking.bedrock.address))
.color_named(NamedColor::DarkBlue)
.to_pretty_console()
)
diff --git a/pumpkin/src/net/bedrock/login.rs b/pumpkin/src/net/bedrock/login.rs
index 500c6eb32..7b2ced3dd 100644
--- a/pumpkin/src/net/bedrock/login.rs
+++ b/pumpkin/src/net/bedrock/login.rs
@@ -107,7 +107,8 @@ impl BedrockClient {
let compression = server
.advanced_config
.networking
- .bedrock_compression
+ .bedrock
+ .compression
.info
.clone();
self.send_game_packet(&CNetworkSettings::new(
@@ -136,13 +137,19 @@ impl BedrockClient {
server: &Server,
) -> Result, LoginError> {
let auth_payload: AuthPayload = serde_json::from_slice(&packet.jwt)?;
- let player_data = if server.basic_config.online_mode {
+ let player_data = if server.advanced_config.networking.bedrock.online_mode {
match auth_payload.authentication_type {
AuthenticationType::Full => {
verify_oidc_token_path(server, &auth_payload.token, false)?
}
AuthenticationType::SelfSigned => {
- if server.advanced_config.networking.authentication.enabled {
+ if server
+ .advanced_config
+ .networking
+ .bedrock
+ .authentication
+ .enabled
+ {
return Err(LoginError::SelfSignedNotAllowed);
}
@@ -181,7 +188,7 @@ impl BedrockClient {
profile_actions: None,
};
- if server.basic_config.bedrock_encryption {
+ if server.advanced_config.networking.bedrock.encryption {
let client_public_key = pumpkin_util::jwt::extract_cpk_from_token(&auth_payload.token)
.map_err(LoginError::ChainValidationFailed)?;
diff --git a/pumpkin/src/net/bedrock/mod.rs b/pumpkin/src/net/bedrock/mod.rs
index 034a1b3f9..7a2fc6df8 100644
--- a/pumpkin/src/net/bedrock/mod.rs
+++ b/pumpkin/src/net/bedrock/mod.rs
@@ -1290,7 +1290,7 @@ impl BedrockClient {
.as_ref()
.map_or(0, |p| p.online) as u32
};
- if player_count >= server.basic_config.max_players {
+ if player_count >= server.advanced_config.networking.bedrock.max_players {
Self::send_offline_packet(
&CNoFreeIncomingConnections::new(server.server_guid),
addr,
diff --git a/pumpkin/src/net/bedrock/play.rs b/pumpkin/src/net/bedrock/play.rs
index 9a5924923..435038844 100644
--- a/pumpkin/src/net/bedrock/play.rs
+++ b/pumpkin/src/net/bedrock/play.rs
@@ -3,7 +3,12 @@ use std::{
sync::{Arc, atomic::Ordering},
};
-use pumpkin_data::{data_component_impl::EquipmentSlot, item_stack::ItemStack};
+use pumpkin_data::{
+ data_component_impl::{
+ BlocksAttacksImpl, ConsumableImpl, EquipmentSlot, EquippableImpl, FoodImpl,
+ },
+ item_stack::ItemStack,
+};
use pumpkin_inventory::screen_handler::{InventoryPlayer, ScreenHandler};
use pumpkin_macros::send_cancellable;
use pumpkin_protocol::bedrock::{
@@ -37,7 +42,7 @@ use pumpkin_protocol::{
codec::{var_int::VarInt, var_long::VarLong, var_uint::VarUInt, var_ulong::VarULong},
java::client::play::{Animation, CEntityAnimation, CSetSelectedSlot, CSystemChatMessage},
};
-use pumpkin_util::{GameMode, math::position::BlockPos, text::TextComponent};
+use pumpkin_util::{GameMode, Hand, math::position::BlockPos, text::TextComponent};
use pumpkin_world::inventory::Inventory;
use pumpkin_world::world::BlockFlags;
@@ -47,8 +52,10 @@ use crate::{
entity::{EntityBase, player::Player},
net::{DisconnectReason, bedrock::BedrockClient},
plugin::player::{
- item_held::PlayerItemHeldEvent, player_chat::PlayerChatEvent,
+ item_held::PlayerItemHeldEvent,
+ player_chat::PlayerChatEvent,
player_command_send::PlayerCommandSendEvent,
+ player_interact_event::{InteractAction, PlayerInteractEvent},
player_toggle_flight_event::PlayerToggleFlightEvent,
},
server::{Server, seasonal_events},
@@ -152,8 +159,10 @@ impl BedrockClient {
}
let server = player.world().server.upgrade().unwrap();
- let view_distance =
- chunk_radius.clamp(2, NonZeroI32::from(server.basic_config.view_distance).get());
+ let view_distance = chunk_radius.clamp(
+ 2,
+ NonZeroI32::from(server.advanced_config.networking.bedrock.view_distance).get(),
+ );
self.enqueue_packet(&CChunkRadiusUpdate {
chunk_radius: VarInt(view_distance),
@@ -733,6 +742,105 @@ impl BedrockClient {
}
}
}
+ } else if data.action_type.0 == 1 {
+ // Click air / Use item
+ let is_creative = player.gamemode.load() == GameMode::Creative;
+ let client_stack = descriptor_to_stack(&data.item_in_hand, is_creative);
+
+ let held_item = player.inventory.held_item();
+ if !client_stack.is_empty() {
+ let mut server_stack = held_item.lock().await;
+ if server_stack.is_empty() || server_stack.item.id != client_stack.item.id {
+ *server_stack = client_stack.clone();
+ }
+ }
+
+ let event = PlayerInteractEvent::new(
+ player,
+ InteractAction::RightClickAir,
+ &pumpkin_data::Block::AIR,
+ None,
+ );
+
+ let stack_for_use = held_item.lock().await.clone();
+
+ {
+ let mut held = held_item.lock().await;
+ let mut cooldown_active = false;
+ if let Some(cooldown) = held.get_use_cooldown() {
+ let group = cooldown
+ .cooldown_group
+ .clone()
+ .unwrap_or_else(|| held.item.registry_key.to_string());
+ if player.is_on_cooldown(&group).await {
+ cooldown_active = true;
+ }
+ }
+
+ if !cooldown_active {
+ if held.get_data_component::().is_some()
+ || held.get_data_component::().is_some()
+ {
+ if let Some(food) = held.get_data_component::() {
+ if player.abilities.lock().await.invulnerable
+ || food.can_always_eat
+ || player.hunger_manager.level.load() < 20
+ {
+ player
+ .living_entity
+ .set_active_hand(
+ Hand::Left,
+ held.clone(),
+ held.get_max_use_time(),
+ )
+ .await;
+ }
+ } else {
+ player
+ .living_entity
+ .set_active_hand(
+ Hand::Left,
+ held.clone(),
+ held.get_max_use_time(),
+ )
+ .await;
+ }
+ }
+ if let Some(equippable) = held.get_data_component::() {
+ let inventory = player.inventory();
+ if !inventory
+ .is_already_equipped(&held_item, equippable.slot)
+ .await
+ {
+ player
+ .enqueue_equipment_change(equippable.slot, &held)
+ .await;
+
+ let binding = {
+ let mut equipment = inventory.entity_equipment.lock().await;
+ equipment.get_or_insert(equippable.slot)
+ };
+ let mut equip_item = binding.lock().await;
+ if equip_item.is_empty() {
+ *equip_item = held.clone();
+ held.decrement_unless_creative(player.gamemode.load(), 1);
+ } else {
+ let binding = held.clone();
+ *held = equip_item.clone();
+ *equip_item = binding;
+ }
+ }
+ }
+ }
+ }
+
+ send_cancellable! {{
+ server;
+ event;
+ 'after: {
+ server.item_registry.on_use(&stack_for_use, player).await;
+ }
+ }}
}
}
TransactionData::UseItemOnEntity(data) => {
diff --git a/pumpkin/src/net/bedrock/unconnected.rs b/pumpkin/src/net/bedrock/unconnected.rs
index 9128eb538..33d8bf5d1 100644
--- a/pumpkin/src/net/bedrock/unconnected.rs
+++ b/pumpkin/src/net/bedrock/unconnected.rs
@@ -30,12 +30,12 @@ impl BedrockClient {
let motd_string = ServerInfo {
edition: "MCPE",
// TODO The default motd is to long to be displayed completely
- motd_line_1: "Pumpkin Server",
+ motd_line_1: server.advanced_config.networking.bedrock.motd.clone(),
protocol_version: CURRENT_BEDROCK_MC_PROTOCOL,
version_name: CURRENT_BEDROCK_MC_VERSION,
player_count,
// A large number looks wreird on the client worlds window
- max_player_count: server.basic_config.max_players,
+ max_player_count: server.advanced_config.networking.bedrock.max_players,
server_unique_id: server.server_guid,
motd_line_2: server.basic_config.default_level_name.clone(),
game_mode: server.defaultgamemode.lock().await.gamemode.to_str(),
diff --git a/pumpkin/src/net/java/login.rs b/pumpkin/src/net/java/login.rs
index 3546105cb..acf8c2aaf 100644
--- a/pumpkin/src/net/java/login.rs
+++ b/pumpkin/src/net/java/login.rs
@@ -34,7 +34,7 @@ impl JavaClient {
// Don't allow new logons when the server is full.
// If `max_players` is set to zero, then there is no max player count enforced.
// TODO: If client is an operator or has otherwise suitable elevated permissions, allow the client to bypass this requirement.
- let max_players = server.basic_config.max_players;
+ let max_players = server.advanced_config.networking.java.max_players;
if max_players > 0 && server.get_player_count() >= max_players as usize {
self.kick(TextComponent::translate_cross(
translation::java::MULTIPLAYER_DISCONNECT_SERVER_FULL,
@@ -74,7 +74,7 @@ impl JavaClient {
}
}
} else {
- let id = if server.basic_config.online_mode {
+ let id = if server.advanced_config.networking.java.online_mode {
login_start.uuid
} else {
offline_uuid(&login_start.name).expect("This is very not safe and bad")
@@ -87,16 +87,19 @@ impl JavaClient {
profile_actions: None,
};
- if server.advanced_config.networking.java_compression.enabled {
+ if server.advanced_config.networking.java.compression.enabled {
self.enable_compression(server).await;
}
- if server.basic_config.encryption {
+ if server.advanced_config.networking.java.encryption {
let verify_token: [u8; 4] = rand::random();
// Wait until we have sent the encryption packet to the client
self.send_packet_now(
&server
- .encryption_request(&verify_token, server.basic_config.online_mode)
+ .encryption_request(
+ &verify_token,
+ server.advanced_config.networking.java.online_mode,
+ )
.await,
)
.await;
@@ -132,7 +135,7 @@ impl JavaClient {
return;
};
- if server.basic_config.online_mode {
+ if server.advanced_config.networking.java.online_mode {
// Online mode auth
match self
.authenticate(server, &shared_secret, &profile.name)
@@ -201,7 +204,8 @@ impl JavaClient {
let compression = server
.advanced_config
.networking
- .java_compression
+ .java
+ .compression
.info
.clone();
// We want to wait until we have sent the compression packet to the client
@@ -236,7 +240,7 @@ impl JavaClient {
username,
&hash,
&ip,
- &server.advanced_config.networking.authentication,
+ &server.advanced_config.networking.java.authentication,
)?;
// Check if the player should join
@@ -244,6 +248,7 @@ impl JavaClient {
if server
.advanced_config
.networking
+ .java
.authentication
.player_profile
.allow_banned_players
@@ -251,6 +256,7 @@ impl JavaClient {
for allowed in &server
.advanced_config
.networking
+ .java
.authentication
.player_profile
.allowed_actions
@@ -270,7 +276,12 @@ impl JavaClient {
for property in profile.properties.load().iter() {
authentication::validate_textures(
property,
- &server.advanced_config.networking.authentication.textures,
+ &server
+ .advanced_config
+ .networking
+ .java
+ .authentication
+ .textures,
)
.map_err(AuthError::TextureError)?;
}
diff --git a/pumpkin/src/net/lan_broadcast.rs b/pumpkin/src/net/lan_broadcast.rs
index d7b9fc590..d7111a668 100644
--- a/pumpkin/src/net/lan_broadcast.rs
+++ b/pumpkin/src/net/lan_broadcast.rs
@@ -1,4 +1,4 @@
-use pumpkin_config::{BasicConfiguration, LANBroadcastConfig};
+use pumpkin_config::LANBroadcastConfig;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::atomic::Ordering;
use std::time::Duration;
@@ -23,7 +23,7 @@ pub struct LANBroadcast {
impl LANBroadcast {
/// Creates a new LAN broadcast instance from the provided configuration
#[must_use]
- pub fn new(config: &LANBroadcastConfig, basic_config: &BasicConfiguration) -> Self {
+ pub fn new(config: &LANBroadcastConfig, server_motd: &str) -> Self {
let port = config.port.unwrap_or(0);
let advanced_motd = config.motd.clone().unwrap_or_default();
@@ -32,7 +32,7 @@ impl LANBroadcast {
warn!(
"Using the server MOTD as the LAN broadcast MOTD. Note that the LAN broadcast MOTD does not support multiple lines, RGB colors, or gradients so consider defining it accordingly."
);
- basic_config.motd.replace('\n', " ")
+ server_motd.replace('\n', " ")
} else {
advanced_motd
};
diff --git a/pumpkin/src/net/query.rs b/pumpkin/src/net/query.rs
index b0dbfb488..d8a9b5831 100644
--- a/pumpkin/src/net/query.rs
+++ b/pumpkin/src/net/query.rs
@@ -87,6 +87,7 @@ pub async fn start_query_handler(server: Arc, query_addr: SocketAddr) {
// Errors of packets that don't meet the format aren't returned since we won't handle them anyway
// The only errors that are thrown are because of a null terminator in a CString
// since those errors need to be corrected by server owner
+#[expect(clippy::too_many_lines)]
#[inline]
async fn handle_packet(
buf: Vec,
@@ -156,7 +157,9 @@ async fn handle_packet(
let response = CFullStatus {
session_id: packet.session_id,
- hostname: CString::new(server.basic_config.motd.as_str())?,
+ hostname: CString::new(
+ server.advanced_config.networking.java.motd.as_str(),
+ )?,
version: CString::new(CURRENT_MC_VERSION)?,
plugins: CString::new(plugins)?,
map: CString::new(
@@ -167,7 +170,8 @@ async fn handle_packet(
.map_or("world", |w| w.get_world_name()),
)?,
num_players: server.get_player_count(),
- max_players: server.basic_config.max_players as usize,
+ max_players: server.advanced_config.networking.java.max_players
+ as usize,
host_port: bound_addr.port(),
host_ip: CString::new(bound_addr.ip().to_string())?,
players,
@@ -179,7 +183,9 @@ async fn handle_packet(
} else {
let response = CBasicStatus {
session_id: packet.session_id,
- motd: CString::new(server.basic_config.motd.as_str())?,
+ motd: CString::new(
+ server.advanced_config.networking.java.motd.as_str(),
+ )?,
map: CString::new(
server
.worlds
@@ -188,7 +194,8 @@ async fn handle_packet(
.map_or("world", |w| w.get_world_name()),
)?,
num_players: server.get_player_count(),
- max_players: server.basic_config.max_players as usize,
+ max_players: server.advanced_config.networking.java.max_players
+ as usize,
host_port: bound_addr.port(),
host_ip: CString::new(bound_addr.ip().to_string())?,
};
diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs
index 76379b7bf..0935f8239 100644
--- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs
+++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs
@@ -292,7 +292,7 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
- Ok(server.basic_config.max_players)
+ Ok(server.advanced_config.networking.java.max_players)
}
async fn is_hardcore(&mut self, _rep: Resource) -> wasmtime::Result {
@@ -310,7 +310,12 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
- Ok(server.advanced_config.networking.authentication.enabled)
+ Ok(server
+ .advanced_config
+ .networking
+ .java
+ .authentication
+ .enabled)
}
async fn get_motd(&mut self, _rep: Resource) -> wasmtime::Result {
@@ -319,7 +324,7 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
- Ok(server.basic_config.motd.clone())
+ Ok(server.advanced_config.networking.java.motd.clone())
}
async fn has_whitelist(&mut self, _rep: Resource) -> wasmtime::Result {
@@ -355,7 +360,7 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
- Ok(server.basic_config.view_distance.get())
+ Ok(server.advanced_config.networking.java.view_distance.get())
}
async fn get_simulation_distance(&mut self, _rep: Resource) -> wasmtime::Result {
@@ -364,7 +369,12 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
- Ok(server.basic_config.simulation_distance.get())
+ Ok(server
+ .advanced_config
+ .networking
+ .java
+ .simulation_distance
+ .get())
}
async fn get_default_gamemode(
diff --git a/pumpkin/src/server/connection_cache.rs b/pumpkin/src/server/connection_cache.rs
index 46a60e38c..25fc7b89e 100644
--- a/pumpkin/src/server/connection_cache.rs
+++ b/pumpkin/src/server/connection_cache.rs
@@ -68,8 +68,8 @@ impl<'a> CachedBranding {
impl CachedStatus {
#[must_use]
- pub fn new(config: &BasicConfiguration) -> Self {
- let status_response = Self::build_response(config);
+ pub fn new(config: &BasicConfiguration, motd: &str, max_players: u32) -> Self {
+ let status_response = Self::build_response(config, motd, max_players);
Self {
status_response,
@@ -135,7 +135,11 @@ impl CachedStatus {
}
}
- pub fn build_response(config: &BasicConfiguration) -> StatusResponse {
+ pub fn build_response(
+ config: &BasicConfiguration,
+ motd: &str,
+ max_players: u32,
+ ) -> StatusResponse {
let favicon = if config.use_favicon {
config.favicon_path.as_ref().map_or_else(
|| {
@@ -185,11 +189,11 @@ impl CachedStatus {
protocol: LOWEST_SUPPORTED_MC_VERSION.protocol_version() as u32,
}),
players: Some(Players {
- max: config.max_players,
+ max: max_players,
online: 0,
sample: vec![],
}),
- description: config.motd.clone(),
+ description: motd.to_string(),
favicon,
// This should stay true even when reports are disabled.
// It prevents the annoying popup when joining the server.
@@ -200,6 +204,10 @@ impl CachedStatus {
impl Default for CachedStatus {
fn default() -> Self {
- Self::new(&BasicConfiguration::default())
+ Self::new(
+ &BasicConfiguration::default(),
+ "A blazingly fast Pumpkin server!",
+ 1000,
+ )
}
}
diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs
index 40c86be17..e2cab8778 100644
--- a/pumpkin/src/server/mod.rs
+++ b/pumpkin/src/server/mod.rs
@@ -199,7 +199,11 @@ impl Server {
let seed = level_info.world_gen_settings.seed;
let level_info = Arc::new(ArcSwap::new(Arc::new(level_info)));
- let listing = Mutex::new(CachedStatus::new(&basic_config));
+ let listing = Mutex::new(CachedStatus::new(
+ &basic_config,
+ &advanced_config.networking.java.motd,
+ advanced_config.networking.java.max_players,
+ ));
let defaultgamemode = Mutex::new(DefaultGamemode {
gamemode: basic_config.default_gamemode,
});
@@ -218,7 +222,7 @@ impl Server {
let tick_rate_manager = Arc::new(ServerTickRateManager::new(basic_config.tps));
let mojang_keys_task = tokio::spawn({
- let auth_config = advanced_config.networking.authentication.clone();
+ let auth_config = advanced_config.networking.java.authentication.clone();
let allow_chat = basic_config.allow_chat_reports;
async move {
if allow_chat {
@@ -351,14 +355,24 @@ impl Server {
info!("All worlds loaded successfully.");
- if server.basic_config.online_mode {
+ if server.advanced_config.networking.bedrock.online_mode {
let server_clone = server.clone();
tokio::spawn(async move {
server_clone
.bedrock_oidc_keys
.get_or_init(|| async {
tokio::task::block_in_place(|| {
- pumpkin_util::jwt::fetch_oidc_jwks().unwrap_or_else(|e| {
+ let auth = &server_clone
+ .advanced_config
+ .networking
+ .bedrock
+ .authentication;
+ pumpkin_util::jwt::fetch_oidc_jwks(
+ auth.url.as_deref(),
+ auth.connect_timeout,
+ auth.read_timeout,
+ )
+ .unwrap_or_else(|e| {
error!("Failed to fetch Bedrock OIDC keys: {e}");
(String::new(), pumpkin_util::jwt::Jwks { keys: Vec::new() })
})
diff --git a/pumpkin/src/world/chunker.rs b/pumpkin/src/world/chunker.rs
index 202c8562c..6c0ef9e42 100644
--- a/pumpkin/src/world/chunker.rs
+++ b/pumpkin/src/world/chunker.rs
@@ -14,10 +14,15 @@ use crate::{
pub fn get_view_distance(player: &Player) -> NonZeroU8 {
let server = player.world().server.upgrade().unwrap();
- player.config.load().view_distance.clamp(
- NonZeroU8::new(2).unwrap(),
- server.basic_config.view_distance,
- )
+ let max_view_distance = match player.client.as_ref() {
+ ClientPlatform::Java(_) => server.advanced_config.networking.java.view_distance,
+ ClientPlatform::Bedrock(_) => server.advanced_config.networking.bedrock.view_distance,
+ };
+ player
+ .config
+ .load()
+ .view_distance
+ .clamp(NonZeroU8::new(2).unwrap(), max_view_distance)
}
// Checks if the target chunk is within the view distance
diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs
index d4b4fc7bb..859c5a950 100644
--- a/pumpkin/src/world/mod.rs
+++ b/pumpkin/src/world/mod.rs
@@ -1849,7 +1849,13 @@ impl World {
has_start_with_map_enabled: false,
// TODO Bedrock permission level are different
permission_level: VarInt(2),
- server_simulation_distance: base_config.simulation_distance.get().into(),
+ server_simulation_distance: server
+ .advanced_config
+ .networking
+ .bedrock
+ .simulation_distance
+ .get()
+ .into(),
has_locked_behavior_pack: false,
has_locked_resource_pack: false,
is_from_locked_world_template: false,
@@ -2535,9 +2541,27 @@ impl World {
entity_id,
base_config.hardcore,
dimensions,
- base_config.max_players.try_into().unwrap(),
- base_config.view_distance.get().into(), // TODO: view distance
- base_config.simulation_distance.get().into(), // TODO: sim view dinstance
+ server
+ .advanced_config
+ .networking
+ .java
+ .max_players
+ .try_into()
+ .unwrap(),
+ server
+ .advanced_config
+ .networking
+ .java
+ .view_distance
+ .get()
+ .into(), // TODO: view distance
+ server
+ .advanced_config
+ .networking
+ .java
+ .simulation_distance
+ .get()
+ .into(), // TODO: sim view dinstance
false,
true,
false,
@@ -2555,7 +2579,7 @@ impl World {
VarInt(player.get_entity().portal_cooldown.load(Ordering::Relaxed) as i32),
self.sea_level.into(),
),
- base_config.online_mode,
+ server.advanced_config.networking.java.online_mode,
// This should stay true even when reports are disabled.
// It prevents the annoying popup when joining the server.
true,