mirror of
https://github.com/Cactus-minecraft-server/World.git
synced 2025-12-07 10:40:37 +00:00
refactor level.rs
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "worldgen"
|
||||
name = "world"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
@@ -9,5 +9,5 @@ rand = "0.9.2"
|
||||
rand_chacha = "0.9.0"
|
||||
nbt = { git = "https://github.com/Cactus-minecraft-server/nbt.git" }
|
||||
[lib]
|
||||
name = "worldgen"
|
||||
name = "world"
|
||||
path = "src/lib.rs"
|
||||
|
||||
428
src/level.rs
428
src/level.rs
@@ -1,79 +1,385 @@
|
||||
use nbt::{Tag, write_nbt};
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
|
||||
use nbt::{Tag, write_nbt};
|
||||
pub struct LevelDat {
|
||||
pub custom_boss_events: CustomBossEvents,
|
||||
pub data_packs: DataPacks,
|
||||
pub dragon_fight: DragonFight,
|
||||
pub game_rules: GameRules,
|
||||
pub version: VersionInfo,
|
||||
pub world_gen_settings: WorldGenSettings,
|
||||
pub scheduled_events: ScheduledEvents,
|
||||
pub server_brands: ServerBrands,
|
||||
|
||||
pub fn create_nbt(
|
||||
seed: i64,
|
||||
is_hardcore: bool,
|
||||
structures: bool,
|
||||
raining: bool,
|
||||
thundering: bool,
|
||||
game_type: i32,
|
||||
generator_version: i32,
|
||||
raintime: i32,
|
||||
spawnx: i32,
|
||||
spawny: i32,
|
||||
spawnz: i32,
|
||||
thundertime: i32,
|
||||
version: i32,
|
||||
last_played: i64,
|
||||
disk_size: i64,
|
||||
time: i64,
|
||||
generator_name: String,
|
||||
level_name: String,
|
||||
path: String,
|
||||
) -> std::io::Result<()> {
|
||||
pub allow_commands: bool,
|
||||
|
||||
pub border_center_x: f64,
|
||||
pub border_center_z: f64,
|
||||
pub border_damage_per_block: f64,
|
||||
pub border_safe_zone: f64,
|
||||
pub border_size: f64,
|
||||
pub border_size_lerp_target: f64,
|
||||
pub border_size_lerp_time: i64,
|
||||
pub border_warning_blocks: i32,
|
||||
pub border_warning_time: i32,
|
||||
|
||||
pub clear_weather_time: i32,
|
||||
pub data_version: i32,
|
||||
pub day_time: i64,
|
||||
pub difficulty: i8,
|
||||
pub difficulty_locked: bool,
|
||||
pub game_type: i32,
|
||||
pub hardcore: bool,
|
||||
pub initialized: bool,
|
||||
pub last_played: i64,
|
||||
pub level_name: String,
|
||||
pub raining: bool,
|
||||
pub rain_time: i32,
|
||||
pub spawn_angle: f32,
|
||||
pub spawn_x: i32,
|
||||
pub spawn_y: i32,
|
||||
pub spawn_z: i32,
|
||||
pub thundering: bool,
|
||||
pub thunder_time: i32,
|
||||
pub time: i64,
|
||||
pub version_id: i32,
|
||||
pub wandering_trader_spawn_chance: i32,
|
||||
pub wandering_trader_spawn_delay: i32,
|
||||
pub was_modded: bool,
|
||||
}
|
||||
|
||||
pub struct CustomBossEvents {}
|
||||
|
||||
pub struct DataPacks {
|
||||
pub disabled: Vec<String>,
|
||||
pub enabled: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct DragonFight {
|
||||
pub gateways: Vec<i32>,
|
||||
pub dragon_killed: bool,
|
||||
pub needs_state_scanning: bool,
|
||||
pub previously_killed: bool,
|
||||
}
|
||||
|
||||
pub type GameRules = HashMap<String, String>;
|
||||
|
||||
pub struct VersionInfo {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub series: String,
|
||||
pub snapshot: i8,
|
||||
}
|
||||
|
||||
pub enum Dimension {
|
||||
Overworld,
|
||||
End,
|
||||
Nether,
|
||||
}
|
||||
|
||||
pub struct WorldGenSettings {
|
||||
pub dimensions: HashMap<String, Dimension>,
|
||||
pub bonus_chest: bool,
|
||||
pub generate_features: bool,
|
||||
pub seed: i64,
|
||||
}
|
||||
|
||||
pub struct ScheduledEvents {}
|
||||
|
||||
pub type ServerBrands = Vec<String>;
|
||||
fn dim_to_str(d: &Dimension) -> &'static str {
|
||||
match d {
|
||||
Dimension::Overworld => "overworld",
|
||||
Dimension::End => "the_end",
|
||||
Dimension::Nether => "the_nether",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_nbt(level: &LevelDat, path: &str) -> std::io::Result<()> {
|
||||
let mut root = Tag::new_compound("Data");
|
||||
root.insert("RandomSeed".to_string(), Tag::new_long("RandomSeed", seed));
|
||||
|
||||
// --- primitives (root) ---
|
||||
root.insert(
|
||||
"allowCommands".to_string(),
|
||||
Tag::new_byte("allowCommands", i8::from(level.allow_commands)),
|
||||
);
|
||||
|
||||
root.insert(
|
||||
"BorderCenterX".to_string(),
|
||||
Tag::new_double("BorderCenterX", level.border_center_x),
|
||||
);
|
||||
root.insert(
|
||||
"BorderCenterZ".to_string(),
|
||||
Tag::new_double("BorderCenterZ", level.border_center_z),
|
||||
);
|
||||
root.insert(
|
||||
"BorderDamagePerBlock".to_string(),
|
||||
Tag::new_double("BorderDamagePerBlock", level.border_damage_per_block),
|
||||
);
|
||||
root.insert(
|
||||
"BorderSafeZone".to_string(),
|
||||
Tag::new_double("BorderSafeZone", level.border_safe_zone),
|
||||
);
|
||||
root.insert(
|
||||
"BorderSize".to_string(),
|
||||
Tag::new_double("BorderSize", level.border_size),
|
||||
);
|
||||
root.insert(
|
||||
"BorderSizeLerpTarget".to_string(),
|
||||
Tag::new_double("BorderSizeLerpTarget", level.border_size_lerp_target),
|
||||
);
|
||||
root.insert(
|
||||
"BorderSizeLerpTime".to_string(),
|
||||
Tag::new_long("BorderSizeLerpTime", level.border_size_lerp_time),
|
||||
);
|
||||
root.insert(
|
||||
"BorderWarningBlocks".to_string(),
|
||||
Tag::new_int("BorderWarningBlocks", level.border_warning_blocks),
|
||||
);
|
||||
root.insert(
|
||||
"BorderWarningTime".to_string(),
|
||||
Tag::new_int("BorderWarningTime", level.border_warning_time),
|
||||
);
|
||||
|
||||
root.insert(
|
||||
"clearWeatherTime".to_string(),
|
||||
Tag::new_int("clearWeatherTime", level.clear_weather_time),
|
||||
);
|
||||
root.insert(
|
||||
"DataVersion".to_string(),
|
||||
Tag::new_int("DataVersion", level.data_version),
|
||||
);
|
||||
root.insert(
|
||||
"DayTime".to_string(),
|
||||
Tag::new_long("DayTime", level.day_time),
|
||||
);
|
||||
root.insert(
|
||||
"Difficulty".to_string(),
|
||||
Tag::new_byte("Difficulty", level.difficulty),
|
||||
);
|
||||
root.insert(
|
||||
"DifficultyLocked".to_string(),
|
||||
Tag::new_byte("DifficultyLocked", i8::from(level.difficulty_locked)),
|
||||
);
|
||||
root.insert(
|
||||
"GameType".to_string(),
|
||||
Tag::new_int("GameType", level.game_type),
|
||||
);
|
||||
root.insert(
|
||||
"hardcore".to_string(),
|
||||
Tag::new_byte("hardcore", i8::from(is_hardcore)),
|
||||
Tag::new_byte("hardcore", i8::from(level.hardcore)),
|
||||
);
|
||||
root.insert(
|
||||
"MapFeatures".to_string(),
|
||||
Tag::new_byte("MapFeatures", i8::from(structures)),
|
||||
"initialized".to_string(),
|
||||
Tag::new_byte("initialized", i8::from(level.initialized)),
|
||||
);
|
||||
root.insert(
|
||||
"raining".to_string(),
|
||||
Tag::new_byte("raining", i8::from(raining)),
|
||||
);
|
||||
root.insert(
|
||||
"thundering".to_string(),
|
||||
Tag::new_byte("thundering", i8::from(thundering)),
|
||||
);
|
||||
root.insert("GameType".to_string(), Tag::new_int("GameType", game_type));
|
||||
root.insert(
|
||||
"GeneratorVersion".to_string(),
|
||||
Tag::new_int("GeneratorVersion", generator_version),
|
||||
);
|
||||
root.insert("RainTime".to_string(), Tag::new_int("RainTime", raintime));
|
||||
root.insert("SpawnX".to_string(), Tag::new_int("SpawnX", spawnx));
|
||||
root.insert("SpawnY".to_string(), Tag::new_int("SpawnY", spawny));
|
||||
root.insert("SpawnZ".to_string(), Tag::new_int("SpawnZ", spawnz));
|
||||
root.insert(
|
||||
"ThunderTime".to_string(),
|
||||
Tag::new_int("ThunderTime", thundertime),
|
||||
);
|
||||
root.insert("Version".to_string(), Tag::new_int("Version", version));
|
||||
root.insert(
|
||||
"LastPlayed".to_string(),
|
||||
Tag::new_long("LastPlayed", last_played),
|
||||
); // Not sure if I should keep LastPlayed field because of the fact that this isn't for a
|
||||
// client world but for a server world
|
||||
root.insert(
|
||||
"SizeOnDisk".to_string(),
|
||||
Tag::new_long("SizeOnDisk", disk_size),
|
||||
);
|
||||
root.insert("Time".to_string(), Tag::new_long("Time", time));
|
||||
root.insert(
|
||||
"GeneratorName".to_string(),
|
||||
Tag::new_string("GeneratorName", generator_name),
|
||||
Tag::new_long("LastPlayed", level.last_played),
|
||||
);
|
||||
root.insert(
|
||||
"LevelName".to_string(),
|
||||
Tag::new_string("LevelName", level_name),
|
||||
Tag::new_string("LevelName", level.level_name.clone()),
|
||||
);
|
||||
let file = File::create(path)?;
|
||||
root.insert(
|
||||
"raining".to_string(),
|
||||
Tag::new_byte("raining", i8::from(level.raining)),
|
||||
);
|
||||
root.insert(
|
||||
"RainTime".to_string(),
|
||||
Tag::new_int("RainTime", level.rain_time),
|
||||
);
|
||||
root.insert(
|
||||
"SpawnAngle".to_string(),
|
||||
Tag::new_float("SpawnAngle", level.spawn_angle),
|
||||
);
|
||||
root.insert("SpawnX".to_string(), Tag::new_int("SpawnX", level.spawn_x));
|
||||
root.insert("SpawnY".to_string(), Tag::new_int("SpawnY", level.spawn_y));
|
||||
root.insert("SpawnZ".to_string(), Tag::new_int("SpawnZ", level.spawn_z));
|
||||
root.insert(
|
||||
"thundering".to_string(),
|
||||
Tag::new_byte("thundering", i8::from(level.thundering)),
|
||||
);
|
||||
root.insert(
|
||||
"ThunderTime".to_string(),
|
||||
Tag::new_int("ThunderTime", level.thunder_time),
|
||||
);
|
||||
root.insert("Time".to_string(), Tag::new_long("Time", level.time));
|
||||
root.insert(
|
||||
"version".to_string(),
|
||||
Tag::new_int("version", level.version_id),
|
||||
);
|
||||
root.insert(
|
||||
"WanderingTraderSpawnChance".to_string(),
|
||||
Tag::new_int(
|
||||
"WanderingTraderSpawnChance",
|
||||
level.wandering_trader_spawn_chance,
|
||||
),
|
||||
);
|
||||
root.insert(
|
||||
"WanderingTraderSpawnDelay".to_string(),
|
||||
Tag::new_int(
|
||||
"WanderingTraderSpawnDelay",
|
||||
level.wandering_trader_spawn_delay,
|
||||
),
|
||||
);
|
||||
root.insert(
|
||||
"WasModded".to_string(),
|
||||
Tag::new_byte("WasModded", i8::from(level.was_modded)),
|
||||
);
|
||||
|
||||
// Mirror of older key from your example, sourced from struct:
|
||||
root.insert(
|
||||
"RandomSeed".to_string(),
|
||||
Tag::new_long("RandomSeed", level.world_gen_settings.seed),
|
||||
);
|
||||
root.insert(
|
||||
"MapFeatures".to_string(),
|
||||
Tag::new_byte(
|
||||
"MapFeatures",
|
||||
i8::from(level.world_gen_settings.generate_features),
|
||||
),
|
||||
);
|
||||
|
||||
// --- GameRules ---
|
||||
let mut gr = Tag::new_compound("GameRules");
|
||||
for (k, v) in &level.game_rules {
|
||||
gr.insert(k.clone(), Tag::new_string(k, v.clone()));
|
||||
}
|
||||
root.insert("GameRules".to_string(), gr);
|
||||
|
||||
// --- Version (compound) ---
|
||||
let mut ver = Tag::new_compound("Version");
|
||||
ver.insert("Id".to_string(), Tag::new_int("Id", level.version.id));
|
||||
ver.insert(
|
||||
"Name".to_string(),
|
||||
Tag::new_string("Name", level.version.name.clone()),
|
||||
);
|
||||
ver.insert(
|
||||
"Series".to_string(),
|
||||
Tag::new_string("Series", level.version.series.clone()),
|
||||
);
|
||||
ver.insert(
|
||||
"Snapshot".to_string(),
|
||||
Tag::new_byte("Snapshot", level.version.snapshot),
|
||||
);
|
||||
root.insert("Version".to_string(), ver);
|
||||
|
||||
// --- DataPacks ---
|
||||
let mut dp = Tag::new_compound("DataPacks");
|
||||
let enabled_list: Vec<Tag> = level
|
||||
.data_packs
|
||||
.enabled
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|s| Tag::new_string("", s))
|
||||
.collect();
|
||||
let disabled_list: Vec<Tag> = level
|
||||
.data_packs
|
||||
.disabled
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|s| Tag::new_string("", s))
|
||||
.collect();
|
||||
dp.insert(
|
||||
"Enabled".to_string(),
|
||||
Tag::new_list("Enabled", 8, enabled_list),
|
||||
);
|
||||
dp.insert(
|
||||
"Disabled".to_string(),
|
||||
Tag::new_list("Disabled", 8, disabled_list),
|
||||
);
|
||||
|
||||
root.insert("DataPacks".to_string(), dp);
|
||||
|
||||
// --- DragonFight ---
|
||||
let mut df = Tag::new_compound("DragonFight");
|
||||
let gateways_list: Vec<Tag> = level
|
||||
.dragon_fight
|
||||
.gateways
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|n| Tag::new_int("", n))
|
||||
.collect();
|
||||
df.insert(
|
||||
"Gateways".to_string(),
|
||||
Tag::new_list("Gateways", 3, gateways_list),
|
||||
);
|
||||
df.insert(
|
||||
"DragonKilled".to_string(),
|
||||
Tag::new_byte("DragonKilled", i8::from(level.dragon_fight.dragon_killed)),
|
||||
);
|
||||
df.insert(
|
||||
"NeedsStateScanning".to_string(),
|
||||
Tag::new_byte(
|
||||
"NeedsStateScanning",
|
||||
i8::from(level.dragon_fight.needs_state_scanning),
|
||||
),
|
||||
);
|
||||
df.insert(
|
||||
"PreviouslyKilled".to_string(),
|
||||
Tag::new_byte(
|
||||
"PreviouslyKilled",
|
||||
i8::from(level.dragon_fight.previously_killed),
|
||||
),
|
||||
);
|
||||
root.insert("DragonFight".to_string(), df);
|
||||
|
||||
// --- WorldGenSettings ---
|
||||
let mut dims = Tag::new_compound("dimensions");
|
||||
for (name, dim) in &level.world_gen_settings.dimensions {
|
||||
dims.insert(
|
||||
name.clone(),
|
||||
Tag::new_string(name, dim_to_str(dim).to_string()),
|
||||
);
|
||||
}
|
||||
let mut wgs = Tag::new_compound("WorldGenSettings");
|
||||
wgs.insert(
|
||||
"seed".to_string(),
|
||||
Tag::new_long("seed", level.world_gen_settings.seed),
|
||||
);
|
||||
wgs.insert(
|
||||
"generate_features".to_string(),
|
||||
Tag::new_byte(
|
||||
"generate_features",
|
||||
i8::from(level.world_gen_settings.generate_features),
|
||||
),
|
||||
);
|
||||
wgs.insert(
|
||||
"bonus_chest".to_string(),
|
||||
Tag::new_byte(
|
||||
"bonus_chest",
|
||||
i8::from(level.world_gen_settings.bonus_chest),
|
||||
),
|
||||
);
|
||||
wgs.insert("dimensions".to_string(), dims);
|
||||
root.insert("WorldGenSettings".to_string(), wgs);
|
||||
|
||||
// --- ServerBrands ---
|
||||
let brands_list: Vec<Tag> = level
|
||||
.server_brands
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|s| Tag::new_string("", s))
|
||||
.collect();
|
||||
root.insert(
|
||||
"ServerBrands".to_string(),
|
||||
Tag::new_list("ServerBrands", 8, brands_list),
|
||||
);
|
||||
|
||||
// --- CustomBossEvents / ScheduledEvents (empty compounds for now) ---
|
||||
root.insert(
|
||||
"CustomBossEvents".to_string(),
|
||||
Tag::new_compound("CustomBossEvents"),
|
||||
);
|
||||
root.insert(
|
||||
"ScheduledEvents".to_string(),
|
||||
Tag::new_compound("ScheduledEvents"),
|
||||
);
|
||||
|
||||
// --- write ---
|
||||
let file = File::create(format!("{path}/level.dat"))?;
|
||||
write_nbt(&root, file)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
105
src/test.rs
105
src/test.rs
@@ -48,33 +48,90 @@ mod perlin_test {
|
||||
/// Test for level.rs
|
||||
#[cfg(test)]
|
||||
mod level_file_test {
|
||||
use crate::level::create_nbt;
|
||||
use crate::level::{
|
||||
CustomBossEvents, DataPacks, Dimension, DragonFight, LevelDat, ScheduledEvents,
|
||||
ServerBrands, VersionInfo, WorldGenSettings, create_nbt,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn test_creation_of_file() -> () {
|
||||
let result = create_nbt(
|
||||
1234,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
213,
|
||||
1235,
|
||||
55555,
|
||||
90000,
|
||||
900,
|
||||
"test".to_string(),
|
||||
"test".to_string(),
|
||||
"target/level.dat".to_string(),
|
||||
);
|
||||
assert_eq!(result.is_ok(), true);
|
||||
fn test_creation_of_file() {
|
||||
let mut game_rules: HashMap<String, String> = HashMap::new();
|
||||
game_rules.insert("doDaylightCycle".into(), "true".into());
|
||||
|
||||
let mut dimensions: HashMap<String, Dimension> = HashMap::new();
|
||||
dimensions.insert("minecraft:overworld".into(), Dimension::Overworld);
|
||||
|
||||
let level = LevelDat {
|
||||
custom_boss_events: CustomBossEvents {},
|
||||
data_packs: DataPacks {
|
||||
disabled: vec![],
|
||||
enabled: vec![],
|
||||
},
|
||||
dragon_fight: DragonFight {
|
||||
gateways: vec![0, 1, 2],
|
||||
dragon_killed: false,
|
||||
needs_state_scanning: false,
|
||||
previously_killed: false,
|
||||
},
|
||||
game_rules,
|
||||
version: VersionInfo {
|
||||
id: 3465,
|
||||
name: "1.20.1".into(),
|
||||
series: "main".into(),
|
||||
snapshot: 0,
|
||||
},
|
||||
world_gen_settings: WorldGenSettings {
|
||||
dimensions,
|
||||
bonus_chest: false,
|
||||
generate_features: true,
|
||||
seed: 1234,
|
||||
},
|
||||
scheduled_events: ScheduledEvents {},
|
||||
server_brands: ServerBrands::from(vec!["vanilla".to_string()]),
|
||||
|
||||
allow_commands: true,
|
||||
|
||||
border_center_x: 0.0,
|
||||
border_center_z: 0.0,
|
||||
border_damage_per_block: 0.0,
|
||||
border_safe_zone: 0.0,
|
||||
border_size: 60_000_000.0,
|
||||
border_size_lerp_target: 60_000_000.0,
|
||||
border_size_lerp_time: 0,
|
||||
border_warning_blocks: 5,
|
||||
border_warning_time: 15,
|
||||
|
||||
clear_weather_time: 0,
|
||||
data_version: 3465,
|
||||
day_time: 0,
|
||||
difficulty: 2,
|
||||
difficulty_locked: false,
|
||||
game_type: 0,
|
||||
hardcore: false,
|
||||
initialized: true,
|
||||
last_played: 0,
|
||||
level_name: "test".into(),
|
||||
raining: false,
|
||||
rain_time: 0,
|
||||
spawn_angle: 0.0,
|
||||
spawn_x: 0,
|
||||
spawn_y: 64,
|
||||
spawn_z: 0,
|
||||
thundering: false,
|
||||
thunder_time: 0,
|
||||
time: 0,
|
||||
version_id: 3465,
|
||||
wandering_trader_spawn_chance: 25,
|
||||
wandering_trader_spawn_delay: 1200,
|
||||
was_modded: false,
|
||||
};
|
||||
|
||||
let result = create_nbt(&level, "target");
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
/// Test for player.rs
|
||||
#[cfg(test)]
|
||||
mod player_data_test {
|
||||
|
||||
Reference in New Issue
Block a user