Multi Dimension refactor
This commit is contained in:
parent
30f1a88126
commit
06c82c75f0
20
Cargo.lock
generated
20
Cargo.lock
generated
@ -194,9 +194,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.9.0"
|
version = "2.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
@ -510,7 +510,7 @@ version = "0.29.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
|
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
"crossterm_winapi",
|
"crossterm_winapi",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"document-features",
|
"document-features",
|
||||||
@ -1844,7 +1844,7 @@ version = "0.1.0-dev+1.21.5"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64",
|
"base64",
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
@ -1968,7 +1968,7 @@ version = "0.1.0-dev+1.21.5"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"cfb8",
|
"cfb8",
|
||||||
"flate2",
|
"flate2",
|
||||||
@ -2022,7 +2022,7 @@ name = "pumpkin-world"
|
|||||||
version = "0.1.0-dev+1.21.5"
|
version = "0.1.0-dev+1.21.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"criterion",
|
"criterion",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
@ -2214,7 +2214,7 @@ version = "0.5.12"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
|
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2368,7 +2368,7 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
|
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
@ -2719,7 +2719,7 @@ version = "0.6.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"system-configuration-sys",
|
"system-configuration-sys",
|
||||||
]
|
]
|
||||||
@ -3625,7 +3625,7 @@ version = "0.39.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@ -32,7 +32,7 @@ cfb8 = "0.8"
|
|||||||
async-compression = { version = "0.4.23", features = ["tokio", "zlib"] }
|
async-compression = { version = "0.4.23", features = ["tokio", "zlib"] }
|
||||||
|
|
||||||
take_mut = "0.2.2"
|
take_mut = "0.2.2"
|
||||||
bitflags = "2.9.0"
|
bitflags = "2.9.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# Validate correctness
|
# Validate correctness
|
||||||
|
|||||||
@ -95,6 +95,16 @@ impl DimensionType {
|
|||||||
Self::TheNether => Identifier::vanilla("the_nether"),
|
Self::TheNether => Identifier::vanilla("the_nether"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_name(name: &str) -> Option<Self> {
|
||||||
|
match name {
|
||||||
|
"minecraft:overworld" => Some(Self::Overworld),
|
||||||
|
"minecraft:overworld_caves" => Some(Self::OverworldCaves),
|
||||||
|
"minecraft:the_end" => Some(Self::TheEnd),
|
||||||
|
"minecraft:the_nether" => Some(Self::TheNether),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Registry {
|
impl Registry {
|
||||||
|
|||||||
@ -26,7 +26,7 @@ crossbeam.workspace = true
|
|||||||
|
|
||||||
num-derive = "0.4"
|
num-derive = "0.4"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
bitflags = "2.9.0"
|
bitflags = "2.9.1"
|
||||||
|
|
||||||
dashmap = "6.1"
|
dashmap = "6.1"
|
||||||
|
|
||||||
|
|||||||
@ -80,7 +80,7 @@ fn initialize_level(
|
|||||||
let (send, mut recv) = tokio::sync::mpsc::unbounded_channel();
|
let (send, mut recv) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
|
||||||
// Our data dir is empty, so we're generating new chunks here
|
// Our data dir is empty, so we're generating new chunks here
|
||||||
let level_to_save = Arc::new(Level::from_root_folder(root_dir.clone()));
|
let level_to_save = Arc::new(Level::from_root_folder(root_dir.clone(), 123));
|
||||||
println!("Level Seed is: {}", level_to_save.seed.0);
|
println!("Level Seed is: {}", level_to_save.seed.0);
|
||||||
|
|
||||||
let level_to_fetch = level_to_save.clone();
|
let level_to_fetch = level_to_save.clone();
|
||||||
@ -194,7 +194,7 @@ fn bench_chunk_io(c: &mut Criterion) {
|
|||||||
&chunks,
|
&chunks,
|
||||||
|b, chunks| {
|
|b, chunks| {
|
||||||
b.to_async(&async_handler).iter(async || {
|
b.to_async(&async_handler).iter(async || {
|
||||||
let level = Arc::new(Level::from_root_folder(root_dir.clone()));
|
let level = Arc::new(Level::from_root_folder(root_dir.clone(), 123));
|
||||||
test_writes(&level, chunks.to_vec()).await
|
test_writes(&level, chunks.to_vec()).await
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -220,7 +220,7 @@ fn bench_chunk_io(c: &mut Criterion) {
|
|||||||
&positions,
|
&positions,
|
||||||
|b, positions| {
|
|b, positions| {
|
||||||
b.to_async(&async_handler).iter(async || {
|
b.to_async(&async_handler).iter(async || {
|
||||||
let level = Arc::new(Level::from_root_folder(root_dir.clone()));
|
let level = Arc::new(Level::from_root_folder(root_dir.clone(), 123));
|
||||||
test_reads(&level, positions.to_vec()).await
|
test_reads(&level, positions.to_vec()).await
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,12 +13,12 @@ pub enum Dimension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Dimension {
|
impl Dimension {
|
||||||
pub fn into_level(&self, mut base_directory: PathBuf) -> Level {
|
pub fn into_level(&self, mut base_directory: PathBuf, seed: i64) -> Level {
|
||||||
match self {
|
match self {
|
||||||
Dimension::Overworld => {}
|
Dimension::Overworld => {}
|
||||||
Dimension::Nether => base_directory.push("DIM-1"),
|
Dimension::Nether => base_directory.push("DIM-1"),
|
||||||
Dimension::End => base_directory.push("DIM1"),
|
Dimension::End => base_directory.push("DIM1"),
|
||||||
}
|
}
|
||||||
Level::from_root_folder(base_directory)
|
Level::from_root_folder(base_directory, seed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -137,7 +137,7 @@ pub enum MaterialCondition {
|
|||||||
#[serde(rename = "minecraft:noise_threshold")]
|
#[serde(rename = "minecraft:noise_threshold")]
|
||||||
NoiseThreshold(NoiseThresholdMaterialCondition),
|
NoiseThreshold(NoiseThresholdMaterialCondition),
|
||||||
#[serde(rename = "minecraft:vertical_gradient")]
|
#[serde(rename = "minecraft:vertical_gradient")]
|
||||||
VerticalGradient(VerticalGradientMaterialCondition),
|
VerticalGradient(Box<VerticalGradientMaterialCondition>),
|
||||||
#[serde(rename = "minecraft:y_above")]
|
#[serde(rename = "minecraft:y_above")]
|
||||||
YAbove(AboveYMaterialCondition),
|
YAbove(AboveYMaterialCondition),
|
||||||
#[serde(rename = "minecraft:water")]
|
#[serde(rename = "minecraft:water")]
|
||||||
|
|||||||
@ -18,7 +18,7 @@ pub enum MaterialRule {
|
|||||||
#[serde(rename = "minecraft:sequence")]
|
#[serde(rename = "minecraft:sequence")]
|
||||||
Sequence(SequenceMaterialRule),
|
Sequence(SequenceMaterialRule),
|
||||||
#[serde(rename = "minecraft:condition")]
|
#[serde(rename = "minecraft:condition")]
|
||||||
Condition(ConditionMaterialRule),
|
Condition(Box<ConditionMaterialRule>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaterialRule {
|
impl MaterialRule {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fs,
|
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{
|
sync::{
|
||||||
Arc,
|
Arc,
|
||||||
@ -25,12 +24,7 @@ use crate::{
|
|||||||
io::{ChunkIO, LoadedData, chunk_file_manager::ChunkFileManager},
|
io::{ChunkIO, LoadedData, chunk_file_manager::ChunkFileManager},
|
||||||
},
|
},
|
||||||
generation::{Seed, WorldGenerator, get_world_gen},
|
generation::{Seed, WorldGenerator, get_world_gen},
|
||||||
lock::{LevelLocker, anvil::AnvilLevelLocker},
|
|
||||||
world::SimpleWorld,
|
world::SimpleWorld,
|
||||||
world_info::{
|
|
||||||
LevelData, WorldInfoError, WorldInfoReader, WorldInfoWriter,
|
|
||||||
anvil::{AnvilLevelInfo, LEVEL_DAT_BACKUP_FILE_NAME, LEVEL_DAT_FILE_NAME},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type SyncChunk = Arc<RwLock<ChunkData>>;
|
pub type SyncChunk = Arc<RwLock<ChunkData>>;
|
||||||
@ -46,8 +40,6 @@ pub type SyncChunk = Arc<RwLock<ChunkData>>;
|
|||||||
/// For more details on world generation, refer to the `WorldGenerator` module.
|
/// For more details on world generation, refer to the `WorldGenerator` module.
|
||||||
pub struct Level {
|
pub struct Level {
|
||||||
pub seed: Seed,
|
pub seed: Seed,
|
||||||
pub level_info: LevelData,
|
|
||||||
world_info_writer: Arc<dyn WorldInfoWriter>,
|
|
||||||
level_folder: LevelFolder,
|
level_folder: LevelFolder,
|
||||||
|
|
||||||
// Holds this level's spawn chunks, which are always loaded
|
// Holds this level's spawn chunks, which are always loaded
|
||||||
@ -60,9 +52,7 @@ pub struct Level {
|
|||||||
|
|
||||||
chunk_saver: Arc<dyn ChunkIO<Data = SyncChunk>>,
|
chunk_saver: Arc<dyn ChunkIO<Data = SyncChunk>>,
|
||||||
world_gen: Arc<dyn WorldGenerator>,
|
world_gen: Arc<dyn WorldGenerator>,
|
||||||
// Gets unlocked when dropped
|
|
||||||
// TODO: Make this a trait
|
|
||||||
_locker: Arc<AnvilLevelLocker>,
|
|
||||||
block_ticks: Arc<Mutex<Vec<ScheduledTick>>>,
|
block_ticks: Arc<Mutex<Vec<ScheduledTick>>>,
|
||||||
fluid_ticks: Arc<Mutex<Vec<ScheduledTick>>>,
|
fluid_ticks: Arc<Mutex<Vec<ScheduledTick>>>,
|
||||||
/// Tracks tasks associated with this world instance
|
/// Tracks tasks associated with this world instance
|
||||||
@ -78,7 +68,7 @@ pub struct LevelFolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Level {
|
impl Level {
|
||||||
pub fn from_root_folder(root_folder: PathBuf) -> Self {
|
pub fn from_root_folder(root_folder: PathBuf, seed: i64) -> Self {
|
||||||
// If we are using an already existing world we want to read the seed from the level.dat, If not we want to check if there is a seed in the config, if not lets create a random one
|
// If we are using an already existing world we want to read the seed from the level.dat, If not we want to check if there is a seed in the config, if not lets create a random one
|
||||||
let region_folder = root_folder.join("region");
|
let region_folder = root_folder.join("region");
|
||||||
if !region_folder.exists() {
|
if !region_folder.exists() {
|
||||||
@ -89,40 +79,11 @@ impl Level {
|
|||||||
region_folder,
|
region_folder,
|
||||||
};
|
};
|
||||||
|
|
||||||
// if we fail to lock, lets crash ???. maybe not the best solution when we have a large server with many worlds and one is locked.
|
|
||||||
// So TODO
|
|
||||||
let locker = AnvilLevelLocker::look(&level_folder).expect("Failed to lock level");
|
|
||||||
|
|
||||||
// TODO: Load info correctly based on world format type
|
// TODO: Load info correctly based on world format type
|
||||||
let level_info = AnvilLevelInfo.read_world_info(&level_folder);
|
|
||||||
if let Err(error) = &level_info {
|
|
||||||
match error {
|
|
||||||
// If it doesn't exist, just make a new one
|
|
||||||
WorldInfoError::InfoNotFound => (),
|
|
||||||
WorldInfoError::UnsupportedVersion(version) => {
|
|
||||||
log::error!("Failed to load world info!, {version}");
|
|
||||||
log::error!("{error}");
|
|
||||||
panic!("Unsupported world data! See the logs for more info.");
|
|
||||||
}
|
|
||||||
e => {
|
|
||||||
panic!("World Error {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let dat_path = level_folder.root_folder.join(LEVEL_DAT_FILE_NAME);
|
|
||||||
if dat_path.exists() {
|
|
||||||
let backup_path = level_folder.root_folder.join(LEVEL_DAT_BACKUP_FILE_NAME);
|
|
||||||
fs::copy(dat_path, backup_path).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let level_info = level_info.unwrap_or_default(); // TODO: Improve error handling
|
log::info!("Loading world with seed: {}", seed);
|
||||||
log::info!(
|
|
||||||
"Loading world with seed: {}",
|
|
||||||
level_info.world_gen_settings.seed
|
|
||||||
);
|
|
||||||
|
|
||||||
let seed = Seed(level_info.world_gen_settings.seed as u64);
|
let seed = Seed(seed as u64);
|
||||||
let world_gen = get_world_gen(seed).into();
|
let world_gen = get_world_gen(seed).into();
|
||||||
|
|
||||||
let chunk_saver: Arc<dyn ChunkIO<Data = SyncChunk>> = match advanced_config().chunk.format {
|
let chunk_saver: Arc<dyn ChunkIO<Data = SyncChunk>> = match advanced_config().chunk.format {
|
||||||
@ -134,14 +95,11 @@ impl Level {
|
|||||||
Self {
|
Self {
|
||||||
seed,
|
seed,
|
||||||
world_gen,
|
world_gen,
|
||||||
world_info_writer: Arc::new(AnvilLevelInfo),
|
|
||||||
level_folder,
|
level_folder,
|
||||||
chunk_saver,
|
chunk_saver,
|
||||||
spawn_chunks: Arc::new(DashMap::new()),
|
spawn_chunks: Arc::new(DashMap::new()),
|
||||||
loaded_chunks: Arc::new(DashMap::new()),
|
loaded_chunks: Arc::new(DashMap::new()),
|
||||||
chunk_watchers: Arc::new(DashMap::new()),
|
chunk_watchers: Arc::new(DashMap::new()),
|
||||||
level_info,
|
|
||||||
_locker: Arc::new(locker),
|
|
||||||
tasks: TaskTracker::new(),
|
tasks: TaskTracker::new(),
|
||||||
shutdown_notifier: Notify::new(),
|
shutdown_notifier: Notify::new(),
|
||||||
block_ticks: Arc::new(Mutex::new(Vec::new())),
|
block_ticks: Arc::new(Mutex::new(Vec::new())),
|
||||||
@ -182,16 +140,6 @@ impl Level {
|
|||||||
// TODO: I think the chunk_saver should be at the server level
|
// TODO: I think the chunk_saver should be at the server level
|
||||||
self.chunk_saver.clear_watched_chunks().await;
|
self.chunk_saver.clear_watched_chunks().await;
|
||||||
self.write_chunks(chunks_to_write).await;
|
self.write_chunks(chunks_to_write).await;
|
||||||
|
|
||||||
// then lets save the world info
|
|
||||||
let result = self
|
|
||||||
.world_info_writer
|
|
||||||
.write_world_info(self.level_info.clone(), &self.level_folder);
|
|
||||||
|
|
||||||
// Lets not stop the overall save for this
|
|
||||||
if let Err(err) = result {
|
|
||||||
log::error!("Failed to save level.dat: {err}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn loaded_chunk_count(&self) -> usize {
|
pub fn loaded_chunk_count(&self) -> usize {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ mod generation;
|
|||||||
pub mod inventory;
|
pub mod inventory;
|
||||||
pub mod item;
|
pub mod item;
|
||||||
pub mod level;
|
pub mod level;
|
||||||
mod lock;
|
pub mod lock;
|
||||||
pub mod world;
|
pub mod world;
|
||||||
pub mod world_info;
|
pub mod world_info;
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use file_guard::{FileGuard, Lock};
|
|||||||
|
|
||||||
use super::{LevelLocker, LockError};
|
use super::{LevelLocker, LockError};
|
||||||
|
|
||||||
use std::{fs::File, io::Write, sync::Arc};
|
use std::{fs::File, io::Write, path::Path, sync::Arc};
|
||||||
|
|
||||||
pub struct AnvilLevelLocker {
|
pub struct AnvilLevelLocker {
|
||||||
_lock: Option<FileGuard<Arc<File>>>,
|
_lock: Option<FileGuard<Arc<File>>>,
|
||||||
@ -13,8 +13,8 @@ const SESSION_LOCK_FILE_NAME: &str = "session.lock";
|
|||||||
const SNOWMAN: &[u8] = "☃".as_bytes();
|
const SNOWMAN: &[u8] = "☃".as_bytes();
|
||||||
|
|
||||||
impl LevelLocker<Self> for AnvilLevelLocker {
|
impl LevelLocker<Self> for AnvilLevelLocker {
|
||||||
fn look(folder: &crate::level::LevelFolder) -> Result<Self, LockError> {
|
fn look(folder: &Path) -> Result<Self, LockError> {
|
||||||
let file_path = folder.root_folder.join(SESSION_LOCK_FILE_NAME);
|
let file_path = folder.join(SESSION_LOCK_FILE_NAME);
|
||||||
let mut file = File::options()
|
let mut file = File::options()
|
||||||
.create(true)
|
.create(true)
|
||||||
.truncate(false)
|
.truncate(false)
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
use thiserror::Error;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::level::LevelFolder;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub mod anvil;
|
pub mod anvil;
|
||||||
|
|
||||||
// Gets unlocked when dropped
|
// Gets unlocked when dropped
|
||||||
pub trait LevelLocker<T>: Send + Sync {
|
pub trait LevelLocker<T>: Send + Sync {
|
||||||
fn look(folder: &LevelFolder) -> Result<T, LockError>;
|
fn look(folder: &Path) -> Result<T, LockError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fs::OpenOptions,
|
fs::OpenOptions,
|
||||||
io::Read,
|
io::Read,
|
||||||
|
path::Path,
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
|
||||||
use flate2::{Compression, read::GzDecoder, write::GzEncoder};
|
use flate2::{Compression, read::GzDecoder, write::GzEncoder};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::world_info::{
|
||||||
level::LevelFolder,
|
MAXIMUM_SUPPORTED_WORLD_DATA_VERSION, MINIMUM_SUPPORTED_WORLD_DATA_VERSION,
|
||||||
world_info::{MAXIMUM_SUPPORTED_WORLD_DATA_VERSION, MINIMUM_SUPPORTED_WORLD_DATA_VERSION},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{LevelData, WorldInfoError, WorldInfoReader, WorldInfoWriter};
|
use super::{LevelData, WorldInfoError, WorldInfoReader, WorldInfoWriter};
|
||||||
@ -54,8 +54,8 @@ fn check_file_data_version(raw_nbt: &[u8]) -> Result<(), WorldInfoError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WorldInfoReader for AnvilLevelInfo {
|
impl WorldInfoReader for AnvilLevelInfo {
|
||||||
fn read_world_info(&self, level_folder: &LevelFolder) -> Result<LevelData, WorldInfoError> {
|
fn read_world_info(&self, level_folder: &Path) -> Result<LevelData, WorldInfoError> {
|
||||||
let path = level_folder.root_folder.join(LEVEL_DAT_FILE_NAME);
|
let path = level_folder.join(LEVEL_DAT_FILE_NAME);
|
||||||
|
|
||||||
let world_info_file = OpenOptions::new().read(true).open(path)?;
|
let world_info_file = OpenOptions::new().read(true).open(path)?;
|
||||||
let mut compression_reader = GzDecoder::new(world_info_file);
|
let mut compression_reader = GzDecoder::new(world_info_file);
|
||||||
@ -75,19 +75,21 @@ impl WorldInfoReader for AnvilLevelInfo {
|
|||||||
impl WorldInfoWriter for AnvilLevelInfo {
|
impl WorldInfoWriter for AnvilLevelInfo {
|
||||||
fn write_world_info(
|
fn write_world_info(
|
||||||
&self,
|
&self,
|
||||||
info: LevelData,
|
info: &LevelData,
|
||||||
level_folder: &LevelFolder,
|
level_folder: &Path,
|
||||||
) -> Result<(), WorldInfoError> {
|
) -> Result<(), WorldInfoError> {
|
||||||
let start = SystemTime::now();
|
let start = SystemTime::now();
|
||||||
let since_the_epoch = start
|
let since_the_epoch = start
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
.expect("Time went backwards");
|
.expect("Time went backwards");
|
||||||
let mut level_data = info;
|
let mut level_data = info.clone();
|
||||||
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.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
// open file
|
// open file
|
||||||
let path = level_folder.root_folder.join(LEVEL_DAT_FILE_NAME);
|
let path = level_folder.join(LEVEL_DAT_FILE_NAME);
|
||||||
let world_info_file = OpenOptions::new()
|
let world_info_file = OpenOptions::new()
|
||||||
.truncate(true)
|
.truncate(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
@ -122,7 +124,6 @@ mod test {
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
global_path,
|
global_path,
|
||||||
level::LevelFolder,
|
|
||||||
world_info::{DataPacks, LevelData, WorldGenSettings, WorldInfoError, WorldVersion},
|
world_info::{DataPacks, LevelData, WorldGenSettings, WorldInfoError, WorldVersion},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -136,16 +137,12 @@ mod test {
|
|||||||
data.world_gen_settings.seed = seed;
|
data.world_gen_settings.seed = seed;
|
||||||
|
|
||||||
let temp_dir = TempDir::new().unwrap();
|
let temp_dir = TempDir::new().unwrap();
|
||||||
let level_folder = LevelFolder {
|
|
||||||
root_folder: temp_dir.path().to_path_buf(),
|
|
||||||
region_folder: temp_dir.path().join("region"),
|
|
||||||
};
|
|
||||||
|
|
||||||
AnvilLevelInfo
|
AnvilLevelInfo
|
||||||
.write_world_info(data, &level_folder)
|
.write_world_info(&data, temp_dir.path())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let data = AnvilLevelInfo.read_world_info(&level_folder).unwrap();
|
let data = AnvilLevelInfo.read_world_info(temp_dir.path()).unwrap();
|
||||||
|
|
||||||
assert_eq!(data.world_gen_settings.seed, seed);
|
assert_eq!(data.world_gen_settings.seed, seed);
|
||||||
}
|
}
|
||||||
@ -219,15 +216,15 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn failed_deserialize_old_level_dat() {
|
fn failed_deserialize_old_level_dat() {
|
||||||
let temp_dir = TempDir::new().unwrap();
|
let temp_dir = TempDir::new().unwrap();
|
||||||
let level_folder = LevelFolder {
|
|
||||||
root_folder: temp_dir.path().to_path_buf(),
|
|
||||||
region_folder: temp_dir.path().join("region"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let test_dat = global_path!("../../assets/level_1_20.dat");
|
let test_dat = global_path!("../../assets/level_1_20.dat");
|
||||||
fs::copy(test_dat, level_folder.root_folder.join(LEVEL_DAT_FILE_NAME)).unwrap();
|
fs::copy(
|
||||||
|
test_dat,
|
||||||
|
temp_dir.path().to_path_buf().join(LEVEL_DAT_FILE_NAME),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let result = AnvilLevelInfo.read_world_info(&level_folder);
|
let result = AnvilLevelInfo.read_world_info(temp_dir.path());
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => panic!("This should fail!"),
|
Ok(_) => panic!("This should fail!"),
|
||||||
Err(WorldInfoError::UnsupportedVersion(_)) => {}
|
Err(WorldInfoError::UnsupportedVersion(_)) => {}
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use pumpkin_config::BASIC_CONFIG;
|
use pumpkin_config::BASIC_CONFIG;
|
||||||
use pumpkin_util::{Difficulty, serde_enum_as_integer};
|
use pumpkin_util::{Difficulty, serde_enum_as_integer};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{generation::Seed, level::LevelFolder};
|
use crate::generation::Seed;
|
||||||
|
|
||||||
pub mod anvil;
|
pub mod anvil;
|
||||||
|
|
||||||
@ -11,16 +13,13 @@ pub mod anvil;
|
|||||||
pub const MINIMUM_SUPPORTED_WORLD_DATA_VERSION: i32 = 4325; // 1.21.5
|
pub const MINIMUM_SUPPORTED_WORLD_DATA_VERSION: i32 = 4325; // 1.21.5
|
||||||
pub const MAXIMUM_SUPPORTED_WORLD_DATA_VERSION: i32 = 4325; // 1.21.5
|
pub const MAXIMUM_SUPPORTED_WORLD_DATA_VERSION: i32 = 4325; // 1.21.5
|
||||||
|
|
||||||
pub(crate) trait WorldInfoReader {
|
pub trait WorldInfoReader {
|
||||||
fn read_world_info(&self, level_folder: &LevelFolder) -> Result<LevelData, WorldInfoError>;
|
fn read_world_info(&self, level_folder: &Path) -> Result<LevelData, WorldInfoError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait WorldInfoWriter: Sync + Send {
|
pub trait WorldInfoWriter: Sync + Send {
|
||||||
fn write_world_info(
|
fn write_world_info(&self, info: &LevelData, level_folder: &Path)
|
||||||
&self,
|
-> Result<(), WorldInfoError>;
|
||||||
info: LevelData,
|
|
||||||
level_folder: &LevelFolder,
|
|
||||||
) -> Result<(), WorldInfoError>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||||
|
|||||||
@ -82,7 +82,7 @@ chrono = { version = "0.4", features = ["serde"] }
|
|||||||
|
|
||||||
# plugins
|
# plugins
|
||||||
libloading = "0.8"
|
libloading = "0.8"
|
||||||
bitflags = "2.9.0"
|
bitflags = "2.9.1"
|
||||||
rustc-hash = "2.1.1"
|
rustc-hash = "2.1.1"
|
||||||
|
|
||||||
# Task handling
|
# Task handling
|
||||||
|
|||||||
@ -16,7 +16,7 @@ use crate::block::pumpkin_block::PumpkinBlock;
|
|||||||
use crate::entity::player::Player;
|
use crate::entity::player::Player;
|
||||||
use crate::server::Server;
|
use crate::server::Server;
|
||||||
use crate::world::World;
|
use crate::world::World;
|
||||||
use crate::world::portal::NetherPortal;
|
use crate::world::portal::nether::NetherPortal;
|
||||||
|
|
||||||
use super::FireBlockBase;
|
use super::FireBlockBase;
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ impl PumpkinBlock for LilyPadBlock {
|
|||||||
_block: Block,
|
_block: Block,
|
||||||
_state: BlockState,
|
_state: BlockState,
|
||||||
) {
|
) {
|
||||||
// Proberbly not the best soultion, but works
|
// Proberbly not the best solution, but works
|
||||||
if entity
|
if entity
|
||||||
.get_entity()
|
.get_entity()
|
||||||
.entity_type
|
.entity_type
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::block::pumpkin_block::PumpkinBlock;
|
use crate::block::pumpkin_block::PumpkinBlock;
|
||||||
|
use crate::entity::EntityBase;
|
||||||
use crate::world::World;
|
use crate::world::World;
|
||||||
use crate::world::portal::NetherPortal;
|
use crate::world::portal::nether::NetherPortal;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use pumpkin_data::Block;
|
|
||||||
use pumpkin_data::block_properties::{Axis, BlockProperties, NetherPortalLikeProperties};
|
use pumpkin_data::block_properties::{Axis, BlockProperties, NetherPortalLikeProperties};
|
||||||
|
use pumpkin_data::{Block, BlockState};
|
||||||
use pumpkin_macros::pumpkin_block;
|
use pumpkin_macros::pumpkin_block;
|
||||||
use pumpkin_util::math::position::BlockPos;
|
use pumpkin_util::math::position::BlockPos;
|
||||||
use pumpkin_world::BlockStateId;
|
use pumpkin_world::BlockStateId;
|
||||||
@ -38,4 +41,14 @@ impl PumpkinBlock for NetherPortalBlock {
|
|||||||
}
|
}
|
||||||
Block::AIR.default_state_id
|
Block::AIR.default_state_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn on_entity_collision(
|
||||||
|
&self,
|
||||||
|
_world: &Arc<World>,
|
||||||
|
_entity: &dyn EntityBase,
|
||||||
|
_pos: BlockPos,
|
||||||
|
_block: Block,
|
||||||
|
_state: BlockState,
|
||||||
|
) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,12 +27,13 @@ pub trait LoadJSONConfiguration {
|
|||||||
let path = data_dir.join(Self::get_path());
|
let path = data_dir.join(Self::get_path());
|
||||||
|
|
||||||
let config = if path.exists() {
|
let config = if path.exists() {
|
||||||
let file_content = fs::read_to_string(&path)
|
let file_content = fs::read_to_string(&path).unwrap_or_else(|_| {
|
||||||
.unwrap_or_else(|_| panic!("Couldn't read configuration file at {path:?}"));
|
panic!("Couldn't read configuration file at {}", path.display())
|
||||||
|
});
|
||||||
|
|
||||||
serde_json::from_str(&file_content).unwrap_or_else(|err| {
|
serde_json::from_str(&file_content).unwrap_or_else(|err| {
|
||||||
panic!(
|
panic!(
|
||||||
"Couldn't parse data config at {path:?}. Reason: {err}. This is probably caused by a config update. Just delete the old data config and restart.",
|
"Couldn't parse data config at {}. Reason: {err}. This is probably caused by a config update. Just delete the old data config and restart.", path.display(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -40,7 +41,8 @@ pub trait LoadJSONConfiguration {
|
|||||||
|
|
||||||
if let Err(err) = fs::write(&path, serde_json::to_string_pretty(&content).unwrap()) {
|
if let Err(err) = fs::write(&path, serde_json::to_string_pretty(&content).unwrap()) {
|
||||||
log::error!(
|
log::error!(
|
||||||
"Couldn't write default data config to {path:?}. Reason: {err}. This is probably caused by a config update. Just delete the old data config and restart.",
|
"Couldn't write default data config to {}. Reason: {err}. This is probably caused by a config update. Just delete the old data config and restart.",
|
||||||
|
path.display(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,13 +74,19 @@ pub trait SaveJSONConfiguration: LoadJSONConfiguration {
|
|||||||
let content = match serde_json::to_string_pretty(self) {
|
let content = match serde_json::to_string_pretty(self) {
|
||||||
Ok(content) => content,
|
Ok(content) => content,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("Couldn't serialize operator data config to {path:?}. Reason: {err}",);
|
log::warn!(
|
||||||
|
"Couldn't serialize operator data config to {}. Reason: {err}",
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = std::fs::write(&path, content) {
|
if let Err(err) = std::fs::write(&path, content) {
|
||||||
log::warn!("Couldn't write operator config to {path:?}. Reason: {err}",);
|
log::warn!(
|
||||||
|
"Couldn't write operator config to {}. Reason: {err}",
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,21 +31,6 @@ impl ServerPlayerData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles a player joining the server.
|
|
||||||
///
|
|
||||||
/// This function loads player data and applies it to a newly joined player.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `player` - The player who joined.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// A Result indicating success or the error that occurred.
|
|
||||||
pub async fn handle_player_join(&self, player: &mut Player) -> Result<(), PlayerDataError> {
|
|
||||||
self.load_and_apply_data_to_player(player).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handles a player leaving the server.
|
/// Handles a player leaving the server.
|
||||||
///
|
///
|
||||||
/// This function saves player data when they disconnect.
|
/// This function saves player data when they disconnect.
|
||||||
@ -141,19 +126,14 @@ impl ServerPlayerData {
|
|||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// A Result indicating success or the error that occurred.
|
/// A Result indicating success or the error that occurred.
|
||||||
pub async fn load_and_apply_data_to_player(
|
pub fn load_data(&self, uuid: &uuid::Uuid) -> Result<Option<NbtCompound>, PlayerDataError> {
|
||||||
&self,
|
|
||||||
player: &mut Player,
|
|
||||||
) -> Result<(), PlayerDataError> {
|
|
||||||
let uuid = &player.gameprofile.id;
|
|
||||||
match self.storage.load_player_data(uuid) {
|
match self.storage.load_player_data(uuid) {
|
||||||
Ok((should_load, mut data)) => {
|
Ok((should_load, data)) => {
|
||||||
if !should_load {
|
if !should_load {
|
||||||
// No data to load, continue with default data
|
// No data to load, continue with default data
|
||||||
return Ok(());
|
return Ok(None);
|
||||||
}
|
}
|
||||||
player.read_nbt(&mut data).await;
|
Ok(Some(data))
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if self.storage.is_save_enabled() {
|
if self.storage.is_save_enabled() {
|
||||||
@ -164,7 +144,7 @@ impl ServerPlayerData {
|
|||||||
log::debug!("Not loading player data for {uuid} (saving disabled)");
|
log::debug!("Not loading player data for {uuid} (saving disabled)");
|
||||||
}
|
}
|
||||||
// Continue with default data even if there's an error
|
// Continue with default data even if there's an error
|
||||||
Ok(())
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,7 +32,7 @@ impl HungerManager {
|
|||||||
let level = self.level.load();
|
let level = self.level.load();
|
||||||
let exhaustion = self.exhaustion.load();
|
let exhaustion = self.exhaustion.load();
|
||||||
let health = player.living_entity.health.load();
|
let health = player.living_entity.health.load();
|
||||||
let difficulty = player.world().await.level.level_info.difficulty.clone();
|
let difficulty = player.world().await.level_info.difficulty.clone();
|
||||||
// Decrease hunger level on exhaustion
|
// Decrease hunger level on exhaustion
|
||||||
if level != 0 && exhaustion > 4.0 {
|
if level != 0 && exhaustion > 4.0 {
|
||||||
self.exhaustion.store(exhaustion - 4.0);
|
self.exhaustion.store(exhaustion - 4.0);
|
||||||
|
|||||||
@ -62,6 +62,7 @@ pub async fn from_type(
|
|||||||
goals: Mutex::new(vec![]),
|
goals: Mutex::new(vec![]),
|
||||||
navigator: Mutex::new(Navigator::default()),
|
navigator: Mutex::new(Navigator::default()),
|
||||||
};
|
};
|
||||||
|
#[expect(clippy::single_match)]
|
||||||
match entity_type {
|
match entity_type {
|
||||||
EntityType::ZOMBIE => Zombie::make(&mob).await,
|
EntityType::ZOMBIE => Zombie::make(&mob).await,
|
||||||
// TODO
|
// TODO
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
use crate::server::Server;
|
use crate::{
|
||||||
|
server::Server,
|
||||||
|
world::portal::{Portal, PortalManager},
|
||||||
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use bytes::BufMut;
|
use bytes::BufMut;
|
||||||
use core::f32;
|
use core::f32;
|
||||||
@ -32,11 +35,11 @@ use serde::Serialize;
|
|||||||
use std::sync::{
|
use std::sync::{
|
||||||
Arc,
|
Arc,
|
||||||
atomic::{
|
atomic::{
|
||||||
AtomicBool, AtomicI32,
|
AtomicBool, AtomicI32, AtomicU32,
|
||||||
Ordering::{Relaxed, SeqCst},
|
Ordering::{self, Relaxed, SeqCst},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::{Mutex, RwLock};
|
||||||
|
|
||||||
use crate::world::World;
|
use crate::world::World;
|
||||||
|
|
||||||
@ -135,6 +138,11 @@ pub struct Entity {
|
|||||||
pub invulnerable: AtomicBool,
|
pub invulnerable: AtomicBool,
|
||||||
/// List of damage types this entity is immune to
|
/// List of damage types this entity is immune to
|
||||||
pub damage_immunities: Vec<DamageType>,
|
pub damage_immunities: Vec<DamageType>,
|
||||||
|
|
||||||
|
pub portal_cooldown: AtomicU32,
|
||||||
|
|
||||||
|
// :c
|
||||||
|
pub portal_manager: Mutex<Option<Mutex<PortalManager>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity {
|
impl Entity {
|
||||||
@ -181,6 +189,8 @@ impl Entity {
|
|||||||
bounding_box_size: AtomicCell::new(bounding_box_size),
|
bounding_box_size: AtomicCell::new(bounding_box_size),
|
||||||
invulnerable: AtomicBool::new(invulnerable),
|
invulnerable: AtomicBool::new(invulnerable),
|
||||||
damage_immunities: Vec::new(),
|
damage_immunities: Vec::new(),
|
||||||
|
portal_cooldown: AtomicU32::new(0),
|
||||||
|
portal_manager: Mutex::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,6 +306,53 @@ impl Entity {
|
|||||||
self.set_rotation(yaw, pitch);
|
self.set_rotation(yaw, pitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_portal_cooldown(&self) -> u32 {
|
||||||
|
if self.entity_type == EntityType::PLAYER {
|
||||||
|
10
|
||||||
|
} else {
|
||||||
|
300
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn tick_portal(&self) {
|
||||||
|
if self.portal_cooldown.load(Ordering::Relaxed) > 0 {
|
||||||
|
self.portal_cooldown.fetch_sub(1, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
let mut manager_guard = self.portal_manager.lock().await;
|
||||||
|
// I know this is ugly, but a quick fix because i can't modify the thing while using it
|
||||||
|
let mut should_remove = false;
|
||||||
|
if let Some(pmanager_mutex) = manager_guard.as_ref() {
|
||||||
|
let mut portal_manager = pmanager_mutex.lock().await;
|
||||||
|
if portal_manager.tick() {
|
||||||
|
// reset cooldown
|
||||||
|
self.portal_cooldown
|
||||||
|
.store(self.default_portal_cooldown(), Ordering::Relaxed);
|
||||||
|
//let world = portal_manager.portal.get_world(self);
|
||||||
|
} else {
|
||||||
|
should_remove = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if should_remove {
|
||||||
|
*manager_guard = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn try_use_portal(&self, portal: Arc<dyn Portal>, pos: BlockPos) {
|
||||||
|
if self.portal_cooldown.load(Ordering::Relaxed) > 0 {
|
||||||
|
self.portal_cooldown
|
||||||
|
.store(self.default_portal_cooldown(), Ordering::Relaxed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut manager = self.portal_manager.lock().await;
|
||||||
|
if manager.is_none() {
|
||||||
|
*manager = Some(Mutex::new(PortalManager::new(portal, pos)));
|
||||||
|
} else if let Some(manager) = manager.as_ref() {
|
||||||
|
let mut manager = manager.lock().await;
|
||||||
|
manager.pos = pos;
|
||||||
|
manager.in_portal = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the `Entity` yaw & pitch rotation
|
/// Sets the `Entity` yaw & pitch rotation
|
||||||
pub fn set_rotation(&self, yaw: f32, pitch: f32) {
|
pub fn set_rotation(&self, yaw: f32, pitch: f32) {
|
||||||
// TODO
|
// TODO
|
||||||
@ -527,6 +584,7 @@ impl EntityBase for Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn tick(&self, _caller: &dyn EntityBase, _: &Server) {
|
async fn tick(&self, _caller: &dyn EntityBase, _: &Server) {
|
||||||
|
self.tick_portal().await;
|
||||||
// TODO: Tick
|
// TODO: Tick
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -886,7 +886,7 @@ impl Player {
|
|||||||
pitch: Option<f32>,
|
pitch: Option<f32>,
|
||||||
) {
|
) {
|
||||||
let current_world = self.living_entity.entity.world.read().await.clone();
|
let current_world = self.living_entity.entity.world.read().await.clone();
|
||||||
let info = &new_world.level.level_info;
|
let info = &new_world.level_info;
|
||||||
let position = if let Some(pos) = position {
|
let position = if let Some(pos) = position {
|
||||||
pos
|
pos
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ use crate::block::registry::BlockRegistry;
|
|||||||
use crate::command::commands::default_dispatcher;
|
use crate::command::commands::default_dispatcher;
|
||||||
use crate::command::commands::defaultgamemode::DefaultGamemode;
|
use crate::command::commands::defaultgamemode::DefaultGamemode;
|
||||||
use crate::data::player_server_data::ServerPlayerData;
|
use crate::data::player_server_data::ServerPlayerData;
|
||||||
|
use crate::entity::NBTStorage;
|
||||||
use crate::item::registry::ItemRegistry;
|
use crate::item::registry::ItemRegistry;
|
||||||
use crate::net::EncryptionError;
|
use crate::net::EncryptionError;
|
||||||
use crate::plugin::player::player_login::PlayerLoginEvent;
|
use crate::plugin::player::player_login::PlayerLoginEvent;
|
||||||
@ -22,8 +23,15 @@ use pumpkin_registry::{DimensionType, Registry};
|
|||||||
use pumpkin_util::math::vector2::Vector2;
|
use pumpkin_util::math::vector2::Vector2;
|
||||||
use pumpkin_util::text::TextComponent;
|
use pumpkin_util::text::TextComponent;
|
||||||
use pumpkin_world::dimension::Dimension;
|
use pumpkin_world::dimension::Dimension;
|
||||||
|
use pumpkin_world::lock::LevelLocker;
|
||||||
|
use pumpkin_world::lock::anvil::AnvilLevelLocker;
|
||||||
|
use pumpkin_world::world_info::anvil::{
|
||||||
|
AnvilLevelInfo, LEVEL_DAT_BACKUP_FILE_NAME, LEVEL_DAT_FILE_NAME,
|
||||||
|
};
|
||||||
|
use pumpkin_world::world_info::{LevelData, WorldInfoError, WorldInfoReader, WorldInfoWriter};
|
||||||
use rand::prelude::SliceRandom;
|
use rand::prelude::SliceRandom;
|
||||||
use rsa::RsaPublicKey;
|
use rsa::RsaPublicKey;
|
||||||
|
use std::fs;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU32};
|
use std::sync::atomic::{AtomicBool, AtomicU32};
|
||||||
use std::{
|
use std::{
|
||||||
@ -57,7 +65,7 @@ pub struct Server {
|
|||||||
pub item_registry: Arc<ItemRegistry>,
|
pub item_registry: Arc<ItemRegistry>,
|
||||||
/// Manages multiple worlds within the server.
|
/// Manages multiple worlds within the server.
|
||||||
pub worlds: RwLock<Vec<Arc<World>>>,
|
pub worlds: RwLock<Vec<Arc<World>>>,
|
||||||
// All the dimensions that exist on the server.
|
/// All the dimensions that exist on the server.
|
||||||
pub dimensions: Vec<DimensionType>,
|
pub dimensions: Vec<DimensionType>,
|
||||||
/// Caches game registries for efficient access.
|
/// Caches game registries for efficient access.
|
||||||
pub cached_registry: Vec<Registry>,
|
pub cached_registry: Vec<Registry>,
|
||||||
@ -77,6 +85,13 @@ pub struct Server {
|
|||||||
// Whether the server whitelist is on or off
|
// Whether the server whitelist is on or off
|
||||||
pub white_list: AtomicBool,
|
pub white_list: AtomicBool,
|
||||||
tasks: TaskTracker,
|
tasks: TaskTracker,
|
||||||
|
|
||||||
|
// world stuff which maybe should be put into a struct
|
||||||
|
pub level_info: Arc<LevelData>,
|
||||||
|
world_info_writer: Arc<dyn WorldInfoWriter>,
|
||||||
|
// Gets unlocked when dropped
|
||||||
|
// TODO: Make this a trait
|
||||||
|
_locker: Arc<AnvilLevelLocker>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
@ -101,18 +116,58 @@ impl Server {
|
|||||||
|
|
||||||
let block_registry = super::block::default_registry();
|
let block_registry = super::block::default_registry();
|
||||||
|
|
||||||
let world = World::load(
|
let level_info = AnvilLevelInfo.read_world_info(&world_path);
|
||||||
Dimension::Overworld.into_level(world_path.clone()),
|
if let Err(error) = &level_info {
|
||||||
|
match error {
|
||||||
|
// If it doesn't exist, just make a new one
|
||||||
|
WorldInfoError::InfoNotFound => (),
|
||||||
|
WorldInfoError::UnsupportedVersion(version) => {
|
||||||
|
log::error!("Failed to load world info!, {version}");
|
||||||
|
log::error!("{error}");
|
||||||
|
panic!("Unsupported world data! See the logs for more info.");
|
||||||
|
}
|
||||||
|
e => {
|
||||||
|
panic!("World Error {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let dat_path = world_path.join(LEVEL_DAT_FILE_NAME);
|
||||||
|
if dat_path.exists() {
|
||||||
|
let backup_path = world_path.join(LEVEL_DAT_BACKUP_FILE_NAME);
|
||||||
|
fs::copy(dat_path, backup_path).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let level_info = Arc::new(level_info.unwrap_or_default()); // TODO: Improve error handling
|
||||||
|
let overworld = World::load(
|
||||||
|
Dimension::Overworld.into_level(world_path.clone(), level_info.world_gen_settings.seed),
|
||||||
|
level_info.clone(),
|
||||||
DimensionType::Overworld,
|
DimensionType::Overworld,
|
||||||
block_registry.clone(),
|
block_registry.clone(),
|
||||||
);
|
);
|
||||||
|
let nether = World::load(
|
||||||
|
Dimension::Nether.into_level(world_path.clone(), level_info.world_gen_settings.seed),
|
||||||
|
level_info.clone(),
|
||||||
|
DimensionType::TheNether,
|
||||||
|
block_registry.clone(),
|
||||||
|
);
|
||||||
|
let end = World::load(
|
||||||
|
Dimension::End.into_level(world_path.clone(), level_info.world_gen_settings.seed),
|
||||||
|
level_info.clone(),
|
||||||
|
DimensionType::TheEnd,
|
||||||
|
block_registry.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// if we fail to lock, lets crash ???. maybe not the best solution when we have a large server with many worlds and one is locked.
|
||||||
|
// So TODO
|
||||||
|
let locker = AnvilLevelLocker::look(&world_path).expect("Failed to lock level");
|
||||||
|
|
||||||
let world_name = world_path.to_str().unwrap();
|
let world_name = world_path.to_str().unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
cached_registry: Registry::get_synced(),
|
cached_registry: Registry::get_synced(),
|
||||||
container_id: 0.into(),
|
container_id: 0.into(),
|
||||||
worlds: RwLock::new(vec![Arc::new(world)]),
|
worlds: RwLock::new(vec![Arc::new(overworld), Arc::new(nether), Arc::new(end)]),
|
||||||
dimensions: vec![
|
dimensions: vec![
|
||||||
DimensionType::Overworld,
|
DimensionType::Overworld,
|
||||||
DimensionType::OverworldCaves,
|
DimensionType::OverworldCaves,
|
||||||
@ -137,6 +192,9 @@ impl Server {
|
|||||||
white_list: AtomicBool::new(BASIC_CONFIG.white_list),
|
white_list: AtomicBool::new(BASIC_CONFIG.white_list),
|
||||||
tasks: TaskTracker::new(),
|
tasks: TaskTracker::new(),
|
||||||
mojang_public_keys: Mutex::new(Vec::new()),
|
mojang_public_keys: Mutex::new(Vec::new()),
|
||||||
|
world_info_writer: Arc::new(AnvilLevelInfo),
|
||||||
|
level_info,
|
||||||
|
_locker: Arc::new(locker),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,6 +220,18 @@ impl Server {
|
|||||||
self.tasks.spawn(task)
|
self.tasks.spawn(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_world_from_dimension(&self, dimension: DimensionType) -> Arc<World> {
|
||||||
|
// TODO: this is really bad
|
||||||
|
let world_guard = self.worlds.read().await;
|
||||||
|
let world = match dimension {
|
||||||
|
DimensionType::Overworld => world_guard.first(),
|
||||||
|
DimensionType::OverworldCaves => todo!(),
|
||||||
|
DimensionType::TheEnd => world_guard.get(2),
|
||||||
|
DimensionType::TheNether => world_guard.get(1),
|
||||||
|
};
|
||||||
|
world.cloned().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::if_then_some_else_none)]
|
#[allow(clippy::if_then_some_else_none)]
|
||||||
/// Adds a new player to the server.
|
/// Adds a new player to the server.
|
||||||
///
|
///
|
||||||
@ -190,19 +260,43 @@ impl Server {
|
|||||||
/// You still have to spawn the `Player` in a `World` to let them join and make them visible.
|
/// You still have to spawn the `Player` in a `World` to let them join and make them visible.
|
||||||
pub async fn add_player(&self, client: Client) -> Option<(Arc<Player>, Arc<World>)> {
|
pub async fn add_player(&self, client: Client) -> Option<(Arc<Player>, Arc<World>)> {
|
||||||
let gamemode = self.defaultgamemode.lock().await.gamemode;
|
let gamemode = self.defaultgamemode.lock().await.gamemode;
|
||||||
// Basically the default world
|
let uuid = client.gameprofile.lock().await.as_ref().unwrap().id;
|
||||||
// TODO: select default from config
|
|
||||||
let world = &self.worlds.read().await[0];
|
let (world, nbt) = if let Ok(Some(data)) = self.player_data_storage.load_data(&uuid) {
|
||||||
|
if let Some(dimension_key) = data.get_string("Dimension") {
|
||||||
|
if let Some(dimension) = DimensionType::from_name(dimension_key) {
|
||||||
|
let world = self.get_world_from_dimension(dimension).await;
|
||||||
|
(world, Some(data))
|
||||||
|
} else {
|
||||||
|
log::warn!("Invalid dimension key in player data: {dimension_key}");
|
||||||
|
let default_world_guard = self.worlds.read().await;
|
||||||
|
let default_world = default_world_guard
|
||||||
|
.first()
|
||||||
|
.expect("Default world should exist");
|
||||||
|
(default_world.clone(), Some(data))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Player data exists but doesn't have a "Dimension" key.
|
||||||
|
log::warn!("Player data missing 'Dimension' key, defaulting to Overworld.");
|
||||||
|
let default_world_guard = self.worlds.read().await;
|
||||||
|
let default_world = default_world_guard
|
||||||
|
.first()
|
||||||
|
.expect("Default world should exist");
|
||||||
|
(default_world.clone(), Some(data))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No player data found or an error occurred, default to the Overworld.
|
||||||
|
let default_world_guard = self.worlds.read().await;
|
||||||
|
let default_world = default_world_guard
|
||||||
|
.first()
|
||||||
|
.expect("Default world should exist");
|
||||||
|
(default_world.clone(), None)
|
||||||
|
};
|
||||||
|
|
||||||
let mut player = Player::new(client, world.clone(), gamemode).await;
|
let mut player = Player::new(client, world.clone(), gamemode).await;
|
||||||
|
|
||||||
// Load player data
|
if let Some(mut nbt_data) = nbt {
|
||||||
if let Err(e) = self
|
player.read_nbt(&mut nbt_data).await;
|
||||||
.player_data_storage
|
|
||||||
.handle_player_join(&mut player)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
log::error!("Unexpected error loading player data: {e}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap in Arc after data is loaded
|
// Wrap in Arc after data is loaded
|
||||||
@ -251,6 +345,13 @@ impl Server {
|
|||||||
for world in self.worlds.read().await.iter() {
|
for world in self.worlds.read().await.iter() {
|
||||||
world.shutdown().await;
|
world.shutdown().await;
|
||||||
}
|
}
|
||||||
|
// then lets save the world info
|
||||||
|
if let Err(err) = self
|
||||||
|
.world_info_writer
|
||||||
|
.write_world_info(&self.level_info, &BASIC_CONFIG.get_world_path())
|
||||||
|
{
|
||||||
|
log::error!("Failed to save level.dat: {err}");
|
||||||
|
}
|
||||||
log::info!("Completed worlds");
|
log::info!("Completed worlds");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -67,7 +67,6 @@ use pumpkin_registry::DimensionType;
|
|||||||
use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos, vector3::Vector3};
|
use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos, vector3::Vector3};
|
||||||
use pumpkin_util::math::{position::chunk_section_from_pos, vector2::Vector2};
|
use pumpkin_util::math::{position::chunk_section_from_pos, vector2::Vector2};
|
||||||
use pumpkin_util::text::{TextComponent, color::NamedColor};
|
use pumpkin_util::text::{TextComponent, color::NamedColor};
|
||||||
use pumpkin_world::world::BlockFlags;
|
|
||||||
use pumpkin_world::{
|
use pumpkin_world::{
|
||||||
BlockStateId, GENERATION_SETTINGS, GeneratorSetting, biome, block::entities::BlockEntity,
|
BlockStateId, GENERATION_SETTINGS, GeneratorSetting, biome, block::entities::BlockEntity,
|
||||||
level::SyncChunk,
|
level::SyncChunk,
|
||||||
@ -78,6 +77,7 @@ use pumpkin_world::{
|
|||||||
entity::entity_data_flags::{DATA_PLAYER_MAIN_HAND, DATA_PLAYER_MODE_CUSTOMISATION},
|
entity::entity_data_flags::{DATA_PLAYER_MAIN_HAND, DATA_PLAYER_MODE_CUSTOMISATION},
|
||||||
world::GetBlockError,
|
world::GetBlockError,
|
||||||
};
|
};
|
||||||
|
use pumpkin_world::{world::BlockFlags, world_info::LevelData};
|
||||||
use rand::{Rng, thread_rng};
|
use rand::{Rng, thread_rng};
|
||||||
use scoreboard::Scoreboard;
|
use scoreboard::Scoreboard;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@ -122,6 +122,7 @@ impl PumpkinError for GetBlockError {
|
|||||||
pub struct World {
|
pub struct World {
|
||||||
/// The underlying level, responsible for chunk management and terrain generation.
|
/// The underlying level, responsible for chunk management and terrain generation.
|
||||||
pub level: Arc<Level>,
|
pub level: Arc<Level>,
|
||||||
|
pub level_info: Arc<LevelData>,
|
||||||
/// A map of active players within the world, keyed by their unique UUID.
|
/// A map of active players within the world, keyed by their unique UUID.
|
||||||
pub players: Arc<RwLock<HashMap<uuid::Uuid, Arc<Player>>>>,
|
pub players: Arc<RwLock<HashMap<uuid::Uuid, Arc<Player>>>>,
|
||||||
/// A map of active entities within the world, keyed by their unique UUID.
|
/// A map of active entities within the world, keyed by their unique UUID.
|
||||||
@ -143,13 +144,13 @@ pub struct World {
|
|||||||
synced_block_event_queue: Mutex<Vec<BlockEvent>>,
|
synced_block_event_queue: Mutex<Vec<BlockEvent>>,
|
||||||
/// A map of unsent block changes, keyed by block position.
|
/// A map of unsent block changes, keyed by block position.
|
||||||
unsent_block_changes: Mutex<HashMap<BlockPos, u16>>,
|
unsent_block_changes: Mutex<HashMap<BlockPos, u16>>,
|
||||||
// TODO: entities
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl World {
|
impl World {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn load(
|
pub fn load(
|
||||||
level: Level,
|
level: Level,
|
||||||
|
level_info: Arc<LevelData>,
|
||||||
dimension_type: DimensionType,
|
dimension_type: DimensionType,
|
||||||
block_registry: Arc<BlockRegistry>,
|
block_registry: Arc<BlockRegistry>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -159,6 +160,7 @@ impl World {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
Self {
|
Self {
|
||||||
level: Arc::new(level),
|
level: Arc::new(level),
|
||||||
|
level_info,
|
||||||
players: Arc::new(RwLock::new(HashMap::new())),
|
players: Arc::new(RwLock::new(HashMap::new())),
|
||||||
entities: Arc::new(RwLock::new(HashMap::new())),
|
entities: Arc::new(RwLock::new(HashMap::new())),
|
||||||
scoreboard: Mutex::new(Scoreboard::new()),
|
scoreboard: Mutex::new(Scoreboard::new()),
|
||||||
@ -559,7 +561,7 @@ impl World {
|
|||||||
|
|
||||||
(position, yaw, pitch)
|
(position, yaw, pitch)
|
||||||
} else {
|
} else {
|
||||||
let info = &self.level.level_info;
|
let info = &self.level_info;
|
||||||
let position = Vector3::new(
|
let position = Vector3::new(
|
||||||
f64::from(info.spawn_x),
|
f64::from(info.spawn_x),
|
||||||
f64::from(info.spawn_y) + 1.0,
|
f64::from(info.spawn_y) + 1.0,
|
||||||
@ -890,7 +892,7 @@ impl World {
|
|||||||
player.send_permission_lvl_update().await;
|
player.send_permission_lvl_update().await;
|
||||||
|
|
||||||
// Teleport
|
// Teleport
|
||||||
let info = &self.level.level_info;
|
let info = &self.level_info;
|
||||||
let mut position = Vector3::new(
|
let mut position = Vector3::new(
|
||||||
f64::from(info.spawn_x),
|
f64::from(info.spawn_x),
|
||||||
f64::from(info.spawn_y),
|
f64::from(info.spawn_y),
|
||||||
|
|||||||
45
pumpkin/src/world/portal/mod.rs
Normal file
45
pumpkin/src/world/portal/mod.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use pumpkin_util::math::position::BlockPos;
|
||||||
|
|
||||||
|
use crate::entity::Entity;
|
||||||
|
|
||||||
|
use super::World;
|
||||||
|
|
||||||
|
pub mod nether;
|
||||||
|
|
||||||
|
pub trait Portal: Send + Sync {
|
||||||
|
fn get_delay(&self) -> u32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
fn get_world(&self, entity: &Entity) -> Arc<World>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PortalManager {
|
||||||
|
pub portal: Arc<dyn Portal>,
|
||||||
|
pub pos: BlockPos,
|
||||||
|
ticks_in_portal: u32,
|
||||||
|
pub in_portal: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PortalManager {
|
||||||
|
pub fn new(portal: Arc<dyn Portal>, pos: BlockPos) -> Self {
|
||||||
|
Self {
|
||||||
|
portal,
|
||||||
|
pos,
|
||||||
|
ticks_in_portal: 0,
|
||||||
|
in_portal: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(&mut self) -> bool {
|
||||||
|
if self.in_portal {
|
||||||
|
self.in_portal = false;
|
||||||
|
self.ticks_in_portal += 1;
|
||||||
|
self.ticks_in_portal >= self.portal.get_delay()
|
||||||
|
} else {
|
||||||
|
self.ticks_in_portal -= 4;
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,9 +6,9 @@ use pumpkin_data::{
|
|||||||
tag::Tagable,
|
tag::Tagable,
|
||||||
};
|
};
|
||||||
use pumpkin_util::math::position::BlockPos;
|
use pumpkin_util::math::position::BlockPos;
|
||||||
use pumpkin_world::block::BlockDirection;
|
use pumpkin_world::{block::BlockDirection, world::BlockFlags};
|
||||||
|
|
||||||
use super::{BlockFlags, World};
|
use crate::world::World;
|
||||||
|
|
||||||
pub struct NetherPortal {
|
pub struct NetherPortal {
|
||||||
axis: HorizontalAxis,
|
axis: HorizontalAxis,
|
||||||
Loading…
Reference in New Issue
Block a user