use nbt::{Tag, Writer}; use std::collections::HashMap; use std::fs::File; pub struct CustomBossEvents { boss: Vec, } impl Default for CustomBossEvents { fn default() -> Self { Self { boss: ["".into()].into(), } } } pub struct DataPacks { pub disabled: Vec, pub enabled: Vec, } impl Default for DataPacks { fn default() -> Self { Self { disabled: [ "minecraft_improvement".into(), "redstone_experiments".into(), "trade_rebalance".into(), ] .into(), enabled: ["vanilla".into()].into(), } } } pub struct DragonFight { pub gateways: Vec, pub dragon_killed: bool, pub needs_state_scanning: bool, pub previously_killed: bool, } impl Default for DragonFight { fn default() -> Self { Self { gateways: [].into(), dragon_killed: false, needs_state_scanning: true, previously_killed: false, } } } pub type GameRules = HashMap; pub struct VersionInfo { pub id: i32, pub name: String, pub series: String, pub snapshot: bool, } impl Default for VersionInfo { fn default() -> Self { Self { id: 4440, name: "1.21.8".into(), series: "main".into(), snapshot: false, } } } pub enum Dimension { Overworld, End, Nether, } pub struct WorldGenSettings { pub dimensions: HashMap, pub bonus_chest: bool, pub generate_features: bool, pub seed: i64, } impl Default for WorldGenSettings { fn default() -> Self { let mut dim: HashMap = HashMap::new(); dim.insert("Overworld".into(), Dimension::Overworld); dim.insert("Nether".into(), Dimension::Nether); dim.insert("The_End".into(), Dimension::End); Self { dimensions: dim, bonus_chest: false, generate_features: true, seed: 42, } } } pub type ServerBrands = Vec; 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: Vec, pub server_brands: ServerBrands, 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, } impl Default for LevelDat { fn default() -> Self { Self { custom_boss_events: CustomBossEvents::default(), data_packs: DataPacks::default(), dragon_fight: DragonFight::default(), game_rules: GameRules::default(), version: VersionInfo::default(), world_gen_settings: WorldGenSettings::default(), scheduled_events: ["".into()].into(), server_brands: ServerBrands::default(), allow_commands: false, border_center_x: 0.0, border_center_z: 0.0, border_damage_per_block: 0.2, border_safe_zone: 5.0, border_size: 59999968.0, border_size_lerp_target: 59999968.0, border_size_lerp_time: 0, border_warning_blocks: 15, border_warning_time: 15, clear_weather_time: 0, data_version: 4440, day_time: 95, difficulty: 1, difficulty_locked: false, game_type: 0, hardcore: false, initialized: true, last_played: 0, level_name: "World".into(), raining: false, rain_time: 0, spawn_angle: 0.0, spawn_x: 0, spawn_y: 82, spawn_z: 0, thundering: false, thunder_time: 0, time: 95, version_id: 19133, wandering_trader_spawn_chance: 25, wandering_trader_spawn_delay: 24000, was_modded: false, } } } 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"); // --- 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(level.hardcore)), ); root.insert( "initialized".to_string(), Tag::new_byte("initialized", i8::from(level.initialized)), ); root.insert( "LastPlayed".to_string(), Tag::new_long("LastPlayed", level.last_played), ); root.insert( "LevelName".to_string(), Tag::new_string("LevelName", level.level_name.clone()), ); 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", i8::from(level.version.snapshot)), ); root.insert("Version".to_string(), ver); // --- DataPacks --- let mut dp = Tag::new_compound("DataPacks"); let enabled_list: Vec = level .data_packs .enabled .iter() .cloned() .map(|s| Tag::new_string("", s)) .collect(); let disabled_list: Vec = 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 = 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 = 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(path)?; let mut w = Writer::to_gzip(file); w.write_tag(&root)?; Ok(()) }