Structures: save the remaining legacy pieces as vanilla does
Desert and jungle temples, swamp huts, buried treasure, ocean monuments, mineshafts, fortresses and strongholds save the tag their StructurePiece subclass writes, with the state placement leaves: the height a scattered feature settled on, the buried treasure chest, placed chests, traps and spawners, and the swamp hut's witch and cat. With these every StructurePieceType is saved. test_structure_start_nbt_parity gets one site of each of these kinds, and a badlands mineshaft. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -871,6 +871,29 @@ impl LegacyPiece for FortressPiece {
|
||||
fn bounding_box(&self) -> BoundingBox {
|
||||
self.frame.bounding_box
|
||||
}
|
||||
fn save(&self) -> Option<crate::nbt::Compound> {
|
||||
use crate::nbt::Tag;
|
||||
let mut tag = super::legacy::piece_tag(
|
||||
self.type_name(),
|
||||
self.frame.bounding_box,
|
||||
self.frame.orientation,
|
||||
self.depth,
|
||||
);
|
||||
let flag = Tag::Byte(i8::from(*self.flag.lock().unwrap()));
|
||||
match self.kind {
|
||||
Kind::BridgeEndFiller => {
|
||||
tag.insert("Seed".into(), Tag::Int(self.seed));
|
||||
}
|
||||
Kind::SmallCorridorLeftTurn | Kind::SmallCorridorRightTurn => {
|
||||
tag.insert("Chest".into(), flag);
|
||||
}
|
||||
Kind::MonsterThrone => {
|
||||
tag.insert("Mob".into(), flag);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Some(tag)
|
||||
}
|
||||
fn type_name(&self) -> &'static str {
|
||||
match self.kind {
|
||||
// StartPiece is built through the BridgeCrossing constructor.
|
||||
|
||||
@@ -732,6 +732,57 @@ impl LegacyPiece for MineshaftPiece {
|
||||
fn bounding_box(&self) -> BoundingBox {
|
||||
self.frame.bounding_box
|
||||
}
|
||||
fn save(&self) -> Option<crate::nbt::Compound> {
|
||||
use crate::nbt::Tag;
|
||||
let flag = |b: bool| Tag::Byte(i8::from(b));
|
||||
let mut tag = super::legacy::piece_tag(
|
||||
self.type_name(),
|
||||
self.frame.bounding_box,
|
||||
self.frame.orientation,
|
||||
self.depth,
|
||||
);
|
||||
// MineshaftStructure.Type's ordinal: NORMAL, MESA.
|
||||
let mesa = self.wood.log == MESA.log;
|
||||
let mut entries = vec![("MST", Tag::Int(i32::from(mesa)))];
|
||||
match &self.kind {
|
||||
Kind::Room(entrances) => entries.push((
|
||||
"Entrances",
|
||||
Tag::List(
|
||||
entrances
|
||||
.iter()
|
||||
.map(|b| {
|
||||
Tag::IntArray(vec![
|
||||
b.min[0], b.min[1], b.min[2], b.max[0], b.max[1], b.max[2],
|
||||
])
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
)),
|
||||
Kind::Corridor {
|
||||
rails,
|
||||
spider,
|
||||
sections,
|
||||
placed_spider,
|
||||
} => entries.extend([
|
||||
("hr", flag(*rails)),
|
||||
("sc", flag(*spider)),
|
||||
("hps", flag(*placed_spider.lock().unwrap())),
|
||||
("Num", Tag::Int(*sections)),
|
||||
]),
|
||||
Kind::Crossing {
|
||||
direction,
|
||||
two_floored,
|
||||
} => {
|
||||
let d = crate::level::data_2d(*direction) as i8;
|
||||
entries.extend([("tf", flag(*two_floored)), ("D", Tag::Byte(d))]);
|
||||
}
|
||||
Kind::Stairs => {}
|
||||
}
|
||||
for (key, value) in entries {
|
||||
tag.insert(key.into(), value);
|
||||
}
|
||||
Some(tag)
|
||||
}
|
||||
fn type_name(&self) -> &'static str {
|
||||
match self.kind {
|
||||
Kind::Room(_) => "minecraft:msroom",
|
||||
|
||||
@@ -463,6 +463,16 @@ impl LegacyPiece for MonumentBuilding {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"minecraft:omb"
|
||||
}
|
||||
/// `MonumentBuilding`: the base fields only; vanilla builds its rooms
|
||||
/// again after loading (`OceanMonumentStructure.regeneratePiecesAfterLoad`).
|
||||
fn save(&self) -> Option<crate::nbt::Compound> {
|
||||
Some(super::legacy::piece_tag(
|
||||
"minecraft:omb",
|
||||
self.frame.bounding_box,
|
||||
self.frame.orientation,
|
||||
0,
|
||||
))
|
||||
}
|
||||
fn post_process(&self, c: &mut PieceContext<'_>) -> Result<()> {
|
||||
let sea = c.world.sea_level()?;
|
||||
let water = c.state("water");
|
||||
|
||||
@@ -18,11 +18,22 @@ fn empty_processors() -> Arc<ProcessorList> {
|
||||
#[derive(Debug)]
|
||||
pub struct BuriedTreasurePiece {
|
||||
pub position: [i32; 3],
|
||||
/// Where the chest went: the box vanilla saves once placed.
|
||||
pub found: Mutex<Option<[i32; 3]>>,
|
||||
}
|
||||
impl LegacyPiece for BuriedTreasurePiece {
|
||||
fn bounding_box(&self) -> BoundingBox {
|
||||
BoundingBox::new(self.position, self.position)
|
||||
}
|
||||
fn save(&self) -> Option<crate::nbt::Compound> {
|
||||
let at = self.found.lock().unwrap().unwrap_or(self.position);
|
||||
Some(super::legacy::piece_tag(
|
||||
"minecraft:btp",
|
||||
BoundingBox::new(at, at),
|
||||
None,
|
||||
0,
|
||||
))
|
||||
}
|
||||
fn type_name(&self) -> &'static str {
|
||||
"minecraft:btp"
|
||||
}
|
||||
@@ -58,6 +69,7 @@ impl LegacyPiece for BuriedTreasurePiece {
|
||||
}
|
||||
}
|
||||
}
|
||||
*self.found.lock().unwrap() = Some(pos);
|
||||
create_chest(c, pos, "minecraft:chests/buried_treasure", None)?;
|
||||
return Ok(());
|
||||
}
|
||||
@@ -441,6 +453,21 @@ impl Scattered {
|
||||
pub fn frame(&self) -> PieceFrame {
|
||||
*self.frame.lock().unwrap()
|
||||
}
|
||||
/// `ScatteredFeaturePiece`'s tag: the box and the height it settled on.
|
||||
pub fn saved(&self, id: &str) -> crate::nbt::Compound {
|
||||
use crate::nbt::Tag;
|
||||
let f = self.frame();
|
||||
let mut tag = super::legacy::piece_tag(id, f.bounding_box, f.orientation, 0);
|
||||
for (key, value) in [
|
||||
("Width", self.width),
|
||||
("Height", self.height),
|
||||
("Depth", self.depth),
|
||||
("HPos", *self.height_position.lock().unwrap()),
|
||||
] {
|
||||
tag.insert(key.into(), Tag::Int(value));
|
||||
}
|
||||
tag
|
||||
}
|
||||
/// updateAverageGroundHeight over the part of the box inside the chunk.
|
||||
pub fn average_ground(&self, c: &mut PieceContext<'_>, offset: i32) -> Result<bool> {
|
||||
if *self.height_position.lock().unwrap() >= 0 {
|
||||
@@ -498,12 +525,15 @@ impl Scattered {
|
||||
#[derive(Debug)]
|
||||
pub struct SwampHutPiece {
|
||||
pub base: Scattered,
|
||||
/// spawnedWitch and spawnedCat: both spawn in the same chunk.
|
||||
spawned: Mutex<bool>,
|
||||
}
|
||||
impl SwampHutPiece {
|
||||
pub fn new(random: &mut impl RandomSource, west: i32, north: i32) -> Self {
|
||||
let direction = random_horizontal(random);
|
||||
Self {
|
||||
base: Scattered::new(west, 64, north, 7, 7, 9, direction),
|
||||
spawned: Mutex::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -511,6 +541,13 @@ impl LegacyPiece for SwampHutPiece {
|
||||
fn bounding_box(&self) -> BoundingBox {
|
||||
self.base.frame().bounding_box
|
||||
}
|
||||
fn save(&self) -> Option<crate::nbt::Compound> {
|
||||
let mut tag = self.base.saved("minecraft:tesh");
|
||||
let spawned = crate::nbt::Tag::Byte(i8::from(*self.spawned.lock().unwrap()));
|
||||
tag.insert("Witch".into(), spawned.clone());
|
||||
tag.insert("Cat".into(), spawned);
|
||||
Some(tag)
|
||||
}
|
||||
fn type_name(&self) -> &'static str {
|
||||
"minecraft:tesh"
|
||||
}
|
||||
@@ -579,6 +616,7 @@ impl LegacyPiece for SwampHutPiece {
|
||||
c.world
|
||||
.entity(super::legacy::spawn_nbt(id, pos, true, true))?;
|
||||
}
|
||||
*self.spawned.lock().unwrap() = true;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -962,6 +962,7 @@ fn try_generate(
|
||||
let stub = top_of_chunk_center(context, chunk, HeightmapType::OceanFloorWg)?;
|
||||
let piece = super::simple::BuriedTreasurePiece {
|
||||
position: [chunk.x * 16 + 9, 90, chunk.z * 16 + 9],
|
||||
found: std::sync::Mutex::new(None),
|
||||
};
|
||||
(stub, legacy(vec![piece]))
|
||||
}
|
||||
|
||||
@@ -1075,6 +1075,48 @@ impl LegacyPiece for StrongholdPiece {
|
||||
fn bounding_box(&self) -> BoundingBox {
|
||||
self.frame.bounding_box
|
||||
}
|
||||
fn save(&self) -> Option<crate::nbt::Compound> {
|
||||
use crate::nbt::Tag;
|
||||
let flag = |b: bool| Tag::Byte(i8::from(b));
|
||||
let mut tag = super::legacy::piece_tag(
|
||||
self.type_name(),
|
||||
self.frame.bounding_box,
|
||||
self.frame.orientation,
|
||||
self.depth,
|
||||
);
|
||||
// SmallDoorType.LEGACY_CODEC: the enum name.
|
||||
let door = match self.door {
|
||||
Door::Opening => "OPENING",
|
||||
Door::Wood => "WOOD_DOOR",
|
||||
Door::Grates => "GRATES",
|
||||
Door::Iron => "IRON_DOOR",
|
||||
};
|
||||
let placed = *self.placed.lock().unwrap();
|
||||
let [a, b, c, d] = self.flags;
|
||||
let mut entries = vec![("EntryDoor", Tag::String(door.into()))];
|
||||
match self.kind {
|
||||
Kind::ChestCorridor => entries.push(("Chest", flag(placed))),
|
||||
Kind::Filler => entries.push(("Steps", Tag::Int(self.value))),
|
||||
Kind::FiveCrossing => entries.extend([
|
||||
("leftLow", flag(a)),
|
||||
("leftHigh", flag(b)),
|
||||
("rightLow", flag(c)),
|
||||
("rightHigh", flag(d)),
|
||||
]),
|
||||
Kind::Library => entries.push(("Tall", flag(a))),
|
||||
Kind::PortalRoom => entries.push(("Mob", flag(placed))),
|
||||
Kind::RoomCrossing => entries.push(("Type", Tag::Int(self.value))),
|
||||
// StartPiece is a StairsDown that is the source.
|
||||
Kind::StairsDown => entries.push(("Source", flag(false))),
|
||||
Kind::Start => entries.push(("Source", flag(true))),
|
||||
Kind::Straight => entries.extend([("Left", flag(a)), ("Right", flag(b))]),
|
||||
_ => {}
|
||||
}
|
||||
for (key, value) in entries {
|
||||
tag.insert(key.into(), value);
|
||||
}
|
||||
Some(tag)
|
||||
}
|
||||
fn type_name(&self) -> &'static str {
|
||||
match self.kind {
|
||||
Kind::Straight => "minecraft:shs",
|
||||
|
||||
@@ -82,6 +82,16 @@ impl LegacyPiece for DesertPyramidPiece {
|
||||
fn bounding_box(&self) -> BoundingBox {
|
||||
self.base.frame().bounding_box
|
||||
}
|
||||
fn save(&self) -> Option<crate::nbt::Compound> {
|
||||
let mut tag = self.base.saved("minecraft:tedp");
|
||||
for (i, placed) in self.placed_chest.lock().unwrap().iter().enumerate() {
|
||||
tag.insert(
|
||||
format!("hasPlacedChest{i}").as_str().into(),
|
||||
crate::nbt::Tag::Byte(i8::from(*placed)),
|
||||
);
|
||||
}
|
||||
Some(tag)
|
||||
}
|
||||
fn type_name(&self) -> &'static str {
|
||||
"minecraft:tedp"
|
||||
}
|
||||
@@ -448,6 +458,22 @@ impl LegacyPiece for JungleTemplePiece {
|
||||
fn bounding_box(&self) -> BoundingBox {
|
||||
self.base.frame().bounding_box
|
||||
}
|
||||
fn save(&self) -> Option<crate::nbt::Compound> {
|
||||
let mut tag = self.base.saved("minecraft:tejp");
|
||||
let placed = *self.placed.lock().unwrap();
|
||||
for (key, value) in [
|
||||
"placedMainChest",
|
||||
"placedHiddenChest",
|
||||
"placedTrap1",
|
||||
"placedTrap2",
|
||||
]
|
||||
.into_iter()
|
||||
.zip(placed)
|
||||
{
|
||||
tag.insert(key.into(), crate::nbt::Tag::Byte(i8::from(value)));
|
||||
}
|
||||
Some(tag)
|
||||
}
|
||||
fn type_name(&self) -> &'static str {
|
||||
"minecraft:tejp"
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
The oracle (VanillaStructureStartOracle with HORIZON_START_NBT) generates the start of each case's chunk with the
|
||||
pinned server's own classes and prints its tag; the probe (world_probe structure_start_nbt) prints the tag
|
||||
Horizon saves for the same chunk. Both print sorted keys and typed numbers, and the lines must be identical.
|
||||
The cases are the start chunks of one site of every structure kind built from jigsaw pools or templates (seed
|
||||
12345).
|
||||
The cases are the start chunks of one site of every structure kind (seed 12345, and a badlands mineshaft of seed
|
||||
-7777), which between them use every StructurePieceType.
|
||||
"""
|
||||
import argparse
|
||||
import collections
|
||||
@@ -31,6 +31,9 @@ CASES = [
|
||||
(12345, 'overworld', 114, -133), (12345, 'overworld', -16, 5), (12345, 'overworld', -109, 91),
|
||||
(12345, 'overworld', 0, -18), (12345, 'overworld', -150, 51), (12345, 'overworld', 61, 49),
|
||||
(12345, 'the_nether', 0, -18), (12345, 'the_nether', -22, 6), (12345, 'the_end', -15, -76),
|
||||
(12345, 'overworld', 38, -115), (12345, 'overworld', -238, -313), (12345, 'overworld', -95, 2),
|
||||
(12345, 'overworld', -126, -106), (12345, 'overworld', 47, 25), (12345, 'overworld', 110, 23),
|
||||
(12345, 'overworld', 4, -1), (12345, 'the_nether', -13, -37), (-7777, 'overworld', 75, 84),
|
||||
]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user