fix: vanilla level.dat loading
This commit is contained in:
parent
e480f32265
commit
b1b2a31c49
@ -12,7 +12,9 @@
|
|||||||
and customizable experience. It prioritizes performance and player enjoyment while adhering to the core mechanics of the game.
|
and customizable experience. It prioritizes performance and player enjoyment while adhering to the core mechanics of the game.
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||

|
<video src="/assets/pumpkin_chunk_loading.webm" autoplay loop muted playsinline>
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
BIN
assets/pumpkin_chunk_loading.webm
Normal file
BIN
assets/pumpkin_chunk_loading.webm
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 23 MiB |
@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
/// Configuration for chunk storage format.
|
/// Configuration for chunk storage format.
|
||||||
///
|
///
|
||||||
/// Supports multiple chunk formats, currently `Anvil` and `Linear`.
|
/// Supports multiple chunk formats, currently `Anvil` and `Linear`.
|
||||||
#[derive(Deserialize, Default, Serialize, Clone)]
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum ChunkConfig {
|
pub enum ChunkConfig {
|
||||||
/// Standard Anvil chunk storage.
|
/// Standard Anvil chunk storage.
|
||||||
@ -16,10 +16,15 @@ pub enum ChunkConfig {
|
|||||||
Linear,
|
Linear,
|
||||||
/// Pumpkin's own optimized world format.
|
/// Pumpkin's own optimized world format.
|
||||||
#[serde(rename = "pump")]
|
#[serde(rename = "pump")]
|
||||||
#[default]
|
|
||||||
Pump,
|
Pump,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ChunkConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Anvil(AnvilChunkConfig::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Configuration for Anvil chunk storage.
|
/// Configuration for Anvil chunk storage.
|
||||||
#[derive(Deserialize, Serialize, Default, Clone)]
|
#[derive(Deserialize, Serialize, Default, Clone)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|||||||
@ -5,7 +5,6 @@ use pumpkin_util::{Difficulty, GameMode, PermissionLvl, random};
|
|||||||
use recipe::RecipeConfig;
|
use recipe::RecipeConfig;
|
||||||
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
||||||
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{fs, num::NonZeroU8, path::Path};
|
use std::{fs, num::NonZeroU8, path::Path};
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
@ -20,7 +19,9 @@ pub mod resource_pack;
|
|||||||
pub use chat::ChatConfig;
|
pub use chat::ChatConfig;
|
||||||
pub use commands::CommandsConfig;
|
pub use commands::CommandsConfig;
|
||||||
pub use networking::auth::AuthenticationConfig;
|
pub use networking::auth::AuthenticationConfig;
|
||||||
|
pub use networking::bedrock::BedrockConfig;
|
||||||
pub use networking::compression::CompressionConfig;
|
pub use networking::compression::CompressionConfig;
|
||||||
|
pub use networking::java::JavaConfig;
|
||||||
pub use networking::lan_broadcast::LANBroadcastConfig;
|
pub use networking::lan_broadcast::LANBroadcastConfig;
|
||||||
pub use networking::rcon::RCONConfig;
|
pub use networking::rcon::RCONConfig;
|
||||||
pub use plugins::PluginsConfig;
|
pub use plugins::PluginsConfig;
|
||||||
@ -64,6 +65,48 @@ impl LoadConfiguration for PumpkinConfig {
|
|||||||
fn validate(&self) {
|
fn validate(&self) {
|
||||||
self.basic.validate();
|
self.basic.validate();
|
||||||
self.advanced.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)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct BasicConfiguration {
|
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.
|
/// The seed for the world generation.
|
||||||
pub seed: Seed,
|
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.
|
/// The default game difficulty.
|
||||||
pub default_difficulty: Difficulty,
|
pub default_difficulty: Difficulty,
|
||||||
/// The op level assigned by the /op command.
|
/// The op level assigned by the /op command.
|
||||||
@ -138,12 +165,6 @@ pub struct BasicConfiguration {
|
|||||||
pub allow_end: bool,
|
pub allow_end: bool,
|
||||||
/// Whether the server is in hardcore mode.
|
/// Whether the server is in hardcore mode.
|
||||||
pub hardcore: bool,
|
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.
|
/// The server's ticks per second.
|
||||||
pub tps: f32,
|
pub tps: f32,
|
||||||
/// The default gamemode for players.
|
/// The default gamemode for players.
|
||||||
@ -169,23 +190,12 @@ pub struct BasicConfiguration {
|
|||||||
impl Default for BasicConfiguration {
|
impl Default for BasicConfiguration {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
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()),
|
seed: Seed(random::get_seed()),
|
||||||
max_players: 1000,
|
|
||||||
view_distance: NonZeroU8::new(16).unwrap(),
|
|
||||||
simulation_distance: NonZeroU8::new(10).unwrap(),
|
|
||||||
default_difficulty: Difficulty::Normal,
|
default_difficulty: Difficulty::Normal,
|
||||||
op_permission_level: PermissionLvl::Four,
|
op_permission_level: PermissionLvl::Four,
|
||||||
allow_nether: true,
|
allow_nether: true,
|
||||||
allow_end: true,
|
allow_end: true,
|
||||||
hardcore: false,
|
hardcore: false,
|
||||||
online_mode: true,
|
|
||||||
encryption: true,
|
|
||||||
motd: "A blazingly fast Pumpkin server!".to_string(),
|
|
||||||
tps: 20.0,
|
tps: 20.0,
|
||||||
default_gamemode: GameMode::Survival,
|
default_gamemode: GameMode::Survival,
|
||||||
force_gamemode: false,
|
force_gamemode: false,
|
||||||
@ -207,35 +217,7 @@ impl BasicConfiguration {
|
|||||||
PathBuf::from(&self.default_level_name)
|
PathBuf::from(&self.default_level_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate(&self) {
|
pub const 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"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AdvancedConfiguration {
|
impl AdvancedConfiguration {
|
||||||
|
|||||||
72
pumpkin-config/src/networking/bedrock.rs
Normal file
72
pumpkin-config/src/networking/bedrock.rs
Normal file
@ -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<String>,
|
||||||
|
/// 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
/// Configuration for packet compression.
|
/// Configuration for packet compression.
|
||||||
///
|
///
|
||||||
/// Controls whether network packet compression is enabled and the compression parameters.
|
/// Controls whether network packet compression is enabled and the compression parameters.
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct CompressionConfig {
|
pub struct CompressionConfig {
|
||||||
/// Whether compression is enabled.
|
/// Whether compression is enabled.
|
||||||
|
|||||||
47
pumpkin-config/src/networking/java.rs
Normal file
47
pumpkin-config/src/networking/java.rs
Normal file
@ -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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,13 +1,16 @@
|
|||||||
use auth::AuthenticationConfig;
|
|
||||||
use proxy::ProxyConfig;
|
use proxy::ProxyConfig;
|
||||||
use query::QueryConfig;
|
use query::QueryConfig;
|
||||||
use rcon::RCONConfig;
|
use rcon::RCONConfig;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{CompressionConfig, LANBroadcastConfig};
|
use crate::LANBroadcastConfig;
|
||||||
|
use bedrock::BedrockConfig;
|
||||||
|
use java::JavaConfig;
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
pub mod bedrock;
|
||||||
pub mod compression;
|
pub mod compression;
|
||||||
|
pub mod java;
|
||||||
pub mod lan_broadcast;
|
pub mod lan_broadcast;
|
||||||
pub mod proxy;
|
pub mod proxy;
|
||||||
pub mod query;
|
pub mod query;
|
||||||
@ -18,19 +21,18 @@ pub mod rcon;
|
|||||||
/// Covers authentication, query, RCON, proxying, packet compression,
|
/// Covers authentication, query, RCON, proxying, packet compression,
|
||||||
/// and LAN broadcast behaviour.
|
/// and LAN broadcast behaviour.
|
||||||
#[derive(Deserialize, Serialize, Default)]
|
#[derive(Deserialize, Serialize, Default)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct NetworkingConfig {
|
pub struct NetworkingConfig {
|
||||||
/// Authentication settings for client connections.
|
|
||||||
pub authentication: AuthenticationConfig,
|
|
||||||
/// Query protocol settings for server status requests.
|
/// Query protocol settings for server status requests.
|
||||||
pub query: QueryConfig,
|
pub query: QueryConfig,
|
||||||
/// RCON (remote console) configuration.
|
/// RCON (remote console) configuration.
|
||||||
pub rcon: RCONConfig,
|
pub rcon: RCONConfig,
|
||||||
/// Proxy-related networking settings.
|
/// Proxy-related networking settings.
|
||||||
pub proxy: ProxyConfig,
|
pub proxy: ProxyConfig,
|
||||||
/// Java Edition packet compression settings.
|
|
||||||
pub java_compression: CompressionConfig,
|
|
||||||
/// Bedrock Edition packet compression settings.
|
|
||||||
pub bedrock_compression: CompressionConfig,
|
|
||||||
/// LAN broadcast settings.
|
/// LAN broadcast settings.
|
||||||
pub lan_broadcast: LANBroadcastConfig,
|
pub lan_broadcast: LANBroadcastConfig,
|
||||||
|
/// Java Edition configuration settings.
|
||||||
|
pub java: JavaConfig,
|
||||||
|
/// Bedrock Edition configuration settings.
|
||||||
|
pub bedrock: BedrockConfig,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ impl PacketWrite for CUnconnectedPong {
|
|||||||
pub struct ServerInfo {
|
pub struct ServerInfo {
|
||||||
/// (BE or MCEE for Education Edition)
|
/// (BE or MCEE for Education Edition)
|
||||||
pub edition: &'static str,
|
pub edition: &'static str,
|
||||||
pub motd_line_1: &'static str,
|
pub motd_line_1: String,
|
||||||
pub protocol_version: u32,
|
pub protocol_version: u32,
|
||||||
pub version_name: &'static str,
|
pub version_name: &'static str,
|
||||||
pub player_count: i32,
|
pub player_count: i32,
|
||||||
|
|||||||
@ -85,6 +85,7 @@ impl PacketWrite for EntityMetadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub enum MetadataValue {
|
pub enum MetadataValue {
|
||||||
Byte(i8),
|
Byte(i8),
|
||||||
Short(i16),
|
Short(i16),
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use std::num::NonZeroI32;
|
|||||||
|
|
||||||
use pumpkin_data::item::{BedrockItem, JavaToBedrockItemMapping};
|
use pumpkin_data::item::{BedrockItem, JavaToBedrockItemMapping};
|
||||||
use pumpkin_data::item_stack::ItemStack;
|
use pumpkin_data::item_stack::ItemStack;
|
||||||
use pumpkin_nbt::Nbt;
|
use pumpkin_nbt::{Nbt, deserializer::NbtReadHelperBedrock};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
codec::{var_int::VarInt, var_uint::VarUInt},
|
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];
|
let mut user_data = vec![0u8; user_data_len as usize];
|
||||||
buf.read_exact(&mut user_data)?;
|
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 {
|
Ok(Self {
|
||||||
id,
|
id,
|
||||||
stack_size,
|
stack_size,
|
||||||
aux_value,
|
aux_value,
|
||||||
block_runtime_id,
|
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];
|
let mut user_data = vec![0u8; user_data_len as usize];
|
||||||
buf.read_exact(&mut user_data)?;
|
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 {
|
Ok(Self {
|
||||||
id,
|
id,
|
||||||
stack_size,
|
stack_size,
|
||||||
aux_value,
|
aux_value,
|
||||||
block_runtime_id,
|
block_runtime_id,
|
||||||
|
nbt_data,
|
||||||
|
place_on_blocks,
|
||||||
|
destroy_blocks,
|
||||||
|
shield_blocking_tick,
|
||||||
net_id,
|
net_id,
|
||||||
..Default::default()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,7 +58,7 @@ impl ClientPacket for CSetEntityMetadata {
|
|||||||
pub struct Metadata<T> {
|
pub struct Metadata<T> {
|
||||||
pub index: TrackedId,
|
pub index: TrackedId,
|
||||||
pub r#type: MetaDataType,
|
pub r#type: MetaDataType,
|
||||||
value: T,
|
pub value: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Metadata<T> {
|
impl<T> Metadata<T> {
|
||||||
|
|||||||
@ -227,8 +227,24 @@ pub const OIDC_AUDIENCE: &str = "api://auth-minecraft-services/multiplayer";
|
|||||||
pub const OIDC_DISCOVERY_URL: &str =
|
pub const OIDC_DISCOVERY_URL: &str =
|
||||||
"https://client.discovery.minecraft-services.net/api/v1.0/discovery/MinecraftPE/builds/1.0.0.0";
|
"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> {
|
pub fn fetch_oidc_jwks(
|
||||||
let discovery: Value = ureq::get(OIDC_DISCOVERY_URL)
|
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()
|
.call()
|
||||||
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?
|
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?
|
||||||
.body_mut()
|
.body_mut()
|
||||||
@ -248,7 +264,8 @@ pub fn fetch_oidc_jwks() -> Result<(String, Jwks), AuthError> {
|
|||||||
.ok_or_else(|| AuthError::PublicKeyBuild("Discovery missing serviceUri".into()))?;
|
.ok_or_else(|| AuthError::PublicKeyBuild("Discovery missing serviceUri".into()))?;
|
||||||
|
|
||||||
let openid_config_url = format!("{service_uri}/.well-known/openid-configuration");
|
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()
|
.call()
|
||||||
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?
|
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?
|
||||||
.body_mut()
|
.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()))?
|
.ok_or_else(|| AuthError::PublicKeyBuild("OpenID config missing issuer".into()))?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let jwks: Jwks = ureq::get(jwks_uri)
|
let jwks: Jwks = agent
|
||||||
|
.get(jwks_uri)
|
||||||
.call()
|
.call()
|
||||||
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?
|
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?
|
||||||
.body_mut()
|
.body_mut()
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use pumpkin_data::dimension::Dimension;
|
|||||||
use pumpkin_util::world_seed::Seed;
|
use pumpkin_util::world_seed::Seed;
|
||||||
use pumpkin_world::ProtoChunk;
|
use pumpkin_world::ProtoChunk;
|
||||||
use pumpkin_world::chunk_system::{Cache, Chunk, StagedChunkEnum, generate_single_chunk};
|
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::generation::get_world_gen;
|
||||||
use pumpkin_world::world::WorldPortalExt;
|
use pumpkin_world::world::WorldPortalExt;
|
||||||
use std::hint::black_box;
|
use std::hint::black_box;
|
||||||
@ -13,7 +13,6 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
const SEED: Seed = Seed(42);
|
const SEED: Seed = Seed(42);
|
||||||
|
|
||||||
// Stub portal — allows all block placements, skips mob spawning.
|
|
||||||
struct BlockRegistry;
|
struct BlockRegistry;
|
||||||
impl WorldPortalExt for BlockRegistry {
|
impl WorldPortalExt for BlockRegistry {
|
||||||
fn can_place_at(
|
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
|
fn make_world_gen() -> Box<WorldGenerator> {
|
||||||
/// and lighting stages get a 3×3 cache.
|
get_world_gen(SEED, Dimension::OVERWORLD, false, Vec::new(), String::new())
|
||||||
|
}
|
||||||
|
|
||||||
fn setup_cache(
|
fn setup_cache(
|
||||||
target_stage: StagedChunkEnum,
|
target_stage: StagedChunkEnum,
|
||||||
world_gen: &VanillaGenerator,
|
world_gen: &WorldGenerator,
|
||||||
block_registry: &dyn WorldPortalExt,
|
block_registry: &dyn WorldPortalExt,
|
||||||
) -> Cache {
|
) -> Cache {
|
||||||
let radius = target_stage.get_direct_radius();
|
let radius = target_stage.get_direct_radius();
|
||||||
@ -97,10 +98,9 @@ fn setup_cache(
|
|||||||
cache
|
cache
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Full pipeline
|
|
||||||
fn bench_full_chunk_generation(c: &mut Criterion) {
|
fn bench_full_chunk_generation(c: &mut Criterion) {
|
||||||
let dimension = Dimension::OVERWORLD;
|
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);
|
let block_registry = Arc::new(BlockRegistry);
|
||||||
|
|
||||||
c.bench_function("full_chunk_generation", |b| {
|
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) {
|
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);
|
let block_registry = Arc::new(BlockRegistry);
|
||||||
|
|
||||||
c.bench_function("biomes_generation", |b| {
|
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) {
|
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);
|
let block_registry = Arc::new(BlockRegistry);
|
||||||
|
|
||||||
c.bench_function("structure_starts_generation", |b| {
|
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) {
|
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);
|
let block_registry = Arc::new(BlockRegistry);
|
||||||
|
|
||||||
c.bench_function("structure_references_generation", |b| {
|
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) {
|
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);
|
let block_registry = Arc::new(BlockRegistry);
|
||||||
|
|
||||||
c.bench_function("noise_generation", |b| {
|
c.bench_function("noise_generation", |b| {
|
||||||
@ -216,7 +215,7 @@ fn bench_noise_generation(c: &mut Criterion) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn bench_surface_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);
|
let block_registry = Arc::new(BlockRegistry);
|
||||||
|
|
||||||
c.bench_function("surface_generation", |b| {
|
c.bench_function("surface_generation", |b| {
|
||||||
@ -243,7 +242,7 @@ fn bench_surface_generation(c: &mut Criterion) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn bench_carvers_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);
|
let block_registry = Arc::new(BlockRegistry);
|
||||||
|
|
||||||
c.bench_function("carvers_generation", |b| {
|
c.bench_function("carvers_generation", |b| {
|
||||||
@ -270,7 +269,7 @@ fn bench_carvers_generation(c: &mut Criterion) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn bench_features_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);
|
let block_registry = Arc::new(BlockRegistry);
|
||||||
|
|
||||||
c.bench_function("features_generation", |b| {
|
c.bench_function("features_generation", |b| {
|
||||||
@ -297,7 +296,7 @@ fn bench_features_generation(c: &mut Criterion) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn bench_lighting_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);
|
let block_registry = Arc::new(BlockRegistry);
|
||||||
|
|
||||||
c.bench_function("lighting_generation", |b| {
|
c.bench_function("lighting_generation", |b| {
|
||||||
|
|||||||
@ -63,7 +63,13 @@ fn bench_concurrent_chunk_generation(c: &mut Criterion) {
|
|||||||
.build()
|
.build()
|
||||||
.expect("Failed to build rayon thread pool");
|
.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 block_registry = Arc::new(BlockRegistry);
|
||||||
|
|
||||||
let bench_name = format!(
|
let bench_name = format!(
|
||||||
|
|||||||
@ -79,7 +79,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn wide_area_surface() {
|
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::{
|
use crate::generation::noise::router::multi_noise_sampler::{
|
||||||
MultiNoiseSampler, MultiNoiseSamplerBuilderOptions,
|
MultiNoiseSampler, MultiNoiseSamplerBuilderOptions,
|
||||||
};
|
};
|
||||||
@ -96,13 +96,19 @@ mod test {
|
|||||||
read_data_from_file!("../../assets/biome_no_blend_no_beard_0.json");
|
read_data_from_file!("../../assets/biome_no_blend_no_beard_0.json");
|
||||||
|
|
||||||
let seed = 0;
|
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 {
|
for data in expected_data {
|
||||||
let chunk_x = data.x;
|
let chunk_x = data.x;
|
||||||
let chunk_z = data.z;
|
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
|
// Create MultiNoiseSampler for populate_biomes
|
||||||
|
|
||||||
|
|||||||
@ -45,7 +45,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sample_value() {
|
fn sample_value() {
|
||||||
use crate::generation::generator::{GeneratorInit, VanillaGenerator};
|
use crate::generation::generator::{GeneratorInit, VanillaGenerator, WorldGenerator};
|
||||||
use crate::generation::noise::router::multi_noise_sampler::{
|
use crate::generation::noise::router::multi_noise_sampler::{
|
||||||
MultiNoiseSampler, MultiNoiseSamplerBuilderOptions,
|
MultiNoiseSampler, MultiNoiseSamplerBuilderOptions,
|
||||||
};
|
};
|
||||||
@ -59,9 +59,16 @@ mod test {
|
|||||||
let chunk_x = 0;
|
let chunk_x = 0;
|
||||||
let chunk_z = 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_x = chunk_pos::start_block_x(chunk_x);
|
||||||
let start_z = chunk_pos::start_block_z(chunk_z);
|
let start_z = chunk_pos::start_block_z(chunk_z);
|
||||||
|
|||||||
@ -922,7 +922,7 @@ mod tests {
|
|||||||
|
|
||||||
let _ = env_logger::try_init();
|
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 temp_dir = TempDir::new().unwrap();
|
||||||
let level_folder = LevelFolder {
|
let level_folder = LevelFolder {
|
||||||
@ -1198,7 +1198,7 @@ mod tests {
|
|||||||
|
|
||||||
let _ = env_logger::try_init();
|
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 temp_dir = TempDir::new().unwrap();
|
||||||
let level_folder = LevelFolder {
|
let level_folder = LevelFolder {
|
||||||
|
|||||||
@ -78,9 +78,16 @@ impl ChunkData {
|
|||||||
chunk_data: &[u8],
|
chunk_data: &[u8],
|
||||||
position: Vector2<i32>,
|
position: Vector2<i32>,
|
||||||
) -> Result<Self, ChunkParsingError> {
|
) -> Result<Self, ChunkParsingError> {
|
||||||
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::<ChunkNbt>(std::io::Cursor::new(chunk_data))
|
||||||
|
} else {
|
||||||
pumpkin_nbt::from_bytes_unnamed::<ChunkNbt>(std::io::Cursor::new(chunk_data))
|
pumpkin_nbt::from_bytes_unnamed::<ChunkNbt>(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 {
|
if chunk_data.x_pos != position.x || chunk_data.z_pos != position.y {
|
||||||
return Err(ChunkParsingError::ErrorDeserializingChunk(format!(
|
return Err(ChunkParsingError::ErrorDeserializingChunk(format!(
|
||||||
@ -222,7 +229,7 @@ impl ChunkData {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut result = Vec::new();
|
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)?;
|
.map_err(ChunkSerializingError::ErrorSerializingChunk)?;
|
||||||
|
|
||||||
Ok(result.into())
|
Ok(result.into())
|
||||||
@ -272,9 +279,16 @@ impl ChunkEntityData {
|
|||||||
chunk_data: &[u8],
|
chunk_data: &[u8],
|
||||||
position: Vector2<i32>,
|
position: Vector2<i32>,
|
||||||
) -> Result<Self, ChunkParsingError> {
|
) -> Result<Self, ChunkParsingError> {
|
||||||
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::<EntityNbt>(std::io::Cursor::new(chunk_data))
|
||||||
|
} else {
|
||||||
pumpkin_nbt::from_bytes_unnamed::<EntityNbt>(std::io::Cursor::new(chunk_data))
|
pumpkin_nbt::from_bytes_unnamed::<EntityNbt>(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
|
if chunk_entity_data.position[0] != position.x
|
||||||
|| chunk_entity_data.position[1] != position.y
|
|| chunk_entity_data.position[1] != position.y
|
||||||
@ -304,7 +318,7 @@ impl ChunkEntityData {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
pumpkin_nbt::to_bytes_unnamed(&nbt, &mut result)
|
pumpkin_nbt::to_bytes(&nbt, &mut result)
|
||||||
.map_err(ChunkSerializingError::ErrorSerializingChunk)?;
|
.map_err(ChunkSerializingError::ErrorSerializingChunk)?;
|
||||||
Ok(result.into())
|
Ok(result.into())
|
||||||
}
|
}
|
||||||
@ -476,12 +490,13 @@ struct ChunkNbt {
|
|||||||
status: ChunkStatus,
|
status: ChunkStatus,
|
||||||
#[serde(rename = "sections")]
|
#[serde(rename = "sections")]
|
||||||
sections: Vec<ChunkSectionNBT>,
|
sections: Vec<ChunkSectionNBT>,
|
||||||
|
#[serde(default)]
|
||||||
heightmaps: ChunkHeightmaps,
|
heightmaps: ChunkHeightmaps,
|
||||||
#[serde(rename = "block_ticks")]
|
#[serde(rename = "block_ticks", default)]
|
||||||
block_ticks: Vec<ScheduledTick<&'static Block>>,
|
block_ticks: Vec<ScheduledTick<&'static Block>>,
|
||||||
#[serde(rename = "fluid_ticks")]
|
#[serde(rename = "fluid_ticks", default)]
|
||||||
fluid_ticks: Vec<ScheduledTick<&'static Fluid>>,
|
fluid_ticks: Vec<ScheduledTick<&'static Fluid>>,
|
||||||
#[serde(rename = "block_entities")]
|
#[serde(rename = "block_entities", default)]
|
||||||
block_entities: Vec<NbtCompound>,
|
block_entities: Vec<NbtCompound>,
|
||||||
#[serde(rename = "isLightOn", default)]
|
#[serde(rename = "isLightOn", default)]
|
||||||
light_correct: bool,
|
light_correct: bool,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use pumpkin_data::dimension::Dimension;
|
use pumpkin_data::dimension::Dimension;
|
||||||
|
|
||||||
use crate::ProtoChunk;
|
use crate::ProtoChunk;
|
||||||
use crate::generation::generator::VanillaGenerator;
|
use crate::generation::generator::WorldGenerator;
|
||||||
use crate::world::WorldPortalExt;
|
use crate::world::WorldPortalExt;
|
||||||
use pumpkin_config::lighting::LightingEngineConfig;
|
use pumpkin_config::lighting::LightingEngineConfig;
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ use super::{Cache, Chunk, StagedChunkEnum};
|
|||||||
pub fn generate_single_chunk(
|
pub fn generate_single_chunk(
|
||||||
_dimension: &Dimension,
|
_dimension: &Dimension,
|
||||||
_biome_mixer_seed: i64,
|
_biome_mixer_seed: i64,
|
||||||
generator: &VanillaGenerator,
|
generator: &WorldGenerator,
|
||||||
block_registry: &dyn WorldPortalExt,
|
block_registry: &dyn WorldPortalExt,
|
||||||
chunk_x: i32,
|
chunk_x: i32,
|
||||||
chunk_z: i32,
|
chunk_z: i32,
|
||||||
@ -117,13 +117,13 @@ mod tests {
|
|||||||
let dimension = Dimension::OVERWORLD;
|
let dimension = Dimension::OVERWORLD;
|
||||||
let seed = Seed(42);
|
let seed = Seed(42);
|
||||||
let block_registry = Arc::new(BlockRegistry);
|
let block_registry = Arc::new(BlockRegistry);
|
||||||
let world_gen = get_world_gen(seed, dimension.clone());
|
let world_gen = get_world_gen(seed, dimension.clone(), false, Vec::new(), String::new());
|
||||||
let biome_mixer_seed = hash_seed(world_gen.random_config.seed);
|
let biome_mixer_seed = hash_seed(world_gen.seed());
|
||||||
|
|
||||||
let _ = generate_single_chunk(
|
let _ = generate_single_chunk(
|
||||||
&dimension,
|
&dimension,
|
||||||
biome_mixer_seed,
|
biome_mixer_seed,
|
||||||
&world_gen,
|
&*world_gen,
|
||||||
block_registry.as_ref(),
|
block_registry.as_ref(),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
@ -136,13 +136,13 @@ mod tests {
|
|||||||
let dimension = Dimension::OVERWORLD;
|
let dimension = Dimension::OVERWORLD;
|
||||||
let seed = Seed(1_782_124_772_053_846_960);
|
let seed = Seed(1_782_124_772_053_846_960);
|
||||||
let block_registry = Arc::new(BlockRegistry);
|
let block_registry = Arc::new(BlockRegistry);
|
||||||
let world_gen = get_world_gen(seed, dimension.clone());
|
let world_gen = get_world_gen(seed, dimension.clone(), false, Vec::new(), String::new());
|
||||||
let biome_mixer_seed = hash_seed(world_gen.random_config.seed);
|
let biome_mixer_seed = hash_seed(world_gen.seed());
|
||||||
|
|
||||||
let chunk = generate_single_chunk(
|
let chunk = generate_single_chunk(
|
||||||
&dimension,
|
&dimension,
|
||||||
biome_mixer_seed,
|
biome_mixer_seed,
|
||||||
&world_gen,
|
&*world_gen,
|
||||||
block_registry.as_ref(),
|
block_registry.as_ref(),
|
||||||
31,
|
31,
|
||||||
-12,
|
-12,
|
||||||
|
|||||||
@ -350,7 +350,7 @@ impl Cache {
|
|||||||
pub fn advance(
|
pub fn advance(
|
||||||
&mut self,
|
&mut self,
|
||||||
stage: StagedChunkEnum,
|
stage: StagedChunkEnum,
|
||||||
generator: &generator::VanillaGenerator,
|
generator: &generator::WorldGenerator,
|
||||||
block_registry: &dyn WorldPortalExt,
|
block_registry: &dyn WorldPortalExt,
|
||||||
lighting_config: &LightingEngineConfig,
|
lighting_config: &LightingEngineConfig,
|
||||||
) {
|
) {
|
||||||
@ -362,35 +362,74 @@ impl Cache {
|
|||||||
}
|
}
|
||||||
match stage {
|
match stage {
|
||||||
StagedChunkEnum::Empty => panic!("empty stage"),
|
StagedChunkEnum::Empty => panic!("empty stage"),
|
||||||
StagedChunkEnum::StructureStart => {
|
StagedChunkEnum::StructureStart => match generator {
|
||||||
self.chunks[mid]
|
generator::WorldGenerator::Noise(noise_gen) => {
|
||||||
.get_proto_chunk_mut()
|
self.chunks[mid]
|
||||||
.set_structure_starts(generator);
|
.get_proto_chunk_mut()
|
||||||
}
|
.set_structure_starts(noise_gen);
|
||||||
StagedChunkEnum::StructureReferences => {
|
}
|
||||||
self.chunks[mid]
|
generator::WorldGenerator::Flat(_) => {}
|
||||||
.get_proto_chunk_mut()
|
},
|
||||||
.set_structure_references(generator);
|
StagedChunkEnum::StructureReferences => match generator {
|
||||||
}
|
generator::WorldGenerator::Noise(noise_gen) => {
|
||||||
StagedChunkEnum::Biomes => self.chunks[mid]
|
self.chunks[mid]
|
||||||
.get_proto_chunk_mut()
|
.get_proto_chunk_mut()
|
||||||
.step_to_biomes(generator),
|
.set_structure_references(noise_gen);
|
||||||
StagedChunkEnum::Noise => self.chunks[mid]
|
}
|
||||||
.get_proto_chunk_mut()
|
generator::WorldGenerator::Flat(_) => {}
|
||||||
.step_to_noise(generator),
|
},
|
||||||
StagedChunkEnum::Surface => self.chunks[mid]
|
StagedChunkEnum::Biomes => match generator {
|
||||||
.get_proto_chunk_mut()
|
generator::WorldGenerator::Noise(noise_gen) => {
|
||||||
.step_to_surface(generator),
|
self.chunks[mid]
|
||||||
StagedChunkEnum::Carvers => self.chunks[mid]
|
.get_proto_chunk_mut()
|
||||||
.get_proto_chunk_mut()
|
.step_to_biomes(noise_gen);
|
||||||
.step_to_carvers(generator),
|
}
|
||||||
StagedChunkEnum::Features => {
|
generator::WorldGenerator::Flat(flat_gen) => {
|
||||||
ProtoChunk::generate_features_and_structure(
|
flat_gen.step_to_biomes(self.chunks[mid].get_proto_chunk_mut());
|
||||||
self,
|
}
|
||||||
block_registry,
|
},
|
||||||
&generator.random_config,
|
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 => {
|
StagedChunkEnum::Lighting => {
|
||||||
let mut engine = crate::lighting::LightEngine::new();
|
let mut engine = crate::lighting::LightEngine::new();
|
||||||
engine.initialize_light(self, lighting_config);
|
engine.initialize_light(self, lighting_config);
|
||||||
@ -410,7 +449,7 @@ impl Cache {
|
|||||||
let chunk = self.chunks[mid].get_proto_chunk_mut();
|
let chunk = self.chunks[mid].get_proto_chunk_mut();
|
||||||
debug_assert_eq!(chunk.stage, StagedChunkEnum::Spawn);
|
debug_assert_eq!(chunk.stage, StagedChunkEnum::Spawn);
|
||||||
chunk.stage = StagedChunkEnum::Full;
|
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 => {}
|
StagedChunkEnum::None => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1272,7 +1272,7 @@ impl GenerationSchedule {
|
|||||||
let send_chunk = self.send_chunk.clone();
|
let send_chunk = self.send_chunk.clone();
|
||||||
let level = level.clone();
|
let level = level.clone();
|
||||||
let settings =
|
let settings =
|
||||||
GenerationSettings::from_dimension(&level.world_gen.dimension);
|
GenerationSettings::from_dimension(level.world_gen.dimension());
|
||||||
|
|
||||||
pool.spawn(move || {
|
pool.spawn(move || {
|
||||||
let result = crate::chunk_system::worker_logic::run_generation(
|
let result = crate::chunk_system::worker_logic::run_generation(
|
||||||
|
|||||||
@ -179,7 +179,10 @@ pub async fn io_write_work(recv: AsyncRx<Vec<(ChunkPos, Chunk)>>, level: Arc<Lev
|
|||||||
Chunk::Level(chunk) => vec.push((pos, chunk)),
|
Chunk::Level(chunk) => vec.push((pos, chunk)),
|
||||||
Chunk::Proto(chunk) => {
|
Chunk::Proto(chunk) => {
|
||||||
let mut temp = 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!() };
|
let Chunk::Level(chunk) = temp else { panic!() };
|
||||||
vec.push((pos, chunk));
|
vec.push((pos, chunk));
|
||||||
}
|
}
|
||||||
@ -262,7 +265,7 @@ pub fn generation_work(
|
|||||||
send: &crossfire::compat::MTx<(ChunkPos, RecvChunk)>,
|
send: &crossfire::compat::MTx<(ChunkPos, RecvChunk)>,
|
||||||
level: &Arc<Level>,
|
level: &Arc<Level>,
|
||||||
) {
|
) {
|
||||||
let settings = GenerationSettings::from_dimension(&level.world_gen.dimension);
|
let settings = GenerationSettings::from_dimension(level.world_gen.dimension());
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let Ok((pos, cache, stage)) = recv.recv() else {
|
let Ok((pos, cache, stage)) = recv.recv() else {
|
||||||
|
|||||||
@ -365,11 +365,14 @@ fn with_carve_run_options<F>(
|
|||||||
) where
|
) where
|
||||||
F: FnOnce(&mut CarveRun<'_, '_>),
|
F: FnOnce(&mut CarveRun<'_, '_>),
|
||||||
{
|
{
|
||||||
use crate::generation::generator::{GeneratorInit, VanillaGenerator};
|
use crate::generation::generator::{GeneratorInit, VanillaGenerator, WorldGenerator};
|
||||||
use pumpkin_util::world_seed::Seed;
|
use pumpkin_util::world_seed::Seed;
|
||||||
|
|
||||||
let generator = VanillaGenerator::new(Seed(42), dimension);
|
let world_gen = WorldGenerator::Noise(Box::new(VanillaGenerator::new(Seed(42), dimension)));
|
||||||
let mut chunk = ProtoChunk::new(0, 0, &generator);
|
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_x = crate::generation::positions::chunk_pos::start_block_x(chunk.x);
|
||||||
let start_z = crate::generation::positions::chunk_pos::start_block_z(chunk.z);
|
let start_z = crate::generation::positions::chunk_pos::start_block_z(chunk.z);
|
||||||
|
|||||||
70
pumpkin-world/src/generation/generator/flat.rs
Normal file
70
pumpkin-world/src/generation/generator/flat.rs
Normal file
@ -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<FlatLayer>,
|
||||||
|
pub biome: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FlatGenerator {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(
|
||||||
|
seed: Seed,
|
||||||
|
dimension: Dimension,
|
||||||
|
layers: Vec<FlatLayer>,
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,6 +18,47 @@ pub trait GeneratorInit {
|
|||||||
use pumpkin_data::structures::{StructurePlacementCalculator, StructureSet};
|
use pumpkin_data::structures::{StructurePlacementCalculator, StructureSet};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
|
pub mod flat;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct FlatLayer {
|
||||||
|
pub block: String,
|
||||||
|
pub height: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum WorldGenerator {
|
||||||
|
Noise(Box<VanillaGenerator>),
|
||||||
|
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 struct VanillaGenerator {
|
||||||
pub random_config: GlobalRandomConfig,
|
pub random_config: GlobalRandomConfig,
|
||||||
pub base_router: ProtoNoiseRouters,
|
pub base_router: ProtoNoiseRouters,
|
||||||
|
|||||||
@ -25,9 +25,22 @@ use pumpkin_util::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_world_gen(seed: Seed, dimension: Dimension) -> Box<VanillaGenerator> {
|
pub fn get_world_gen(
|
||||||
// TODO decide which WorldGenerator to pick based on config.
|
seed: Seed,
|
||||||
Box::new(VanillaGenerator::new(seed, dimension))
|
dimension: Dimension,
|
||||||
|
is_flat: bool,
|
||||||
|
flat_layers: Vec<generator::FlatLayer>,
|
||||||
|
flat_biome: String,
|
||||||
|
) -> Box<generator::WorldGenerator> {
|
||||||
|
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 {
|
pub struct GlobalRandomConfig {
|
||||||
|
|||||||
@ -176,17 +176,28 @@ impl TerrainCache {
|
|||||||
|
|
||||||
impl ProtoChunk {
|
impl ProtoChunk {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(x: i32, z: i32, generator: &super::generator::VanillaGenerator) -> Self {
|
pub fn new(x: i32, z: i32, generator: &super::generator::WorldGenerator) -> Self {
|
||||||
let dimension = &generator.dimension;
|
let dimension = generator.dimension();
|
||||||
let height = dimension.logical_height as u16;
|
let height = dimension.logical_height as u16;
|
||||||
let section_count = (height as usize) / 16;
|
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];
|
let default_heightmap = [i16::MIN; CHUNK_AREA];
|
||||||
Self {
|
Self {
|
||||||
x,
|
x,
|
||||||
z,
|
z,
|
||||||
default_block: generator.default_block,
|
default_block,
|
||||||
biome_mixer_seed: generator.biome_mixer_seed,
|
biome_mixer_seed,
|
||||||
flat_block_map: vec![BlockStateId::AIR; CHUNK_AREA * height as usize]
|
flat_block_map: vec![BlockStateId::AIR; CHUNK_AREA * height as usize]
|
||||||
.into_boxed_slice(),
|
.into_boxed_slice(),
|
||||||
flat_biome_map: vec![
|
flat_biome_map: vec![
|
||||||
@ -225,7 +236,7 @@ impl ProtoChunk {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn from_chunk_data(
|
pub fn from_chunk_data(
|
||||||
chunk_data: &ChunkData,
|
chunk_data: &ChunkData,
|
||||||
generator: &super::generator::VanillaGenerator,
|
generator: &super::generator::WorldGenerator,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut proto_chunk = Self::new(chunk_data.x, chunk_data.z, generator);
|
let mut proto_chunk = Self::new(chunk_data.x, chunk_data.z, generator);
|
||||||
|
|
||||||
@ -518,7 +529,6 @@ impl ProtoChunk {
|
|||||||
#[expect(clippy::too_many_lines)]
|
#[expect(clippy::too_many_lines)]
|
||||||
pub fn step_to_noise(&mut self, generator: &super::generator::VanillaGenerator) {
|
pub fn step_to_noise(&mut self, generator: &super::generator::VanillaGenerator) {
|
||||||
debug_assert_eq!(self.stage, StagedChunkEnum::StructureReferences);
|
debug_assert_eq!(self.stage, StagedChunkEnum::StructureReferences);
|
||||||
|
|
||||||
let settings = generator.settings;
|
let settings = generator.settings;
|
||||||
let generation_shape = &settings.shape;
|
let generation_shape = &settings.shape;
|
||||||
let horizontal_cell_count = CHUNK_DIM / generation_shape.horizontal_cell_block_count();
|
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) {
|
pub fn step_to_carvers(&mut self, generator: &super::generator::VanillaGenerator) {
|
||||||
debug_assert_eq!(self.stage, StagedChunkEnum::Surface);
|
debug_assert_eq!(self.stage, StagedChunkEnum::Surface);
|
||||||
|
|
||||||
super::carver::carve(self, generator);
|
super::carver::carve(self, generator);
|
||||||
|
|
||||||
self.stage = StagedChunkEnum::Carvers;
|
self.stage = StagedChunkEnum::Carvers;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use crate::chunk::format::linear::LinearV2File;
|
use crate::chunk::format::linear::LinearV2File;
|
||||||
use crate::chunk::format::pump::PumpFile;
|
use crate::chunk::format::pump::PumpFile;
|
||||||
use crate::chunk_system::{ChunkListener, ChunkLoading, GenerationSchedule, LevelChannel};
|
use crate::chunk_system::{ChunkListener, ChunkLoading, GenerationSchedule, LevelChannel};
|
||||||
use crate::generation::generator::VanillaGenerator;
|
use crate::generation::generator::WorldGenerator;
|
||||||
use crate::lighting::DynamicLightEngine;
|
use crate::lighting::DynamicLightEngine;
|
||||||
use crate::{
|
use crate::{
|
||||||
chunk::{
|
chunk::{
|
||||||
@ -77,7 +77,7 @@ pub struct Level {
|
|||||||
pub chunk_saver: Arc<dyn FileIO<Data = SyncChunk>>,
|
pub chunk_saver: Arc<dyn FileIO<Data = SyncChunk>>,
|
||||||
entity_saver: Arc<dyn FileIO<Data = SyncEntityChunk>>,
|
entity_saver: Arc<dyn FileIO<Data = SyncEntityChunk>>,
|
||||||
|
|
||||||
pub world_gen: Arc<VanillaGenerator>,
|
pub world_gen: Arc<WorldGenerator>,
|
||||||
|
|
||||||
/// Handles runtime lighting updates
|
/// Handles runtime lighting updates
|
||||||
pub light_engine: DynamicLightEngine,
|
pub light_engine: DynamicLightEngine,
|
||||||
@ -125,6 +125,7 @@ pub struct LevelFolder {
|
|||||||
|
|
||||||
impl Level {
|
impl Level {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
pub fn from_root_folder(
|
pub fn from_root_folder(
|
||||||
level_config: &LevelConfig,
|
level_config: &LevelConfig,
|
||||||
root_folder: PathBuf,
|
root_folder: PathBuf,
|
||||||
@ -132,11 +133,18 @@ impl Level {
|
|||||||
dimension: Dimension,
|
dimension: Dimension,
|
||||||
gen_pool: Option<Arc<rayon::ThreadPool>>,
|
gen_pool: Option<Arc<rayon::ThreadPool>>,
|
||||||
) -> Arc<Self> {
|
) -> Arc<Self> {
|
||||||
let (namespace, name) = match dimension.minecraft_name.split_once(':') {
|
let dim_folder = if dimension.minecraft_name == Dimension::OVERWORLD.minecraft_name
|
||||||
Some((ns, n)) => (ns, n),
|
|| dimension.minecraft_name == Dimension::THE_NETHER.minecraft_name
|
||||||
None => ("minecraft", dimension.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 region_folder = dim_folder.join("region");
|
||||||
let entities_folder = dim_folder.join("entities");
|
let entities_folder = dim_folder.join("entities");
|
||||||
@ -154,8 +162,62 @@ impl Level {
|
|||||||
poi_folder,
|
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 seed = Seed(seed as u64);
|
||||||
let world_gen = get_world_gen(seed, dimension).into();
|
let world_gen: Arc<WorldGenerator> = Arc::from(get_world_gen(
|
||||||
|
seed,
|
||||||
|
dimension,
|
||||||
|
is_flat,
|
||||||
|
flat_layers,
|
||||||
|
flat_biome,
|
||||||
|
));
|
||||||
|
|
||||||
let chunk_saver: Arc<dyn FileIO<Data = SyncChunk>> = match &level_config.chunk {
|
let chunk_saver: Arc<dyn FileIO<Data = SyncChunk>> = match &level_config.chunk {
|
||||||
ChunkConfig::Linear => Arc::new(ChunkFileManager::<LinearV2File<ChunkData>>::new(())),
|
ChunkConfig::Linear => Arc::new(ChunkFileManager::<LinearV2File<ChunkData>>::new(())),
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
#![allow(clippy::unreachable)]
|
||||||
|
|
||||||
use pumpkin_data::{
|
use pumpkin_data::{
|
||||||
Block, BlockState, chunk_gen_settings::GenerationSettings, dimension::Dimension,
|
Block, BlockState, chunk_gen_settings::GenerationSettings, dimension::Dimension,
|
||||||
};
|
};
|
||||||
@ -57,15 +59,21 @@ pub fn bench_create_and_populate_noise(
|
|||||||
_terrain_cache: &TerrainCache,
|
_terrain_cache: &TerrainCache,
|
||||||
_default_block: &'static BlockState,
|
_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::{
|
use crate::generation::noise::router::surface_height_sampler::{
|
||||||
SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions,
|
SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions,
|
||||||
};
|
};
|
||||||
use crate::generation::proto_chunk::StandardChunkFluidLevelSampler;
|
use crate::generation::proto_chunk::StandardChunkFluidLevelSampler;
|
||||||
use pumpkin_util::world_seed::Seed;
|
use pumpkin_util::world_seed::Seed;
|
||||||
|
|
||||||
let generator = VanillaGenerator::new(Seed(random_config.seed), Dimension::OVERWORLD);
|
let world_gen = WorldGenerator::Noise(Box::new(VanillaGenerator::new(
|
||||||
let mut chunk = ProtoChunk::new(0, 0, &generator);
|
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
|
// Create noise sampler and other required components
|
||||||
let settings = generator.settings;
|
let settings = generator.settings;
|
||||||
@ -119,7 +127,7 @@ pub fn bench_create_and_populate_noise(
|
|||||||
);
|
);
|
||||||
|
|
||||||
chunk.populate_noise(
|
chunk.populate_noise(
|
||||||
&generator,
|
generator,
|
||||||
&mut noise_sampler,
|
&mut noise_sampler,
|
||||||
&generator.random_config.ore_random_deriver,
|
&generator.random_config.ore_random_deriver,
|
||||||
&mut surface_height_estimate_sampler,
|
&mut surface_height_estimate_sampler,
|
||||||
@ -133,15 +141,21 @@ pub fn bench_create_and_populate_biome(
|
|||||||
_terrain_cache: &TerrainCache,
|
_terrain_cache: &TerrainCache,
|
||||||
_default_block: &'static BlockState,
|
_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::{
|
use crate::generation::noise::router::multi_noise_sampler::{
|
||||||
MultiNoiseSampler, MultiNoiseSamplerBuilderOptions,
|
MultiNoiseSampler, MultiNoiseSamplerBuilderOptions,
|
||||||
};
|
};
|
||||||
use crate::generation::{biome_coords, positions::chunk_pos};
|
use crate::generation::{biome_coords, positions::chunk_pos};
|
||||||
use pumpkin_util::world_seed::Seed;
|
use pumpkin_util::world_seed::Seed;
|
||||||
|
|
||||||
let generator = VanillaGenerator::new(Seed(random_config.seed), Dimension::OVERWORLD);
|
let world_gen = WorldGenerator::Noise(Box::new(VanillaGenerator::new(
|
||||||
let mut chunk = ProtoChunk::new(0, 0, &generator);
|
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
|
// Create multi-noise sampler
|
||||||
let start_x = chunk_pos::start_block_x(0);
|
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 =
|
let mut multi_noise_sampler =
|
||||||
MultiNoiseSampler::generate(&generator.base_router.multi_noise, &multi_noise_config);
|
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(
|
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,
|
_terrain_cache: &TerrainCache,
|
||||||
_default_block: &'static BlockState,
|
_default_block: &'static BlockState,
|
||||||
) {
|
) {
|
||||||
use crate::generation::generator::{GeneratorInit, VanillaGenerator};
|
use crate::generation::generator::{GeneratorInit, VanillaGenerator, WorldGenerator};
|
||||||
use crate::generation::noise::router::{
|
use crate::generation::noise::router::{
|
||||||
multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions},
|
multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions},
|
||||||
surface_height_sampler::{
|
surface_height_sampler::{
|
||||||
@ -179,8 +193,14 @@ pub fn bench_create_and_populate_noise_with_surface(
|
|||||||
use crate::generation::proto_chunk::StandardChunkFluidLevelSampler;
|
use crate::generation::proto_chunk::StandardChunkFluidLevelSampler;
|
||||||
use pumpkin_util::world_seed::Seed;
|
use pumpkin_util::world_seed::Seed;
|
||||||
|
|
||||||
let generator = VanillaGenerator::new(Seed(random_config.seed), Dimension::OVERWORLD);
|
let world_gen = WorldGenerator::Noise(Box::new(VanillaGenerator::new(
|
||||||
let mut chunk = ProtoChunk::new(0, 0, &generator);
|
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
|
// Create all required components
|
||||||
let settings = generator.settings;
|
let settings = generator.settings;
|
||||||
@ -241,12 +261,12 @@ pub fn bench_create_and_populate_noise_with_surface(
|
|||||||
&surface_config,
|
&surface_config,
|
||||||
);
|
);
|
||||||
|
|
||||||
chunk.populate_biomes(&generator, &mut multi_noise_sampler);
|
chunk.populate_biomes(generator, &mut multi_noise_sampler);
|
||||||
chunk.populate_noise(
|
chunk.populate_noise(
|
||||||
&generator,
|
generator,
|
||||||
&mut noise_sampler,
|
&mut noise_sampler,
|
||||||
&generator.random_config.ore_random_deriver,
|
&generator.random_config.ore_random_deriver,
|
||||||
&mut surface_height_estimate_sampler,
|
&mut surface_height_estimate_sampler,
|
||||||
);
|
);
|
||||||
chunk.build_surface(&generator, &mut surface_height_estimate_sampler);
|
chunk.build_surface(generator, &mut surface_height_estimate_sampler);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -177,7 +177,7 @@ impl PoiRegion {
|
|||||||
/// Compress chunk data to bytes
|
/// Compress chunk data to bytes
|
||||||
fn compress_chunk_data(chunk_data: &PoiChunkData) -> std::io::Result<Vec<u8>> {
|
fn compress_chunk_data(chunk_data: &PoiChunkData) -> std::io::Result<Vec<u8>> {
|
||||||
let mut uncompressed = Vec::new();
|
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()))?;
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
|
||||||
|
|
||||||
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
|
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
|
||||||
@ -191,8 +191,16 @@ impl PoiRegion {
|
|||||||
let mut uncompressed = Vec::new();
|
let mut uncompressed = Vec::new();
|
||||||
decoder.read_to_end(&mut uncompressed)?;
|
decoder.read_to_end(&mut uncompressed)?;
|
||||||
|
|
||||||
pumpkin_nbt::from_bytes_unnamed(Cursor::new(uncompressed))
|
let is_named = uncompressed.len() >= 3
|
||||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))
|
&& 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<()> {
|
pub fn save(&mut self, path: &Path) -> std::io::Result<()> {
|
||||||
|
|||||||
@ -12,6 +12,12 @@ use serde::{Deserialize, Serialize};
|
|||||||
use crate::world_info::{
|
use crate::world_info::{
|
||||||
MAXIMUM_SUPPORTED_LEVEL_VERSION, MAXIMUM_SUPPORTED_WORLD_DATA_VERSION,
|
MAXIMUM_SUPPORTED_LEVEL_VERSION, MAXIMUM_SUPPORTED_WORLD_DATA_VERSION,
|
||||||
MINIMUM_SUPPORTED_LEVEL_VERSION, MINIMUM_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};
|
use super::{LevelData, WorldInfoError, WorldInfoReader, WorldInfoWriter};
|
||||||
@ -89,10 +95,43 @@ impl WorldInfoReader for AnvilLevelInfo {
|
|||||||
|
|
||||||
check_file_data_version(&buf)?;
|
check_file_data_version(&buf)?;
|
||||||
check_file_level_version(&buf)?;
|
check_file_level_version(&buf)?;
|
||||||
let info = pumpkin_nbt::from_bytes::<LevelDat>(Cursor::new(buf))
|
let mut info = pumpkin_nbt::from_bytes::<LevelDat>(Cursor::new(buf))
|
||||||
.map_err(|e| WorldInfoError::DeserializationError(e.to_string()))?;
|
.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)
|
Ok(info.data)
|
||||||
}
|
}
|
||||||
@ -112,20 +151,76 @@ impl WorldInfoWriter for AnvilLevelInfo {
|
|||||||
level_data.last_played = since_the_epoch.as_millis() as i64;
|
level_data.last_played = since_the_epoch.as_millis() as i64;
|
||||||
let level = LevelDat { data: level_data };
|
let level = LevelDat { data: level_data };
|
||||||
|
|
||||||
// open file
|
// ── Write level.dat ───────────────────────────────────────────────────
|
||||||
let path = level_folder.join(LEVEL_DAT_FILE_NAME);
|
let path = level_folder.join(LEVEL_DAT_FILE_NAME);
|
||||||
let world_info_file = File::create(path)?;
|
let world_info_file = File::create(path)?;
|
||||||
|
|
||||||
// write compressed data into file
|
|
||||||
let compression_writer = GzEncoder::new(world_info_file, Compression::best());
|
let compression_writer = GzEncoder::new(world_info_file, Compression::best());
|
||||||
// TODO: Proper error handling
|
// TODO: Proper error handling
|
||||||
pumpkin_nbt::to_bytes(&level, compression_writer)
|
pumpkin_nbt::to_bytes(&level, compression_writer)
|
||||||
.expect("Failed to write level.dat to disk");
|
.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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||||
pub struct LevelDat {
|
pub struct LevelDat {
|
||||||
// This tag contains all the level data.
|
// This tag contains all the level data.
|
||||||
#[serde(rename = "Data")]
|
#[serde(rename = "Data")]
|
||||||
@ -267,7 +362,13 @@ mod test {
|
|||||||
let level_dat_again: LevelDat =
|
let level_dat_again: LevelDat =
|
||||||
from_bytes(Cursor::new(serialized)).expect("Failed to decode from bytes");
|
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]
|
#[test]
|
||||||
|
|||||||
417
pumpkin-world/src/world_info/data_files.rs
Normal file
417
pumpkin-world/src/world_info/data_files.rs
Normal file
@ -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<T> {
|
||||||
|
#[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<String, DimensionClock>,
|
||||||
|
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 `<world>/data/minecraft/` directory exists.
|
||||||
|
pub fn ensure_minecraft_data_dir(level_folder: &Path) -> Result<PathBuf, WorldInfoError> {
|
||||||
|
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::<DataFileRoot<WeatherData>, _>(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<WorldGenSettings> {
|
||||||
|
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::<DataFileRoot<WorldGenSettingsData>, _>(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::<DataFileRoot<WanderingTraderData>, _>(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()))
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub mod anvil;
|
pub mod anvil;
|
||||||
|
pub mod data_files;
|
||||||
|
|
||||||
// Constraint: disk biome palette serialization changed in 1.21.5
|
// Constraint: disk biome palette serialization changed in 1.21.5
|
||||||
pub const MINIMUM_SUPPORTED_WORLD_DATA_VERSION: i32 = 4435; // 1.21.9
|
pub const MINIMUM_SUPPORTED_WORLD_DATA_VERSION: i32 = 4435; // 1.21.9
|
||||||
@ -48,21 +49,14 @@ pub struct LevelData {
|
|||||||
pub border_warning_blocks: f64,
|
pub border_warning_blocks: f64,
|
||||||
#[serde(default = "default_border_warning_time")]
|
#[serde(default = "default_border_warning_time")]
|
||||||
pub border_warning_time: f64,
|
pub border_warning_time: f64,
|
||||||
#[serde(rename = "clearWeatherTime", default)]
|
|
||||||
pub clear_weather_time: i32,
|
|
||||||
#[serde(default = "default_data_packs")]
|
#[serde(default = "default_data_packs")]
|
||||||
pub data_packs: DataPacks,
|
pub data_packs: DataPacks,
|
||||||
pub data_version: i32,
|
pub data_version: i32,
|
||||||
#[serde(default)]
|
|
||||||
pub day_time: i64,
|
|
||||||
#[serde(with = "serde_enum_as_integer", default = "default_difficulty")]
|
#[serde(with = "serde_enum_as_integer", default = "default_difficulty")]
|
||||||
pub difficulty: Difficulty,
|
pub difficulty: Difficulty,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub difficulty_locked: bool,
|
pub difficulty_locked: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub game_rules: GameRuleRegistry,
|
|
||||||
pub world_gen_settings: WorldGenSettings,
|
|
||||||
#[serde(default)]
|
|
||||||
pub last_played: i64,
|
pub last_played: i64,
|
||||||
#[serde(default = "default_level_name")]
|
#[serde(default = "default_level_name")]
|
||||||
pub level_name: String,
|
pub level_name: String,
|
||||||
@ -82,6 +76,26 @@ pub struct LevelData {
|
|||||||
pub level_version: i32,
|
pub level_version: i32,
|
||||||
#[serde(rename = "map_id", default)]
|
#[serde(rename = "map_id", default)]
|
||||||
pub map_id: i32,
|
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;
|
const DEFAULT_BORDER_DAMAGE_PER_BLOCK: f64 = 0.2;
|
||||||
@ -134,6 +148,13 @@ pub struct WorldGenSettings {
|
|||||||
pub dimensions: Dimensions,
|
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<String, Dimension>;
|
pub type Dimensions = HashMap<String, Dimension>;
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
pub struct Dimension {
|
pub struct Dimension {
|
||||||
@ -144,7 +165,8 @@ pub struct Dimension {
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
pub struct Generator {
|
pub struct Generator {
|
||||||
pub settings: GeneratorSettings,
|
#[serde(default)]
|
||||||
|
pub settings: Option<GeneratorSettings>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub biome_source: Option<BiomeSource>,
|
pub biome_source: Option<BiomeSource>,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
@ -227,7 +249,9 @@ impl WorldGenSettings {
|
|||||||
"minecraft:overworld".to_string(),
|
"minecraft:overworld".to_string(),
|
||||||
Dimension {
|
Dimension {
|
||||||
generator: Generator {
|
generator: Generator {
|
||||||
settings: GeneratorSettings::Reference("minecraft:overworld".to_string()),
|
settings: Some(GeneratorSettings::Reference(
|
||||||
|
"minecraft:overworld".to_string(),
|
||||||
|
)),
|
||||||
biome_source: Some(BiomeSource::WithPreset {
|
biome_source: Some(BiomeSource::WithPreset {
|
||||||
preset: "minecraft:overworld".to_string(),
|
preset: "minecraft:overworld".to_string(),
|
||||||
biome_type: "minecraft:multi_noise".to_string(),
|
biome_type: "minecraft:multi_noise".to_string(),
|
||||||
@ -241,7 +265,7 @@ impl WorldGenSettings {
|
|||||||
"minecraft:the_nether".to_string(),
|
"minecraft:the_nether".to_string(),
|
||||||
Dimension {
|
Dimension {
|
||||||
generator: Generator {
|
generator: Generator {
|
||||||
settings: GeneratorSettings::Reference("minecraft:nether".to_string()),
|
settings: Some(GeneratorSettings::Reference("minecraft:nether".to_string())),
|
||||||
biome_source: Some(BiomeSource::WithPreset {
|
biome_source: Some(BiomeSource::WithPreset {
|
||||||
preset: "minecraft:nether".to_string(),
|
preset: "minecraft:nether".to_string(),
|
||||||
biome_type: "minecraft:multi_noise".to_string(),
|
biome_type: "minecraft:multi_noise".to_string(),
|
||||||
@ -255,7 +279,7 @@ impl WorldGenSettings {
|
|||||||
"minecraft:the_end".to_string(),
|
"minecraft:the_end".to_string(),
|
||||||
Dimension {
|
Dimension {
|
||||||
generator: Generator {
|
generator: Generator {
|
||||||
settings: GeneratorSettings::Reference("minecraft:end".to_string()),
|
settings: Some(GeneratorSettings::Reference("minecraft:end".to_string())),
|
||||||
biome_source: Some(BiomeSource::Simple {
|
biome_source: Some(BiomeSource::Simple {
|
||||||
biome_type: "minecraft:the_end".to_string(),
|
biome_type: "minecraft:the_end".to_string(),
|
||||||
}),
|
}),
|
||||||
@ -310,14 +334,10 @@ impl LevelData {
|
|||||||
border_size_lerp_time: 0,
|
border_size_lerp_time: 0,
|
||||||
border_warning_blocks: DEFAULT_BORDER_WARNING_BLOCKS,
|
border_warning_blocks: DEFAULT_BORDER_WARNING_BLOCKS,
|
||||||
border_warning_time: DEFAULT_BORDER_WARNING_TIME,
|
border_warning_time: DEFAULT_BORDER_WARNING_TIME,
|
||||||
clear_weather_time: -1,
|
|
||||||
data_packs: default_data_packs(),
|
data_packs: default_data_packs(),
|
||||||
data_version: MAXIMUM_SUPPORTED_WORLD_DATA_VERSION,
|
data_version: MAXIMUM_SUPPORTED_WORLD_DATA_VERSION,
|
||||||
day_time: 0,
|
|
||||||
difficulty: DEFAULT_DIFFICULTY,
|
difficulty: DEFAULT_DIFFICULTY,
|
||||||
difficulty_locked: false,
|
difficulty_locked: false,
|
||||||
game_rules: GameRuleRegistry::default(),
|
|
||||||
world_gen_settings: WorldGenSettings::new(seed),
|
|
||||||
last_played: -1,
|
last_played: -1,
|
||||||
level_name: DEFAULT_LEVEL_NAME.to_string(),
|
level_name: DEFAULT_LEVEL_NAME.to_string(),
|
||||||
spawn_x: 0,
|
spawn_x: 0,
|
||||||
@ -328,6 +348,11 @@ impl LevelData {
|
|||||||
world_version: WorldVersion::default(),
|
world_version: WorldVersion::default(),
|
||||||
level_version: MAXIMUM_SUPPORTED_LEVEL_VERSION,
|
level_version: MAXIMUM_SUPPORTED_LEVEL_VERSION,
|
||||||
map_id: 0,
|
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,
|
InfoNotFound,
|
||||||
#[error("Deserialization error: {0}")]
|
#[error("Deserialization error: {0}")]
|
||||||
DeserializationError(String),
|
DeserializationError(String),
|
||||||
|
#[error("Serialization error: {0}")]
|
||||||
|
SerializationError(String),
|
||||||
#[error("Unsupported world data version: {0}")]
|
#[error("Unsupported world data version: {0}")]
|
||||||
UnsupportedDataVersion(i32),
|
UnsupportedDataVersion(i32),
|
||||||
#[error("Unsupported world level version: {0}")]
|
#[error("Unsupported world level version: {0}")]
|
||||||
|
|||||||
@ -153,7 +153,6 @@ impl BlockBehaviour for DropperBlock {
|
|||||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
if let Some(block_entity) = args.world.get_block_entity(args.position) {
|
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::<DropperBlockEntity>()
|
let Some(dropper) = block_entity.as_any().downcast_ref::<DropperBlockEntity>()
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -242,10 +242,10 @@ async fn resolve_profiles_from_token(
|
|||||||
return Ok(vec![profile]);
|
return Ok(vec![profile]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if server.basic_config.online_mode {
|
if server.advanced_config.networking.java.online_mode {
|
||||||
match lookup_profile_by_name(
|
match lookup_profile_by_name(
|
||||||
raw_arg.value,
|
raw_arg.value,
|
||||||
&server.advanced_config.networking.authentication,
|
&server.advanced_config.networking.java.authentication,
|
||||||
) {
|
) {
|
||||||
Ok(Some((uuid, resolved_name))) => {
|
Ok(Some((uuid, resolved_name))) => {
|
||||||
server
|
server
|
||||||
|
|||||||
@ -70,10 +70,10 @@ impl GameProfileResult {
|
|||||||
return Ok(vec![profile]);
|
return Ok(vec![profile]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if server.basic_config.online_mode {
|
if server.advanced_config.networking.java.online_mode {
|
||||||
return match lookup_profile_by_name(
|
return match lookup_profile_by_name(
|
||||||
name,
|
name,
|
||||||
&server.advanced_config.networking.authentication,
|
&server.advanced_config.networking.java.authentication,
|
||||||
) {
|
) {
|
||||||
Ok(Some((uuid, resolved_name))) => {
|
Ok(Some((uuid, resolved_name))) => {
|
||||||
server
|
server
|
||||||
|
|||||||
@ -37,6 +37,23 @@ impl CommandExecutor for ListCommandExecutor {
|
|||||||
ListMode::Uuids => get_player_names_and_ids(&players),
|
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
|
context
|
||||||
.source
|
.source
|
||||||
.send_feedback(
|
.send_feedback(
|
||||||
@ -45,9 +62,7 @@ impl CommandExecutor for ListCommandExecutor {
|
|||||||
COMMANDS_LIST_PLAYERS,
|
COMMANDS_LIST_PLAYERS,
|
||||||
[
|
[
|
||||||
TextComponent::text(players_len.to_string()),
|
TextComponent::text(players_len.to_string()),
|
||||||
TextComponent::text(
|
TextComponent::text(max_players.to_string()),
|
||||||
context.server().basic_config.max_players.to_string(),
|
|
||||||
),
|
|
||||||
list,
|
list,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -51,14 +51,14 @@ impl BegGoal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_begging(mob: &dyn Mob, begging: bool) {
|
fn set_begging(mob: &dyn Mob, begging: bool) {
|
||||||
mob.get_mob_entity()
|
mob.get_mob_entity().living_entity.entity.send_meta_data(
|
||||||
.living_entity
|
&[Metadata::new(
|
||||||
.entity
|
|
||||||
.send_meta_data(&[Metadata::new(
|
|
||||||
TrackedData::INTERESTED_ID,
|
TrackedData::INTERESTED_ID,
|
||||||
MetaDataType::BOOLEAN,
|
MetaDataType::BOOLEAN,
|
||||||
begging,
|
begging,
|
||||||
)]);
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -173,29 +173,35 @@ impl EntityBase for AreaEffectCloudEntity {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Send initial particle and radius
|
// Send initial particle and radius
|
||||||
self.entity
|
self.entity.send_meta_data(
|
||||||
.send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
|
&[pumpkin_protocol::java::client::play::Metadata::new(
|
||||||
pumpkin_data::tracked_data::TrackedData::PARTICLE,
|
pumpkin_data::tracked_data::TrackedData::PARTICLE,
|
||||||
pumpkin_data::meta_data_type::MetaDataType::PARTICLE,
|
pumpkin_data::meta_data_type::MetaDataType::PARTICLE,
|
||||||
&meta,
|
&meta,
|
||||||
)]);
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
self.entity
|
self.entity.send_meta_data(
|
||||||
.send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
|
&[pumpkin_protocol::java::client::play::Metadata::new(
|
||||||
pumpkin_data::tracked_data::TrackedData::RADIUS,
|
pumpkin_data::tracked_data::TrackedData::RADIUS,
|
||||||
pumpkin_data::meta_data_type::MetaDataType::FLOAT,
|
pumpkin_data::meta_data_type::MetaDataType::FLOAT,
|
||||||
radius,
|
radius,
|
||||||
)]);
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
// Initial waiting flag
|
// Initial waiting flag
|
||||||
let wait_time = *self.wait_time.lock().await;
|
let wait_time = *self.wait_time.lock().await;
|
||||||
let is_waiting = 0 < wait_time;
|
let is_waiting = 0 < wait_time;
|
||||||
self.entity
|
self.entity.send_meta_data(
|
||||||
.send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
|
&[pumpkin_protocol::java::client::play::Metadata::new(
|
||||||
pumpkin_data::tracked_data::TrackedData::WAITING,
|
pumpkin_data::tracked_data::TrackedData::WAITING,
|
||||||
pumpkin_data::meta_data_type::MetaDataType::BOOLEAN,
|
pumpkin_data::meta_data_type::MetaDataType::BOOLEAN,
|
||||||
is_waiting,
|
is_waiting,
|
||||||
)]);
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,12 +231,14 @@ impl EntityBase for AreaEffectCloudEntity {
|
|||||||
|
|
||||||
// When the waiting period ends, notify clients so they render full particles
|
// When the waiting period ends, notify clients so they render full particles
|
||||||
if age == wait_time && wait_time > 0 {
|
if age == wait_time && wait_time > 0 {
|
||||||
self.entity
|
self.entity.send_meta_data(
|
||||||
.send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
|
&[pumpkin_protocol::java::client::play::Metadata::new(
|
||||||
pumpkin_data::tracked_data::TrackedData::WAITING,
|
pumpkin_data::tracked_data::TrackedData::WAITING,
|
||||||
pumpkin_data::meta_data_type::MetaDataType::BOOLEAN,
|
pumpkin_data::meta_data_type::MetaDataType::BOOLEAN,
|
||||||
false,
|
false,
|
||||||
)]);
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if age < wait_time {
|
if age < wait_time {
|
||||||
@ -251,12 +259,14 @@ impl EntityBase for AreaEffectCloudEntity {
|
|||||||
|
|
||||||
// Send new radius
|
// Send new radius
|
||||||
drop(radius);
|
drop(radius);
|
||||||
self.entity
|
self.entity.send_meta_data(
|
||||||
.send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
|
&[pumpkin_protocol::java::client::play::Metadata::new(
|
||||||
pumpkin_data::tracked_data::TrackedData::RADIUS,
|
pumpkin_data::tracked_data::TrackedData::RADIUS,
|
||||||
pumpkin_data::meta_data_type::MetaDataType::FLOAT,
|
pumpkin_data::meta_data_type::MetaDataType::FLOAT,
|
||||||
current_radius,
|
current_radius,
|
||||||
)]);
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tick down reapplication map
|
// Tick down reapplication map
|
||||||
@ -380,13 +390,14 @@ impl EntityBase for AreaEffectCloudEntity {
|
|||||||
drop(radius_lock);
|
drop(radius_lock);
|
||||||
|
|
||||||
// Send updated radius to clients
|
// Send updated radius to clients
|
||||||
self.entity.send_meta_data(&[
|
self.entity.send_meta_data(
|
||||||
pumpkin_protocol::java::client::play::Metadata::new(
|
&[pumpkin_protocol::java::client::play::Metadata::new(
|
||||||
pumpkin_data::tracked_data::TrackedData::RADIUS,
|
pumpkin_data::tracked_data::TrackedData::RADIUS,
|
||||||
pumpkin_data::meta_data_type::MetaDataType::FLOAT,
|
pumpkin_data::meta_data_type::MetaDataType::FLOAT,
|
||||||
current_radius,
|
current_radius,
|
||||||
),
|
)],
|
||||||
]);
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply duration-on-use (shorten lifespan)
|
// Apply duration-on-use (shorten lifespan)
|
||||||
|
|||||||
@ -152,11 +152,21 @@ impl BreathManager {
|
|||||||
pub fn send_air_supply(&self, player: &Player) {
|
pub fn send_air_supply(&self, player: &Player) {
|
||||||
let air = self.air_supply.load(Ordering::Relaxed).clamp(0, MAX_AIR);
|
let air = self.air_supply.load(Ordering::Relaxed).clamp(0, MAX_AIR);
|
||||||
|
|
||||||
player.get_entity().send_meta_data(&[Metadata::new(
|
let mut bedrock_meta =
|
||||||
TrackedData::AIR_SUPPLY_ID,
|
pumpkin_protocol::bedrock::client::set_actor_data::EntityMetadata::new();
|
||||||
MetaDataType::INT,
|
bedrock_meta.set(
|
||||||
VarInt(air),
|
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) {
|
pub fn reset(&self, player: &Player) {
|
||||||
|
|||||||
@ -22,11 +22,14 @@ impl EndCrystalEntity {
|
|||||||
|
|
||||||
impl EndCrystalEntity {
|
impl EndCrystalEntity {
|
||||||
pub fn set_show_bottom(&self, show_bottom: bool) {
|
pub fn set_show_bottom(&self, show_bottom: bool) {
|
||||||
self.entity.send_meta_data(&[Metadata::new(
|
self.entity.send_meta_data(
|
||||||
TrackedData::SHOW_BOTTOM,
|
&[Metadata::new(
|
||||||
MetaDataType::BOOLEAN,
|
TrackedData::SHOW_BOTTOM,
|
||||||
show_bottom,
|
MetaDataType::BOOLEAN,
|
||||||
)]);
|
show_bottom,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -93,11 +93,14 @@ impl EntityBase for FallingEntity {
|
|||||||
|
|
||||||
fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
|
fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
self.entity.send_meta_data(&[Metadata::new(
|
self.entity.send_meta_data(
|
||||||
TrackedData::START_POS,
|
&[Metadata::new(
|
||||||
MetaDataType::BLOCK_POS,
|
TrackedData::START_POS,
|
||||||
self.entity.block_pos.load(),
|
MetaDataType::BLOCK_POS,
|
||||||
)]);
|
self.entity.block_pos.load(),
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -470,11 +470,14 @@ impl EntityBase for ItemEntity {
|
|||||||
|
|
||||||
fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
|
fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
|
||||||
Box::pin(async {
|
Box::pin(async {
|
||||||
self.entity.send_meta_data(&[Metadata::new(
|
self.entity.send_meta_data(
|
||||||
TrackedData::ITEM,
|
&[Metadata::new(
|
||||||
MetaDataType::ITEM_STACK,
|
TrackedData::ITEM,
|
||||||
&ItemStackSerializer::from(self.item_stack.lock().await.clone()),
|
MetaDataType::ITEM_STACK,
|
||||||
)]);
|
&ItemStackSerializer::from(self.item_stack.lock().await.clone()),
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -237,11 +237,26 @@ impl LivingEntity {
|
|||||||
b &= !index;
|
b &= !index;
|
||||||
}
|
}
|
||||||
self.livings_flags.store(b, Ordering::Relaxed);
|
self.livings_flags.store(b, Ordering::Relaxed);
|
||||||
self.entity.send_meta_data(&[Metadata::new(
|
|
||||||
TrackedData::LIVING_ENTITY_FLAGS,
|
let bedrock_meta = (flag == Self::USING_ITEM_FLAG).then(|| {
|
||||||
MetaDataType::BYTE,
|
let mut meta = pumpkin_protocol::bedrock::client::set_actor_data::EntityMetadata::new();
|
||||||
b,
|
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) {
|
pub async fn clear_active_hand(&self) {
|
||||||
@ -274,11 +289,14 @@ impl LivingEntity {
|
|||||||
let clamped = health.max(0.0).min(max_health);
|
let clamped = health.max(0.0).min(max_health);
|
||||||
self.health.store(clamped);
|
self.health.store(clamped);
|
||||||
// tell everyone entities health changed
|
// tell everyone entities health changed
|
||||||
self.entity.send_meta_data(&[Metadata::new(
|
self.entity.send_meta_data(
|
||||||
TrackedData::HEALTH_ID,
|
&[Metadata::new(
|
||||||
MetaDataType::FLOAT,
|
TrackedData::HEALTH_ID,
|
||||||
clamped,
|
MetaDataType::FLOAT,
|
||||||
)]);
|
clamped,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current maximum health for this entity
|
/// Returns the current maximum health for this entity
|
||||||
@ -328,8 +346,10 @@ impl LivingEntity {
|
|||||||
|
|
||||||
// Send absorption metadata for players (visual yellow hearts)
|
// Send absorption metadata for players (visual yellow hearts)
|
||||||
if let Some(tracked_id) = self.player_absorption_id() {
|
if let Some(tracked_id) = self.player_absorption_id() {
|
||||||
self.entity
|
self.entity.send_meta_data(
|
||||||
.send_meta_data(&[Metadata::new(tracked_id, MetaDataType::FLOAT, new_abs)]);
|
&[Metadata::new(tracked_id, MetaDataType::FLOAT, new_abs)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1791,11 +1811,14 @@ impl LivingEntity {
|
|||||||
// Clear any absorption
|
// Clear any absorption
|
||||||
self.absorption.store(0.0);
|
self.absorption.store(0.0);
|
||||||
// Send health metadata
|
// Send health metadata
|
||||||
self.entity.send_meta_data(&[Metadata::new(
|
self.entity.send_meta_data(
|
||||||
TrackedData::HEALTH_ID,
|
&[Metadata::new(
|
||||||
MetaDataType::FLOAT,
|
TrackedData::HEALTH_ID,
|
||||||
max_health,
|
MetaDataType::FLOAT,
|
||||||
)]);
|
max_health,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
self.reset_effects_and_attributes().await;
|
self.reset_effects_and_attributes().await;
|
||||||
|
|
||||||
|
|||||||
@ -86,14 +86,14 @@ impl CreeperEntity {
|
|||||||
|
|
||||||
pub fn set_fuse_speed(&self, speed: i32) {
|
pub fn set_fuse_speed(&self, speed: i32) {
|
||||||
self.fuse_speed.store(speed, Ordering::Relaxed);
|
self.fuse_speed.store(speed, Ordering::Relaxed);
|
||||||
self.mob_entity
|
self.mob_entity.living_entity.entity.send_meta_data(
|
||||||
.living_entity
|
&[Metadata::new(
|
||||||
.entity
|
|
||||||
.send_meta_data(&[Metadata::new(
|
|
||||||
TrackedData::FUSE_ID,
|
TrackedData::FUSE_ID,
|
||||||
MetaDataType::INTEGER,
|
MetaDataType::INTEGER,
|
||||||
VarInt(speed),
|
VarInt(speed),
|
||||||
)]);
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn explode(&self) {
|
async fn explode(&self) {
|
||||||
@ -219,11 +219,14 @@ impl Mob for CreeperEntity {
|
|||||||
);
|
);
|
||||||
|
|
||||||
self.ignited.store(true, Ordering::Relaxed);
|
self.ignited.store(true, Ordering::Relaxed);
|
||||||
entity.send_meta_data(&[Metadata::new(
|
entity.send_meta_data(
|
||||||
TrackedData::IS_IGNITED,
|
&[Metadata::new(
|
||||||
MetaDataType::BOOLEAN,
|
TrackedData::IS_IGNITED,
|
||||||
true,
|
MetaDataType::BOOLEAN,
|
||||||
)]);
|
true,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
if player.gamemode.load() != pumpkin_util::GameMode::Creative {
|
if player.gamemode.load() != pumpkin_util::GameMode::Creative {
|
||||||
// TODO: Handle DamageResult::Broken to broadcast item break and update player slot.
|
// TODO: Handle DamageResult::Broken to broadcast item break and update player slot.
|
||||||
|
|||||||
@ -282,14 +282,14 @@ impl EndermanEntity {
|
|||||||
|
|
||||||
pub fn set_angry(&self, angry: bool) {
|
pub fn set_angry(&self, angry: bool) {
|
||||||
self.angry.store(angry, Ordering::Relaxed);
|
self.angry.store(angry, Ordering::Relaxed);
|
||||||
self.mob_entity
|
self.mob_entity.living_entity.entity.send_meta_data(
|
||||||
.living_entity
|
&[Metadata::new(
|
||||||
.entity
|
|
||||||
.send_meta_data(&[Metadata::new(
|
|
||||||
TrackedData::CREEPY,
|
TrackedData::CREEPY,
|
||||||
MetaDataType::BOOLEAN,
|
MetaDataType::BOOLEAN,
|
||||||
angry,
|
angry,
|
||||||
)]);
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_angry(&self) -> bool {
|
pub fn is_angry(&self) -> bool {
|
||||||
@ -298,27 +298,27 @@ impl EndermanEntity {
|
|||||||
|
|
||||||
pub fn set_provoked(&self, provoked: bool) {
|
pub fn set_provoked(&self, provoked: bool) {
|
||||||
self.provoked.store(provoked, Ordering::Relaxed);
|
self.provoked.store(provoked, Ordering::Relaxed);
|
||||||
self.mob_entity
|
self.mob_entity.living_entity.entity.send_meta_data(
|
||||||
.living_entity
|
&[Metadata::new(
|
||||||
.entity
|
|
||||||
.send_meta_data(&[Metadata::new(
|
|
||||||
TrackedData::STARED_AT,
|
TrackedData::STARED_AT,
|
||||||
MetaDataType::BOOLEAN,
|
MetaDataType::BOOLEAN,
|
||||||
provoked,
|
provoked,
|
||||||
)]);
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_carried_block(&self, block_state: Option<BlockStateId>) {
|
pub fn set_carried_block(&self, block_state: Option<BlockStateId>) {
|
||||||
self.carried_block.store(block_state);
|
self.carried_block.store(block_state);
|
||||||
let value = block_state.map_or(VarInt(0), |id| VarInt(id.as_u16() as i32));
|
let value = block_state.map_or(VarInt(0), |id| VarInt(id.as_u16() as i32));
|
||||||
self.mob_entity
|
self.mob_entity.living_entity.entity.send_meta_data(
|
||||||
.living_entity
|
&[Metadata::new(
|
||||||
.entity
|
|
||||||
.send_meta_data(&[Metadata::new(
|
|
||||||
TrackedData::CARRY_STATE,
|
TrackedData::CARRY_STATE,
|
||||||
MetaDataType::OPTIONAL_BLOCK_STATE,
|
MetaDataType::OPTIONAL_BLOCK_STATE,
|
||||||
value,
|
value,
|
||||||
)]);
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_carried_block(&self) -> Option<BlockStateId> {
|
pub fn get_carried_block(&self) -> Option<BlockStateId> {
|
||||||
|
|||||||
@ -165,11 +165,14 @@ impl MobEntity {
|
|||||||
if new_b != old_b {
|
if new_b != old_b {
|
||||||
self.mob_flags.store(new_b, Ordering::Relaxed);
|
self.mob_flags.store(new_b, Ordering::Relaxed);
|
||||||
|
|
||||||
self.living_entity.entity.send_meta_data(&[Metadata::new(
|
self.living_entity.entity.send_meta_data(
|
||||||
TrackedData::MOB_FLAGS_ID,
|
&[Metadata::new(
|
||||||
MetaDataType::BYTE,
|
TrackedData::MOB_FLAGS_ID,
|
||||||
new_b,
|
MetaDataType::BYTE,
|
||||||
)]);
|
new_b,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -493,11 +496,14 @@ pub trait Mob: EntityBase + Send + Sync {
|
|||||||
let entity = self.get_entity();
|
let entity = self.get_entity();
|
||||||
let is_baby = entity.age.load(std::sync::atomic::Ordering::Relaxed) < 0;
|
let is_baby = entity.age.load(std::sync::atomic::Ordering::Relaxed) < 0;
|
||||||
if is_baby {
|
if is_baby {
|
||||||
entity.send_meta_data(&[Metadata::new(
|
entity.send_meta_data(
|
||||||
TrackedData::BABY_ID,
|
&[Metadata::new(
|
||||||
MetaDataType::BOOLEAN,
|
TrackedData::BABY_ID,
|
||||||
true,
|
MetaDataType::BOOLEAN,
|
||||||
)]);
|
true,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,11 +110,14 @@ impl ShulkerEntity {
|
|||||||
fn set_attach_face(&self, face: BlockDirection) {
|
fn set_attach_face(&self, face: BlockDirection) {
|
||||||
self.attach_face.store(face as u8, Ordering::Relaxed);
|
self.attach_face.store(face as u8, Ordering::Relaxed);
|
||||||
let entity = &self.mob_entity.living_entity.entity;
|
let entity = &self.mob_entity.living_entity.entity;
|
||||||
entity.send_meta_data(&[Metadata::new(
|
entity.send_meta_data(
|
||||||
TrackedData::ATTACH_FACE_ID,
|
&[Metadata::new(
|
||||||
MetaDataType::DIRECTION,
|
TrackedData::ATTACH_FACE_ID,
|
||||||
VarInt(face as i32),
|
MetaDataType::DIRECTION,
|
||||||
)]);
|
VarInt(face as i32),
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_raw_peek(&self) -> u8 {
|
pub fn get_raw_peek(&self) -> u8 {
|
||||||
@ -148,11 +151,14 @@ impl ShulkerEntity {
|
|||||||
|
|
||||||
self.peek_amount.store(amount, Ordering::Relaxed);
|
self.peek_amount.store(amount, Ordering::Relaxed);
|
||||||
|
|
||||||
entity.send_meta_data(&[Metadata::new(
|
entity.send_meta_data(
|
||||||
TrackedData::PEEK_ID,
|
&[Metadata::new(
|
||||||
MetaDataType::BYTE,
|
TrackedData::PEEK_ID,
|
||||||
amount,
|
MetaDataType::BYTE,
|
||||||
)]);
|
amount,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_closed(&self) -> bool {
|
pub fn is_closed(&self) -> bool {
|
||||||
|
|||||||
@ -179,11 +179,16 @@ pub trait EntityBase: Send + Sync + NBTStorage + std::any::Any {
|
|||||||
let is_baby = entity.age.load(Ordering::Relaxed) < 0;
|
let is_baby = entity.age.load(Ordering::Relaxed) < 0;
|
||||||
|
|
||||||
if is_baby {
|
if is_baby {
|
||||||
entity.send_meta_data(&[Metadata::new(
|
let mut bedrock_meta = EntityMetadata::new();
|
||||||
TrackedData::BABY_ID,
|
bedrock_meta.set_flag(entity_data_key::FLAGS, entity_data_flag::BABY as u8, true);
|
||||||
MetaDataType::BOOLEAN,
|
entity.send_meta_data(
|
||||||
true,
|
&[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
|
/// Sets a custom name for the entity, typically used with nametags
|
||||||
pub fn set_custom_name(&self, name: TextComponent) {
|
pub fn set_custom_name(&self, name: TextComponent) {
|
||||||
self.custom_name.store(Arc::new(Some(name.clone())));
|
self.custom_name.store(Arc::new(Some(name.clone())));
|
||||||
self.send_meta_data(&[Metadata::new(
|
let mut bedrock_meta = EntityMetadata::new();
|
||||||
TrackedData::CUSTOM_NAME,
|
bedrock_meta.set(
|
||||||
MetaDataType::OPTIONAL_TEXT_COMPONENT,
|
entity_data_key::NAME,
|
||||||
Some(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) {
|
pub fn set_custom_name_visible(&self, visible: bool) {
|
||||||
self.custom_name_visible.store(visible, Ordering::Relaxed);
|
self.custom_name_visible.store(visible, Ordering::Relaxed);
|
||||||
self.send_meta_data(&[Metadata::new(
|
let mut bedrock_meta = EntityMetadata::new();
|
||||||
TrackedData::CUSTOM_NAME_VISIBLE,
|
if let Some(name) = &**self.custom_name.load() {
|
||||||
MetaDataType::BOOLEAN,
|
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,
|
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) {
|
pub fn send_velocity(&self) {
|
||||||
@ -2383,11 +2427,19 @@ impl Entity {
|
|||||||
// Only update and send metadata if the value changed
|
// Only update and send metadata if the value changed
|
||||||
if new_frozen_ticks != old_frozen_ticks {
|
if new_frozen_ticks != old_frozen_ticks {
|
||||||
self.frozen_ticks.store(new_frozen_ticks, Ordering::Relaxed);
|
self.frozen_ticks.store(new_frozen_ticks, Ordering::Relaxed);
|
||||||
self.send_meta_data(&[Metadata::new(
|
let mut bedrock_meta = EntityMetadata::new();
|
||||||
TrackedData::TICKS_FROZEN,
|
bedrock_meta.set(
|
||||||
MetaDataType::INTEGER,
|
entity_data_key::FREEZING_EFFECT_STRENGTH,
|
||||||
VarInt(new_frozen_ticks),
|
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.
|
// Vanilla parity: full-freeze damage is tick-phase based.
|
||||||
@ -2639,11 +2691,14 @@ impl Entity {
|
|||||||
self.flags.fetch_and(!mask, Ordering::Relaxed) & !mask
|
self.flags.fetch_and(!mask, Ordering::Relaxed) & !mask
|
||||||
};
|
};
|
||||||
|
|
||||||
self.send_meta_data(&[Metadata::new(
|
self.send_meta_data(
|
||||||
TrackedData::SHARED_FLAGS_ID,
|
&[Metadata::new(
|
||||||
MetaDataType::BYTE,
|
TrackedData::SHARED_FLAGS_ID,
|
||||||
new_je_flags,
|
MetaDataType::BYTE,
|
||||||
)]);
|
new_je_flags,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(bedrock_flag) = flag.to_bedrock() {
|
if let Some(bedrock_flag) = flag.to_bedrock() {
|
||||||
let (key, index) = if bedrock_flag >= 64 {
|
let (key, index) = if bedrock_flag >= 64 {
|
||||||
@ -2710,25 +2765,52 @@ impl Entity {
|
|||||||
.play_sound(sound, SoundCategory::Neutral, &self.pos.load());
|
.play_sound(sound, SoundCategory::Neutral, &self.pos.load());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_meta_data<T: Serialize>(&self, meta: &[Metadata<T>]) {
|
pub fn send_meta_data<T: Serialize>(
|
||||||
|
&self,
|
||||||
|
meta: &[Metadata<T>],
|
||||||
|
bedrock_meta: Option<&EntityMetadata>,
|
||||||
|
) {
|
||||||
let world = self.world.load();
|
let world = self.world.load();
|
||||||
let chunk_pos = self.chunk_pos.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) {
|
for player in world.players.load().iter() {
|
||||||
let mut buf = Vec::new();
|
match player.client.as_ref() {
|
||||||
for m in meta {
|
ClientPlatform::Java(client) => {
|
||||||
m.write(&mut buf, &client.version.load()).unwrap();
|
// 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.bounding_box.store(aabb);
|
||||||
self.entity_dimension.store(dimension);
|
self.entity_dimension.store(dimension);
|
||||||
let pose = pose as i32;
|
let pose = pose as i32;
|
||||||
self.send_meta_data(&[Metadata::new(
|
let mut bedrock_meta = EntityMetadata::new();
|
||||||
TrackedData::POSE,
|
bedrock_meta.set(entity_data_key::POSE_INDEX, MetadataValue::Int(pose));
|
||||||
MetaDataType::ENTITY_POSE,
|
self.send_meta_data(
|
||||||
VarInt(pose),
|
&[Metadata::new(
|
||||||
)]);
|
TrackedData::POSE,
|
||||||
|
MetaDataType::ENTITY_POSE,
|
||||||
|
VarInt(pose),
|
||||||
|
)],
|
||||||
|
Some(&bedrock_meta),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -137,17 +137,23 @@ impl Mob for CatEntity {
|
|||||||
let entity = self.get_entity();
|
let entity = self.get_entity();
|
||||||
let is_baby = entity.age.load(Ordering::Relaxed) < 0;
|
let is_baby = entity.age.load(Ordering::Relaxed) < 0;
|
||||||
if is_baby {
|
if is_baby {
|
||||||
entity.send_meta_data(&[Metadata::new(
|
entity.send_meta_data(
|
||||||
TrackedData::BABY_ID,
|
&[Metadata::new(
|
||||||
MetaDataType::BOOLEAN,
|
TrackedData::BABY_ID,
|
||||||
true,
|
MetaDataType::BOOLEAN,
|
||||||
)]);
|
true,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
entity.send_meta_data(&[Metadata::new(
|
entity.send_meta_data(
|
||||||
TrackedData::CAT_VARIANT,
|
&[Metadata::new(
|
||||||
MetaDataType::CAT_VARIANT,
|
TrackedData::CAT_VARIANT,
|
||||||
VarInt(self.variant.load(Ordering::Relaxed) as i32),
|
MetaDataType::CAT_VARIANT,
|
||||||
)]);
|
VarInt(self.variant.load(Ordering::Relaxed) as i32),
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -131,17 +131,23 @@ impl Mob for ChickenEntity {
|
|||||||
let entity = self.get_entity();
|
let entity = self.get_entity();
|
||||||
let is_baby = entity.age.load(Ordering::Relaxed) < 0;
|
let is_baby = entity.age.load(Ordering::Relaxed) < 0;
|
||||||
if is_baby {
|
if is_baby {
|
||||||
entity.send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
|
entity.send_meta_data(
|
||||||
TrackedData::BABY_ID,
|
&[pumpkin_protocol::java::client::play::Metadata::new(
|
||||||
MetaDataType::BOOLEAN,
|
TrackedData::BABY_ID,
|
||||||
true,
|
MetaDataType::BOOLEAN,
|
||||||
)]);
|
true,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
entity.send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
|
entity.send_meta_data(
|
||||||
TrackedData::VARIANT,
|
&[pumpkin_protocol::java::client::play::Metadata::new(
|
||||||
MetaDataType::CHICKEN_VARIANT,
|
TrackedData::VARIANT,
|
||||||
VarInt(self.variant.load(Ordering::Relaxed) as i32),
|
MetaDataType::CHICKEN_VARIANT,
|
||||||
)]);
|
VarInt(self.variant.load(Ordering::Relaxed) as i32),
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -103,17 +103,23 @@ impl Mob for FrogEntity {
|
|||||||
let entity = self.get_entity();
|
let entity = self.get_entity();
|
||||||
let is_baby = entity.age.load(Ordering::Relaxed) < 0;
|
let is_baby = entity.age.load(Ordering::Relaxed) < 0;
|
||||||
if is_baby {
|
if is_baby {
|
||||||
entity.send_meta_data(&[Metadata::new(
|
entity.send_meta_data(
|
||||||
TrackedData::BABY_ID,
|
&[Metadata::new(
|
||||||
MetaDataType::BOOLEAN,
|
TrackedData::BABY_ID,
|
||||||
true,
|
MetaDataType::BOOLEAN,
|
||||||
)]);
|
true,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
entity.send_meta_data(&[Metadata::new(
|
entity.send_meta_data(
|
||||||
TrackedData::VARIANT,
|
&[Metadata::new(
|
||||||
MetaDataType::FROG_VARIANT,
|
TrackedData::VARIANT,
|
||||||
VarInt(self.variant.load(Ordering::Relaxed) as i32),
|
MetaDataType::FROG_VARIANT,
|
||||||
)]);
|
VarInt(self.variant.load(Ordering::Relaxed) as i32),
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,14 +80,14 @@ impl SheepEntity {
|
|||||||
|
|
||||||
fn set_packed_and_sync(&self, byte: u8) {
|
fn set_packed_and_sync(&self, byte: u8) {
|
||||||
self.color_and_sheared.store(byte, Ordering::Relaxed);
|
self.color_and_sheared.store(byte, Ordering::Relaxed);
|
||||||
self.mob_entity
|
self.mob_entity.living_entity.entity.send_meta_data(
|
||||||
.living_entity
|
&[Metadata::new(
|
||||||
.entity
|
|
||||||
.send_meta_data(&[Metadata::new(
|
|
||||||
TrackedData::WOOL_ID,
|
TrackedData::WOOL_ID,
|
||||||
MetaDataType::BYTE,
|
MetaDataType::BYTE,
|
||||||
byte as i8,
|
byte as i8,
|
||||||
)]);
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_color(&self, color: u8) {
|
pub fn set_color(&self, color: u8) {
|
||||||
|
|||||||
@ -155,11 +155,14 @@ impl VillagerEntity {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Send initial metadata
|
// Send initial metadata
|
||||||
mob_arc.get_entity().send_meta_data(&[Metadata::new(
|
mob_arc.get_entity().send_meta_data(
|
||||||
TrackedData::VILLAGER_DATA,
|
&[Metadata::new(
|
||||||
MetaDataType::VILLAGER_DATA,
|
TrackedData::VILLAGER_DATA,
|
||||||
villager_data,
|
MetaDataType::VILLAGER_DATA,
|
||||||
)]);
|
villager_data,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
mob_arc
|
mob_arc
|
||||||
}
|
}
|
||||||
@ -210,11 +213,14 @@ impl VillagerEntity {
|
|||||||
*villager_data = data;
|
*villager_data = data;
|
||||||
old_profession
|
old_profession
|
||||||
};
|
};
|
||||||
self.get_entity().send_meta_data(&[Metadata::new(
|
self.get_entity().send_meta_data(
|
||||||
TrackedData::VILLAGER_DATA,
|
&[Metadata::new(
|
||||||
MetaDataType::VILLAGER_DATA,
|
TrackedData::VILLAGER_DATA,
|
||||||
data,
|
MetaDataType::VILLAGER_DATA,
|
||||||
)]);
|
data,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
if old_profession != data.profession {
|
if old_profession != data.profession {
|
||||||
self.generate_trades(data.profession_enum(), data.level.0)
|
self.generate_trades(data.profession_enum(), data.level.0)
|
||||||
@ -760,11 +766,14 @@ impl Mob for VillagerEntity {
|
|||||||
if is_sleeping {
|
if is_sleeping {
|
||||||
// Wake up if bed was broken
|
// Wake up if bed was broken
|
||||||
self.get_entity().set_pose(EntityPose::Standing);
|
self.get_entity().set_pose(EntityPose::Standing);
|
||||||
self.get_entity().send_meta_data(&[Metadata::new(
|
self.get_entity().send_meta_data(
|
||||||
TrackedData::SLEEPING_POS_ID,
|
&[Metadata::new(
|
||||||
MetaDataType::OPTIONAL_BLOCK_POS,
|
TrackedData::SLEEPING_POS_ID,
|
||||||
None::<BlockPos>,
|
MetaDataType::OPTIONAL_BLOCK_POS,
|
||||||
)]);
|
None::<BlockPos>,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -857,11 +866,14 @@ impl Mob for VillagerEntity {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
self.get_entity().set_pose(EntityPose::Sleeping);
|
self.get_entity().set_pose(EntityPose::Sleeping);
|
||||||
self.get_entity().send_meta_data(&[Metadata::new(
|
self.get_entity().send_meta_data(
|
||||||
TrackedData::SLEEPING_POS_ID,
|
&[Metadata::new(
|
||||||
MetaDataType::OPTIONAL_BLOCK_POS,
|
TrackedData::SLEEPING_POS_ID,
|
||||||
Some(home_pos),
|
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().set_pose(EntityPose::Standing);
|
||||||
self.get_entity().send_meta_data(&[Metadata::new(
|
self.get_entity().send_meta_data(
|
||||||
TrackedData::SLEEPING_POS_ID,
|
&[Metadata::new(
|
||||||
MetaDataType::OPTIONAL_BLOCK_POS,
|
TrackedData::SLEEPING_POS_ID,
|
||||||
None::<BlockPos>,
|
MetaDataType::OPTIONAL_BLOCK_POS,
|
||||||
)]);
|
None::<BlockPos>,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -129,17 +129,23 @@ impl Mob for WolfEntity {
|
|||||||
let entity = self.get_entity();
|
let entity = self.get_entity();
|
||||||
let is_baby = entity.age.load(Ordering::Relaxed) < 0;
|
let is_baby = entity.age.load(Ordering::Relaxed) < 0;
|
||||||
if is_baby {
|
if is_baby {
|
||||||
entity.send_meta_data(&[Metadata::new(
|
entity.send_meta_data(
|
||||||
TrackedData::BABY_ID,
|
&[Metadata::new(
|
||||||
MetaDataType::BOOLEAN,
|
TrackedData::BABY_ID,
|
||||||
true,
|
MetaDataType::BOOLEAN,
|
||||||
)]);
|
true,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
entity.send_meta_data(&[Metadata::new(
|
entity.send_meta_data(
|
||||||
TrackedData::VARIANT,
|
&[Metadata::new(
|
||||||
MetaDataType::WOLF_VARIANT,
|
TrackedData::VARIANT,
|
||||||
VarInt(self.variant.load(Ordering::Relaxed) as i32),
|
MetaDataType::WOLF_VARIANT,
|
||||||
)]);
|
VarInt(self.variant.load(Ordering::Relaxed) as i32),
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1592,11 +1592,14 @@ impl Player {
|
|||||||
self.living_entity
|
self.living_entity
|
||||||
.entity
|
.entity
|
||||||
.set_pos(bed_head_pos.to_f64().add_raw(0.5, 0.6875, 0.5));
|
.set_pos(bed_head_pos.to_f64().add_raw(0.5, 0.6875, 0.5));
|
||||||
self.get_entity().send_meta_data(&[Metadata::new(
|
self.get_entity().send_meta_data(
|
||||||
TrackedData::SLEEPING_POS_ID,
|
&[Metadata::new(
|
||||||
MetaDataType::OPTIONAL_BLOCK_POS,
|
TrackedData::SLEEPING_POS_ID,
|
||||||
Some(bed_head_pos),
|
MetaDataType::OPTIONAL_BLOCK_POS,
|
||||||
)]);
|
Some(bed_head_pos),
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
self.get_entity().set_velocity(Vector3::default());
|
self.get_entity().set_velocity(Vector3::default());
|
||||||
|
|
||||||
self.sleeping_since.store(Some(0));
|
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_pose(EntityPose::Standing);
|
||||||
self.living_entity.entity.set_pos(self.position());
|
self.living_entity.entity.set_pos(self.position());
|
||||||
self.living_entity.entity.send_meta_data(&[Metadata::new(
|
self.living_entity.entity.send_meta_data(
|
||||||
TrackedData::SLEEPING_POS_ID,
|
&[Metadata::new(
|
||||||
MetaDataType::OPTIONAL_BLOCK_POS,
|
TrackedData::SLEEPING_POS_ID,
|
||||||
None::<BlockPos>,
|
MetaDataType::OPTIONAL_BLOCK_POS,
|
||||||
)]);
|
None::<BlockPos>,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
self.set_stat(
|
self.set_stat(
|
||||||
statistics::StatisticCategory::Custom,
|
statistics::StatisticCategory::Custom,
|
||||||
@ -2952,18 +2958,21 @@ impl Player {
|
|||||||
/// Send the player's skin layers and used hand to all players.
|
/// Send the player's skin layers and used hand to all players.
|
||||||
pub fn send_client_information(&self) {
|
pub fn send_client_information(&self) {
|
||||||
let config = self.config.load();
|
let config = self.config.load();
|
||||||
self.living_entity.entity.send_meta_data(&[
|
self.living_entity.entity.send_meta_data(
|
||||||
Metadata::new(
|
&[
|
||||||
TrackedData::PLAYER_MODE_CUSTOMISATION,
|
Metadata::new(
|
||||||
MetaDataType::BYTE,
|
TrackedData::PLAYER_MODE_CUSTOMISATION,
|
||||||
config.skin_parts,
|
MetaDataType::BYTE,
|
||||||
),
|
config.skin_parts,
|
||||||
// Metadata::new(
|
),
|
||||||
// TrackedData::DATA_MAIN_ARM_ID,
|
// Metadata::new(
|
||||||
// MetaDataType::ARM,
|
// TrackedData::DATA_MAIN_ARM_ID,
|
||||||
// VarInt(config.main_hand as u8 as i32),
|
// MetaDataType::ARM,
|
||||||
// ),
|
// VarInt(config.main_hand as u8 as i32),
|
||||||
]);
|
// ),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn can_harvest(&self, state: &BlockState, block: &'static Block) -> bool {
|
pub async fn can_harvest(&self, state: &BlockState, block: &'static Block) -> bool {
|
||||||
|
|||||||
@ -73,11 +73,14 @@ impl EntityBase for EggEntity {
|
|||||||
let stack = self.item_stack.read().await;
|
let stack = self.item_stack.read().await;
|
||||||
|
|
||||||
// Sync the item stack so the client renders the correct color/variant
|
// Sync the item stack so the client renders the correct color/variant
|
||||||
entity.send_meta_data(&[Metadata::new(
|
entity.send_meta_data(
|
||||||
TrackedData::ITEM_STACK,
|
&[Metadata::new(
|
||||||
MetaDataType::ITEM_STACK,
|
TrackedData::ITEM_STACK,
|
||||||
&ItemStackSerializer::from(stack.clone()),
|
MetaDataType::ITEM_STACK,
|
||||||
)]);
|
&ItemStackSerializer::from(stack.clone()),
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -172,11 +172,14 @@ impl EntityBase for EyeOfEnder {
|
|||||||
|
|
||||||
fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
|
fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
|
||||||
Box::pin(async {
|
Box::pin(async {
|
||||||
self.entity.send_meta_data(&[Metadata::new(
|
self.entity.send_meta_data(
|
||||||
TrackedData::ITEM,
|
&[Metadata::new(
|
||||||
MetaDataType::ITEM_STACK,
|
TrackedData::ITEM,
|
||||||
&ItemStackSerializer::from(self.item_stack.lock().await.clone()),
|
MetaDataType::ITEM_STACK,
|
||||||
)]);
|
&ItemStackSerializer::from(self.item_stack.lock().await.clone()),
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -68,11 +68,14 @@ impl FireworkRocketEntity {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Set shooter metadata
|
// Set shooter metadata
|
||||||
rocket.entity.entity.send_meta_data(&[Metadata::new(
|
rocket.entity.entity.send_meta_data(
|
||||||
TrackedData::ATTACHED_TO_TARGET,
|
&[Metadata::new(
|
||||||
MetaDataType::OPTIONAL_INT,
|
TrackedData::ATTACHED_TO_TARGET,
|
||||||
OptionalInt(Some(shooter.entity_id)),
|
MetaDataType::OPTIONAL_INT,
|
||||||
)]);
|
OptionalInt(Some(shooter.entity_id)),
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
rocket
|
rocket
|
||||||
}
|
}
|
||||||
|
|||||||
@ -207,11 +207,14 @@ impl FishingBobberEntity {
|
|||||||
if ebb.intersects(&search_box) {
|
if ebb.intersects(&search_box) {
|
||||||
self.hooked_entity_id
|
self.hooked_entity_id
|
||||||
.store(cand.get_entity().entity_id, Ordering::Relaxed);
|
.store(cand.get_entity().entity_id, Ordering::Relaxed);
|
||||||
entity.send_meta_data(&[Metadata::new(
|
entity.send_meta_data(
|
||||||
TrackedData::HOOKED_ENTITY,
|
&[Metadata::new(
|
||||||
MetaDataType::INT,
|
TrackedData::HOOKED_ENTITY,
|
||||||
cand.get_entity().entity_id + 1,
|
MetaDataType::INT,
|
||||||
)]);
|
cand.get_entity().entity_id + 1,
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,13 +69,16 @@ impl EntityBase for LingeringPotionEntity {
|
|||||||
let stack = self.item_stack.read().await;
|
let stack = self.item_stack.read().await;
|
||||||
|
|
||||||
// Sync the item stack so the client renders the correct potion type
|
// Sync the item stack so the client renders the correct potion type
|
||||||
entity.send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
|
entity.send_meta_data(
|
||||||
pumpkin_data::tracked_data::TrackedData::ITEM_STACK,
|
&[pumpkin_protocol::java::client::play::Metadata::new(
|
||||||
pumpkin_data::meta_data_type::MetaDataType::ITEM_STACK,
|
pumpkin_data::tracked_data::TrackedData::ITEM_STACK,
|
||||||
&pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer::from(
|
pumpkin_data::meta_data_type::MetaDataType::ITEM_STACK,
|
||||||
stack.clone(),
|
&pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer::from(
|
||||||
),
|
stack.clone(),
|
||||||
)]);
|
),
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -107,13 +107,16 @@ impl EntityBase for SplashPotionEntity {
|
|||||||
let stack = self.item_stack.read().await;
|
let stack = self.item_stack.read().await;
|
||||||
|
|
||||||
// Sync the item stack
|
// Sync the item stack
|
||||||
entity.send_meta_data(&[pumpkin_protocol::java::client::play::Metadata::new(
|
entity.send_meta_data(
|
||||||
pumpkin_data::tracked_data::TrackedData::ITEM_STACK,
|
&[pumpkin_protocol::java::client::play::Metadata::new(
|
||||||
pumpkin_data::meta_data_type::MetaDataType::ITEM_STACK,
|
pumpkin_data::tracked_data::TrackedData::ITEM_STACK,
|
||||||
&pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer::from(
|
pumpkin_data::meta_data_type::MetaDataType::ITEM_STACK,
|
||||||
stack.clone(),
|
&pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer::from(
|
||||||
),
|
stack.clone(),
|
||||||
)]);
|
),
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -85,18 +85,21 @@ impl EntityBase for TNTEntity {
|
|||||||
self.entity
|
self.entity
|
||||||
.set_velocity(Vector3::new(-pos.sin() * 0.02, 0.2, -pos.cos() * 0.02));
|
.set_velocity(Vector3::new(-pos.sin() * 0.02, 0.2, -pos.cos() * 0.02));
|
||||||
|
|
||||||
self.entity.send_meta_data(&[
|
self.entity.send_meta_data(
|
||||||
Metadata::new(
|
&[
|
||||||
TrackedData::FUSE_ID,
|
Metadata::new(
|
||||||
MetaDataType::INTEGER,
|
TrackedData::FUSE_ID,
|
||||||
VarInt(self.fuse.load(Relaxed) as i32),
|
MetaDataType::INTEGER,
|
||||||
),
|
VarInt(self.fuse.load(Relaxed) as i32),
|
||||||
Metadata::new(
|
),
|
||||||
TrackedData::BLOCK_STATE_ID,
|
Metadata::new(
|
||||||
MetaDataType::BLOCK_STATE,
|
TrackedData::BLOCK_STATE_ID,
|
||||||
VarInt(i32::from(Block::TNT.default_state.id.as_u16())),
|
MetaDataType::BLOCK_STATE,
|
||||||
),
|
VarInt(i32::from(Block::TNT.default_state.id.as_u16())),
|
||||||
]);
|
),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -39,10 +39,13 @@ impl BoatEntity {
|
|||||||
self.left_paddle_moving.store(left, Ordering::Relaxed);
|
self.left_paddle_moving.store(left, Ordering::Relaxed);
|
||||||
self.right_paddle_moving.store(right, Ordering::Relaxed);
|
self.right_paddle_moving.store(right, Ordering::Relaxed);
|
||||||
|
|
||||||
self.vehicle.entity.send_meta_data(&[
|
self.vehicle.entity.send_meta_data(
|
||||||
Metadata::new(TrackedData::ID_PADDLE_LEFT, MetaDataType::BOOLEAN, left),
|
&[
|
||||||
Metadata::new(TrackedData::ID_PADDLE_RIGHT, MetaDataType::BOOLEAN, right),
|
Metadata::new(TrackedData::ID_PADDLE_LEFT, MetaDataType::BOOLEAN, left),
|
||||||
]);
|
Metadata::new(TrackedData::ID_PADDLE_RIGHT, MetaDataType::BOOLEAN, right),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_wobble_metadata(&self) {
|
fn send_wobble_metadata(&self) {
|
||||||
|
|||||||
@ -64,23 +64,29 @@ impl VehicleEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_wobble_metadata(&self) {
|
pub fn send_wobble_metadata(&self) {
|
||||||
self.entity.send_meta_data(&[
|
self.entity.send_meta_data(
|
||||||
Metadata::new(
|
&[
|
||||||
TrackedData::ID_HURT,
|
Metadata::new(
|
||||||
MetaDataType::INTEGER,
|
TrackedData::ID_HURT,
|
||||||
self.get_hurt_time(),
|
MetaDataType::INTEGER,
|
||||||
),
|
self.get_hurt_time(),
|
||||||
Metadata::new(
|
),
|
||||||
TrackedData::ID_HURTDIR,
|
Metadata::new(
|
||||||
MetaDataType::INTEGER,
|
TrackedData::ID_HURTDIR,
|
||||||
self.get_hurt_dir(),
|
MetaDataType::INTEGER,
|
||||||
),
|
self.get_hurt_dir(),
|
||||||
]);
|
),
|
||||||
self.entity.send_meta_data(&[Metadata::new(
|
],
|
||||||
TrackedData::ID_DAMAGE,
|
None,
|
||||||
MetaDataType::FLOAT,
|
);
|
||||||
self.get_damage(),
|
self.entity.send_meta_data(
|
||||||
)]);
|
&[Metadata::new(
|
||||||
|
TrackedData::ID_DAMAGE,
|
||||||
|
MetaDataType::FLOAT,
|
||||||
|
self.get_damage(),
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn kill_and_drop_self(&self) {
|
pub async fn kill_and_drop_self(&self) {
|
||||||
|
|||||||
@ -141,11 +141,13 @@ fn find_stronghold(world: &Arc<World>, origin: BlockPos) -> Option<BlockPos> {
|
|||||||
let generator = &level.world_gen;
|
let generator = &level.world_gen;
|
||||||
let seed = level.seed.0;
|
let seed = level.seed.0;
|
||||||
|
|
||||||
|
let global_cache = generator.global_structure_cache()?;
|
||||||
|
|
||||||
find_nearest_structure(
|
find_nearest_structure(
|
||||||
origin,
|
origin,
|
||||||
&[&StructureSet::get("strongholds").unwrap().placement],
|
&[&StructureSet::get("strongholds").unwrap().placement],
|
||||||
100, // max search radius in chunks, matches vanilla default
|
100, // max search radius in chunks, matches vanilla default
|
||||||
seed as i64,
|
seed as i64,
|
||||||
&generator.global_structure_cache,
|
global_cache,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -231,8 +231,8 @@ impl PumpkinServer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let tcp_listener = if server.basic_config.java_edition {
|
let tcp_listener = if server.advanced_config.networking.java.enabled {
|
||||||
let address = server.basic_config.java_edition_address;
|
let address = server.advanced_config.networking.java.address;
|
||||||
// Setup the TCP server socket.
|
// Setup the TCP server socket.
|
||||||
let listener = match TcpListener::bind(address).await {
|
let listener = match TcpListener::bind(address).await {
|
||||||
Ok(l) => l,
|
Ok(l) => l,
|
||||||
@ -275,7 +275,7 @@ impl PumpkinServer {
|
|||||||
|
|
||||||
let lan_broadcast = LANBroadcast::new(
|
let lan_broadcast = LANBroadcast::new(
|
||||||
&server.advanced_config.networking.lan_broadcast,
|
&server.advanced_config.networking.lan_broadcast,
|
||||||
&server.basic_config,
|
&server.advanced_config.networking.java.motd,
|
||||||
);
|
);
|
||||||
server.spawn_task(lan_broadcast.start(addr));
|
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(
|
Some(Arc::new(
|
||||||
UdpSocket::bind(server.basic_config.bedrock_edition_address)
|
UdpSocket::bind(server.advanced_config.networking.bedrock.address)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to bind UDP Socket"),
|
.expect("Failed to bind UDP Socket"),
|
||||||
))
|
))
|
||||||
|
|||||||
@ -110,34 +110,34 @@ async fn main() {
|
|||||||
.color_named(NamedColor::Gold)
|
.color_named(NamedColor::Gold)
|
||||||
.to_pretty_console()
|
.to_pretty_console()
|
||||||
);
|
);
|
||||||
let basic_config = &pumpkin_server.server.basic_config;
|
let advanced_config = &pumpkin_server.server.advanced_config;
|
||||||
info!(
|
info!(
|
||||||
"Server is now running. Connect using port: {}{}{}",
|
"Server is now running. Connect using port: {}{}{}",
|
||||||
if basic_config.java_edition {
|
if advanced_config.networking.java.enabled {
|
||||||
format!(
|
format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
TextComponent::text("Java Edition:")
|
TextComponent::text("Java Edition:")
|
||||||
.color_named(NamedColor::Yellow)
|
.color_named(NamedColor::Yellow)
|
||||||
.to_pretty_console(),
|
.to_pretty_console(),
|
||||||
TextComponent::text(format!("{}", basic_config.java_edition_address))
|
TextComponent::text(format!("{}", advanced_config.networking.java.address))
|
||||||
.color_named(NamedColor::DarkBlue)
|
.color_named(NamedColor::DarkBlue)
|
||||||
.to_pretty_console()
|
.to_pretty_console()
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
TextComponent::text(String::new()).to_pretty_console()
|
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
|
" | " // Separator if both are enabled
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
},
|
},
|
||||||
if basic_config.bedrock_edition {
|
if advanced_config.networking.bedrock.enabled {
|
||||||
format!(
|
format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
TextComponent::text("Bedrock Edition:")
|
TextComponent::text("Bedrock Edition:")
|
||||||
.color_named(NamedColor::Gold)
|
.color_named(NamedColor::Gold)
|
||||||
.to_pretty_console(),
|
.to_pretty_console(),
|
||||||
TextComponent::text(format!("{}", basic_config.bedrock_edition_address))
|
TextComponent::text(format!("{}", advanced_config.networking.bedrock.address))
|
||||||
.color_named(NamedColor::DarkBlue)
|
.color_named(NamedColor::DarkBlue)
|
||||||
.to_pretty_console()
|
.to_pretty_console()
|
||||||
)
|
)
|
||||||
|
|||||||
@ -107,7 +107,8 @@ impl BedrockClient {
|
|||||||
let compression = server
|
let compression = server
|
||||||
.advanced_config
|
.advanced_config
|
||||||
.networking
|
.networking
|
||||||
.bedrock_compression
|
.bedrock
|
||||||
|
.compression
|
||||||
.info
|
.info
|
||||||
.clone();
|
.clone();
|
||||||
self.send_game_packet(&CNetworkSettings::new(
|
self.send_game_packet(&CNetworkSettings::new(
|
||||||
@ -136,13 +137,19 @@ impl BedrockClient {
|
|||||||
server: &Server,
|
server: &Server,
|
||||||
) -> Result<Option<PacketHandlerResult>, LoginError> {
|
) -> Result<Option<PacketHandlerResult>, LoginError> {
|
||||||
let auth_payload: AuthPayload = serde_json::from_slice(&packet.jwt)?;
|
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 {
|
match auth_payload.authentication_type {
|
||||||
AuthenticationType::Full => {
|
AuthenticationType::Full => {
|
||||||
verify_oidc_token_path(server, &auth_payload.token, false)?
|
verify_oidc_token_path(server, &auth_payload.token, false)?
|
||||||
}
|
}
|
||||||
AuthenticationType::SelfSigned => {
|
AuthenticationType::SelfSigned => {
|
||||||
if server.advanced_config.networking.authentication.enabled {
|
if server
|
||||||
|
.advanced_config
|
||||||
|
.networking
|
||||||
|
.bedrock
|
||||||
|
.authentication
|
||||||
|
.enabled
|
||||||
|
{
|
||||||
return Err(LoginError::SelfSignedNotAllowed);
|
return Err(LoginError::SelfSignedNotAllowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +188,7 @@ impl BedrockClient {
|
|||||||
profile_actions: None,
|
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)
|
let client_public_key = pumpkin_util::jwt::extract_cpk_from_token(&auth_payload.token)
|
||||||
.map_err(LoginError::ChainValidationFailed)?;
|
.map_err(LoginError::ChainValidationFailed)?;
|
||||||
|
|
||||||
|
|||||||
@ -1290,7 +1290,7 @@ impl BedrockClient {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(0, |p| p.online) as u32
|
.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(
|
Self::send_offline_packet(
|
||||||
&CNoFreeIncomingConnections::new(server.server_guid),
|
&CNoFreeIncomingConnections::new(server.server_guid),
|
||||||
addr,
|
addr,
|
||||||
|
|||||||
@ -3,7 +3,12 @@ use std::{
|
|||||||
sync::{Arc, atomic::Ordering},
|
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_inventory::screen_handler::{InventoryPlayer, ScreenHandler};
|
||||||
use pumpkin_macros::send_cancellable;
|
use pumpkin_macros::send_cancellable;
|
||||||
use pumpkin_protocol::bedrock::{
|
use pumpkin_protocol::bedrock::{
|
||||||
@ -37,7 +42,7 @@ use pumpkin_protocol::{
|
|||||||
codec::{var_int::VarInt, var_long::VarLong, var_uint::VarUInt, var_ulong::VarULong},
|
codec::{var_int::VarInt, var_long::VarLong, var_uint::VarUInt, var_ulong::VarULong},
|
||||||
java::client::play::{Animation, CEntityAnimation, CSetSelectedSlot, CSystemChatMessage},
|
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::inventory::Inventory;
|
||||||
use pumpkin_world::world::BlockFlags;
|
use pumpkin_world::world::BlockFlags;
|
||||||
@ -47,8 +52,10 @@ use crate::{
|
|||||||
entity::{EntityBase, player::Player},
|
entity::{EntityBase, player::Player},
|
||||||
net::{DisconnectReason, bedrock::BedrockClient},
|
net::{DisconnectReason, bedrock::BedrockClient},
|
||||||
plugin::player::{
|
plugin::player::{
|
||||||
item_held::PlayerItemHeldEvent, player_chat::PlayerChatEvent,
|
item_held::PlayerItemHeldEvent,
|
||||||
|
player_chat::PlayerChatEvent,
|
||||||
player_command_send::PlayerCommandSendEvent,
|
player_command_send::PlayerCommandSendEvent,
|
||||||
|
player_interact_event::{InteractAction, PlayerInteractEvent},
|
||||||
player_toggle_flight_event::PlayerToggleFlightEvent,
|
player_toggle_flight_event::PlayerToggleFlightEvent,
|
||||||
},
|
},
|
||||||
server::{Server, seasonal_events},
|
server::{Server, seasonal_events},
|
||||||
@ -152,8 +159,10 @@ impl BedrockClient {
|
|||||||
}
|
}
|
||||||
let server = player.world().server.upgrade().unwrap();
|
let server = player.world().server.upgrade().unwrap();
|
||||||
|
|
||||||
let view_distance =
|
let view_distance = chunk_radius.clamp(
|
||||||
chunk_radius.clamp(2, NonZeroI32::from(server.basic_config.view_distance).get());
|
2,
|
||||||
|
NonZeroI32::from(server.advanced_config.networking.bedrock.view_distance).get(),
|
||||||
|
);
|
||||||
|
|
||||||
self.enqueue_packet(&CChunkRadiusUpdate {
|
self.enqueue_packet(&CChunkRadiusUpdate {
|
||||||
chunk_radius: VarInt(view_distance),
|
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::<ConsumableImpl>().is_some()
|
||||||
|
|| held.get_data_component::<BlocksAttacksImpl>().is_some()
|
||||||
|
{
|
||||||
|
if let Some(food) = held.get_data_component::<FoodImpl>() {
|
||||||
|
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::<EquippableImpl>() {
|
||||||
|
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) => {
|
TransactionData::UseItemOnEntity(data) => {
|
||||||
|
|||||||
@ -30,12 +30,12 @@ impl BedrockClient {
|
|||||||
let motd_string = ServerInfo {
|
let motd_string = ServerInfo {
|
||||||
edition: "MCPE",
|
edition: "MCPE",
|
||||||
// TODO The default motd is to long to be displayed completely
|
// 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,
|
protocol_version: CURRENT_BEDROCK_MC_PROTOCOL,
|
||||||
version_name: CURRENT_BEDROCK_MC_VERSION,
|
version_name: CURRENT_BEDROCK_MC_VERSION,
|
||||||
player_count,
|
player_count,
|
||||||
// A large number looks wreird on the client worlds window
|
// 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,
|
server_unique_id: server.server_guid,
|
||||||
motd_line_2: server.basic_config.default_level_name.clone(),
|
motd_line_2: server.basic_config.default_level_name.clone(),
|
||||||
game_mode: server.defaultgamemode.lock().await.gamemode.to_str(),
|
game_mode: server.defaultgamemode.lock().await.gamemode.to_str(),
|
||||||
|
|||||||
@ -34,7 +34,7 @@ impl JavaClient {
|
|||||||
// Don't allow new logons when the server is full.
|
// 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.
|
// 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.
|
// 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 {
|
if max_players > 0 && server.get_player_count() >= max_players as usize {
|
||||||
self.kick(TextComponent::translate_cross(
|
self.kick(TextComponent::translate_cross(
|
||||||
translation::java::MULTIPLAYER_DISCONNECT_SERVER_FULL,
|
translation::java::MULTIPLAYER_DISCONNECT_SERVER_FULL,
|
||||||
@ -74,7 +74,7 @@ impl JavaClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let id = if server.basic_config.online_mode {
|
let id = if server.advanced_config.networking.java.online_mode {
|
||||||
login_start.uuid
|
login_start.uuid
|
||||||
} else {
|
} else {
|
||||||
offline_uuid(&login_start.name).expect("This is very not safe and bad")
|
offline_uuid(&login_start.name).expect("This is very not safe and bad")
|
||||||
@ -87,16 +87,19 @@ impl JavaClient {
|
|||||||
profile_actions: None,
|
profile_actions: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if server.advanced_config.networking.java_compression.enabled {
|
if server.advanced_config.networking.java.compression.enabled {
|
||||||
self.enable_compression(server).await;
|
self.enable_compression(server).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if server.basic_config.encryption {
|
if server.advanced_config.networking.java.encryption {
|
||||||
let verify_token: [u8; 4] = rand::random();
|
let verify_token: [u8; 4] = rand::random();
|
||||||
// Wait until we have sent the encryption packet to the client
|
// Wait until we have sent the encryption packet to the client
|
||||||
self.send_packet_now(
|
self.send_packet_now(
|
||||||
&server
|
&server
|
||||||
.encryption_request(&verify_token, server.basic_config.online_mode)
|
.encryption_request(
|
||||||
|
&verify_token,
|
||||||
|
server.advanced_config.networking.java.online_mode,
|
||||||
|
)
|
||||||
.await,
|
.await,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
@ -132,7 +135,7 @@ impl JavaClient {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if server.basic_config.online_mode {
|
if server.advanced_config.networking.java.online_mode {
|
||||||
// Online mode auth
|
// Online mode auth
|
||||||
match self
|
match self
|
||||||
.authenticate(server, &shared_secret, &profile.name)
|
.authenticate(server, &shared_secret, &profile.name)
|
||||||
@ -201,7 +204,8 @@ impl JavaClient {
|
|||||||
let compression = server
|
let compression = server
|
||||||
.advanced_config
|
.advanced_config
|
||||||
.networking
|
.networking
|
||||||
.java_compression
|
.java
|
||||||
|
.compression
|
||||||
.info
|
.info
|
||||||
.clone();
|
.clone();
|
||||||
// We want to wait until we have sent the compression packet to the client
|
// We want to wait until we have sent the compression packet to the client
|
||||||
@ -236,7 +240,7 @@ impl JavaClient {
|
|||||||
username,
|
username,
|
||||||
&hash,
|
&hash,
|
||||||
&ip,
|
&ip,
|
||||||
&server.advanced_config.networking.authentication,
|
&server.advanced_config.networking.java.authentication,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Check if the player should join
|
// Check if the player should join
|
||||||
@ -244,6 +248,7 @@ impl JavaClient {
|
|||||||
if server
|
if server
|
||||||
.advanced_config
|
.advanced_config
|
||||||
.networking
|
.networking
|
||||||
|
.java
|
||||||
.authentication
|
.authentication
|
||||||
.player_profile
|
.player_profile
|
||||||
.allow_banned_players
|
.allow_banned_players
|
||||||
@ -251,6 +256,7 @@ impl JavaClient {
|
|||||||
for allowed in &server
|
for allowed in &server
|
||||||
.advanced_config
|
.advanced_config
|
||||||
.networking
|
.networking
|
||||||
|
.java
|
||||||
.authentication
|
.authentication
|
||||||
.player_profile
|
.player_profile
|
||||||
.allowed_actions
|
.allowed_actions
|
||||||
@ -270,7 +276,12 @@ impl JavaClient {
|
|||||||
for property in profile.properties.load().iter() {
|
for property in profile.properties.load().iter() {
|
||||||
authentication::validate_textures(
|
authentication::validate_textures(
|
||||||
property,
|
property,
|
||||||
&server.advanced_config.networking.authentication.textures,
|
&server
|
||||||
|
.advanced_config
|
||||||
|
.networking
|
||||||
|
.java
|
||||||
|
.authentication
|
||||||
|
.textures,
|
||||||
)
|
)
|
||||||
.map_err(AuthError::TextureError)?;
|
.map_err(AuthError::TextureError)?;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use pumpkin_config::{BasicConfiguration, LANBroadcastConfig};
|
use pumpkin_config::LANBroadcastConfig;
|
||||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@ -23,7 +23,7 @@ pub struct LANBroadcast {
|
|||||||
impl LANBroadcast {
|
impl LANBroadcast {
|
||||||
/// Creates a new LAN broadcast instance from the provided configuration
|
/// Creates a new LAN broadcast instance from the provided configuration
|
||||||
#[must_use]
|
#[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 port = config.port.unwrap_or(0);
|
||||||
|
|
||||||
let advanced_motd = config.motd.clone().unwrap_or_default();
|
let advanced_motd = config.motd.clone().unwrap_or_default();
|
||||||
@ -32,7 +32,7 @@ impl LANBroadcast {
|
|||||||
warn!(
|
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."
|
"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 {
|
} else {
|
||||||
advanced_motd
|
advanced_motd
|
||||||
};
|
};
|
||||||
|
|||||||
@ -87,6 +87,7 @@ pub async fn start_query_handler(server: Arc<Server>, query_addr: SocketAddr) {
|
|||||||
// Errors of packets that don't meet the format aren't returned since we won't handle them anyway
|
// 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
|
// 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
|
// since those errors need to be corrected by server owner
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
#[inline]
|
#[inline]
|
||||||
async fn handle_packet(
|
async fn handle_packet(
|
||||||
buf: Vec<u8>,
|
buf: Vec<u8>,
|
||||||
@ -156,7 +157,9 @@ async fn handle_packet(
|
|||||||
|
|
||||||
let response = CFullStatus {
|
let response = CFullStatus {
|
||||||
session_id: packet.session_id,
|
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)?,
|
version: CString::new(CURRENT_MC_VERSION)?,
|
||||||
plugins: CString::new(plugins)?,
|
plugins: CString::new(plugins)?,
|
||||||
map: CString::new(
|
map: CString::new(
|
||||||
@ -167,7 +170,8 @@ async fn handle_packet(
|
|||||||
.map_or("world", |w| w.get_world_name()),
|
.map_or("world", |w| w.get_world_name()),
|
||||||
)?,
|
)?,
|
||||||
num_players: server.get_player_count(),
|
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_port: bound_addr.port(),
|
||||||
host_ip: CString::new(bound_addr.ip().to_string())?,
|
host_ip: CString::new(bound_addr.ip().to_string())?,
|
||||||
players,
|
players,
|
||||||
@ -179,7 +183,9 @@ async fn handle_packet(
|
|||||||
} else {
|
} else {
|
||||||
let response = CBasicStatus {
|
let response = CBasicStatus {
|
||||||
session_id: packet.session_id,
|
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(
|
map: CString::new(
|
||||||
server
|
server
|
||||||
.worlds
|
.worlds
|
||||||
@ -188,7 +194,8 @@ async fn handle_packet(
|
|||||||
.map_or("world", |w| w.get_world_name()),
|
.map_or("world", |w| w.get_world_name()),
|
||||||
)?,
|
)?,
|
||||||
num_players: server.get_player_count(),
|
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_port: bound_addr.port(),
|
||||||
host_ip: CString::new(bound_addr.ip().to_string())?,
|
host_ip: CString::new(bound_addr.ip().to_string())?,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -292,7 +292,7 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
.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<Server>) -> wasmtime::Result<bool> {
|
async fn is_hardcore(&mut self, _rep: Resource<Server>) -> wasmtime::Result<bool> {
|
||||||
@ -310,7 +310,12 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
.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<Server>) -> wasmtime::Result<String> {
|
async fn get_motd(&mut self, _rep: Resource<Server>) -> wasmtime::Result<String> {
|
||||||
@ -319,7 +324,7 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
.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<Server>) -> wasmtime::Result<bool> {
|
async fn has_whitelist(&mut self, _rep: Resource<Server>) -> wasmtime::Result<bool> {
|
||||||
@ -355,7 +360,7 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
.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<Server>) -> wasmtime::Result<u8> {
|
async fn get_simulation_distance(&mut self, _rep: Resource<Server>) -> wasmtime::Result<u8> {
|
||||||
@ -364,7 +369,12 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
.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(
|
async fn get_default_gamemode(
|
||||||
|
|||||||
@ -68,8 +68,8 @@ impl<'a> CachedBranding {
|
|||||||
|
|
||||||
impl CachedStatus {
|
impl CachedStatus {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(config: &BasicConfiguration) -> Self {
|
pub fn new(config: &BasicConfiguration, motd: &str, max_players: u32) -> Self {
|
||||||
let status_response = Self::build_response(config);
|
let status_response = Self::build_response(config, motd, max_players);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
status_response,
|
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 {
|
let favicon = if config.use_favicon {
|
||||||
config.favicon_path.as_ref().map_or_else(
|
config.favicon_path.as_ref().map_or_else(
|
||||||
|| {
|
|| {
|
||||||
@ -185,11 +189,11 @@ impl CachedStatus {
|
|||||||
protocol: LOWEST_SUPPORTED_MC_VERSION.protocol_version() as u32,
|
protocol: LOWEST_SUPPORTED_MC_VERSION.protocol_version() as u32,
|
||||||
}),
|
}),
|
||||||
players: Some(Players {
|
players: Some(Players {
|
||||||
max: config.max_players,
|
max: max_players,
|
||||||
online: 0,
|
online: 0,
|
||||||
sample: vec![],
|
sample: vec![],
|
||||||
}),
|
}),
|
||||||
description: config.motd.clone(),
|
description: motd.to_string(),
|
||||||
favicon,
|
favicon,
|
||||||
// This should stay true even when reports are disabled.
|
// This should stay true even when reports are disabled.
|
||||||
// It prevents the annoying popup when joining the server.
|
// It prevents the annoying popup when joining the server.
|
||||||
@ -200,6 +204,10 @@ impl CachedStatus {
|
|||||||
|
|
||||||
impl Default for CachedStatus {
|
impl Default for CachedStatus {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(&BasicConfiguration::default())
|
Self::new(
|
||||||
|
&BasicConfiguration::default(),
|
||||||
|
"A blazingly fast Pumpkin server!",
|
||||||
|
1000,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -199,7 +199,11 @@ impl Server {
|
|||||||
let seed = level_info.world_gen_settings.seed;
|
let seed = level_info.world_gen_settings.seed;
|
||||||
let level_info = Arc::new(ArcSwap::new(Arc::new(level_info)));
|
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 {
|
let defaultgamemode = Mutex::new(DefaultGamemode {
|
||||||
gamemode: basic_config.default_gamemode,
|
gamemode: basic_config.default_gamemode,
|
||||||
});
|
});
|
||||||
@ -218,7 +222,7 @@ impl Server {
|
|||||||
let tick_rate_manager = Arc::new(ServerTickRateManager::new(basic_config.tps));
|
let tick_rate_manager = Arc::new(ServerTickRateManager::new(basic_config.tps));
|
||||||
|
|
||||||
let mojang_keys_task = tokio::spawn({
|
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;
|
let allow_chat = basic_config.allow_chat_reports;
|
||||||
async move {
|
async move {
|
||||||
if allow_chat {
|
if allow_chat {
|
||||||
@ -351,14 +355,24 @@ impl Server {
|
|||||||
|
|
||||||
info!("All worlds loaded successfully.");
|
info!("All worlds loaded successfully.");
|
||||||
|
|
||||||
if server.basic_config.online_mode {
|
if server.advanced_config.networking.bedrock.online_mode {
|
||||||
let server_clone = server.clone();
|
let server_clone = server.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
server_clone
|
server_clone
|
||||||
.bedrock_oidc_keys
|
.bedrock_oidc_keys
|
||||||
.get_or_init(|| async {
|
.get_or_init(|| async {
|
||||||
tokio::task::block_in_place(|| {
|
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}");
|
error!("Failed to fetch Bedrock OIDC keys: {e}");
|
||||||
(String::new(), pumpkin_util::jwt::Jwks { keys: Vec::new() })
|
(String::new(), pumpkin_util::jwt::Jwks { keys: Vec::new() })
|
||||||
})
|
})
|
||||||
|
|||||||
@ -14,10 +14,15 @@ use crate::{
|
|||||||
|
|
||||||
pub fn get_view_distance(player: &Player) -> NonZeroU8 {
|
pub fn get_view_distance(player: &Player) -> NonZeroU8 {
|
||||||
let server = player.world().server.upgrade().unwrap();
|
let server = player.world().server.upgrade().unwrap();
|
||||||
player.config.load().view_distance.clamp(
|
let max_view_distance = match player.client.as_ref() {
|
||||||
NonZeroU8::new(2).unwrap(),
|
ClientPlatform::Java(_) => server.advanced_config.networking.java.view_distance,
|
||||||
server.basic_config.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
|
// Checks if the target chunk is within the view distance
|
||||||
|
|||||||
@ -1849,7 +1849,13 @@ impl World {
|
|||||||
has_start_with_map_enabled: false,
|
has_start_with_map_enabled: false,
|
||||||
// TODO Bedrock permission level are different
|
// TODO Bedrock permission level are different
|
||||||
permission_level: VarInt(2),
|
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_behavior_pack: false,
|
||||||
has_locked_resource_pack: false,
|
has_locked_resource_pack: false,
|
||||||
is_from_locked_world_template: false,
|
is_from_locked_world_template: false,
|
||||||
@ -2535,9 +2541,27 @@ impl World {
|
|||||||
entity_id,
|
entity_id,
|
||||||
base_config.hardcore,
|
base_config.hardcore,
|
||||||
dimensions,
|
dimensions,
|
||||||
base_config.max_players.try_into().unwrap(),
|
server
|
||||||
base_config.view_distance.get().into(), // TODO: view distance
|
.advanced_config
|
||||||
base_config.simulation_distance.get().into(), // TODO: sim view dinstance
|
.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,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
@ -2555,7 +2579,7 @@ impl World {
|
|||||||
VarInt(player.get_entity().portal_cooldown.load(Ordering::Relaxed) as i32),
|
VarInt(player.get_entity().portal_cooldown.load(Ordering::Relaxed) as i32),
|
||||||
self.sea_level.into(),
|
self.sea_level.into(),
|
||||||
),
|
),
|
||||||
base_config.online_mode,
|
server.advanced_config.networking.java.online_mode,
|
||||||
// This should stay true even when reports are disabled.
|
// This should stay true even when reports are disabled.
|
||||||
// It prevents the annoying popup when joining the server.
|
// It prevents the annoying popup when joining the server.
|
||||||
true,
|
true,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user