Port YUNG's worldgen as written; delete the old YUNG conversion and assembler
Clean-room (spec-yungsapi.md §2, §3, §5; no mod source read; the legacy files were deleted unread). horizon-port-pack's new `yung` module carries `yungsapi:yung_jigsaw` structures and `yungsapi:yung_single_element` pool elements as the mods write them, so players' packs reach the YUNG draws of the jigsaw assembler. Fields whose behaviour the specification leaves open (adapt_noise, max_y/min_y, enhanced terrain, element conditions, modifiers, priorities, depths, ignore_bounds) are counted as lost by name. YUNG's other structure, placement and processor types go to the general code (unsupported or dropped) until the specification covers them. Deleted: legacy_structure_mods/yung.rs, horizon-world's structure/yung.rs, the `horizon:yungsapi` keys and `horizon:disables`, which nothing writes now. Two test initializers of the removed field (enhanced_terrain.rs, piece_limited.rs) were removed by line from the compiler's error positions. The layout harness now carries the mods through `horizon-port-pack carry`: 82 of 84 Better Dungeons starts match the oracle piece for piece, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -814,7 +814,6 @@ pub fn carry(
|
||||
if kind == "structure" {
|
||||
let path = rel.trim_start_matches("worldgen/structure/");
|
||||
let structure = format!("{ns}:{}", path.trim_end_matches(".json"));
|
||||
changed |= crate::legacy_structure_mods::yung::disables(&structure, &mut v);
|
||||
changed |=
|
||||
crate::worldgen::add_piece_counts(&structure, &mut v, &piece_counts);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
//! conversion.
|
||||
pub(crate) mod ctov;
|
||||
pub(crate) mod lithostitched;
|
||||
pub(crate) mod yung;
|
||||
|
||||
#[cfg(test)]
|
||||
fn data() -> crate::processors::ProcessorData {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,3 +20,4 @@ pub mod pipeline;
|
||||
pub mod processors;
|
||||
pub mod report;
|
||||
pub mod worldgen;
|
||||
mod yung;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
//! What is not here is taken out of its list later and counted
|
||||
//! (`worldgen::upgrade`). The entity NBT is written in 26.3's shape (an
|
||||
//! `equipment` map, not 1.21.1's `HandItems` and `ArmorItems`).
|
||||
use crate::legacy_structure_mods::{ctov, lithostitched, yung};
|
||||
use crate::legacy_structure_mods::{ctov, lithostitched};
|
||||
use serde_json::{Value, json};
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
@@ -177,12 +177,12 @@ fn carry_at(
|
||||
let mut out = Vec::with_capacity(expanded.len());
|
||||
for p in expanded {
|
||||
let kind = p["processor_type"].as_str().unwrap_or_default().to_owned();
|
||||
// Lithostitched's and YUNG's processors are carried apart from this
|
||||
// code (`legacy_structure_mods`).
|
||||
// Lithostitched's processors are carried apart from this code
|
||||
// (`legacy_structure_mods`).
|
||||
let replacement = lithostitched::processor(&kind, &p, data, carried).or_else(|| {
|
||||
match pillar_type(&kind).or_else(|| yung::pillar_type(&kind)) {
|
||||
match pillar_type(&kind) {
|
||||
Some(to) => Some(vec![pillar(&p, to, data, carried, depth)]),
|
||||
None => as_data(&kind, &p, data).or_else(|| yung::processor(&kind, &p)),
|
||||
None => as_data(&kind, &p, data),
|
||||
}
|
||||
});
|
||||
match replacement {
|
||||
@@ -860,8 +860,10 @@ mod tests {
|
||||
"horizon:bottom_pillar",
|
||||
"horizon:close_off_fluid_sources",
|
||||
"horizon:close_off_air_sources",
|
||||
"horizon:yung_pillar",
|
||||
"horizon:stair_pillar"
|
||||
// YUNG's processors stay Java (dropped later) until the
|
||||
// specification covers them.
|
||||
"betterfortresses:pillar_processor",
|
||||
"betterfortresses:stair_pillar_processor"
|
||||
]
|
||||
);
|
||||
// The named list is written in once; its own pillar loses its list.
|
||||
@@ -878,8 +880,7 @@ mod tests {
|
||||
p[4]["weighted_list_of_replacement_blocks"],
|
||||
json!([{"block": "minecraft:prismarine", "weight": 1}])
|
||||
);
|
||||
assert_eq!(p[6]["pillar"], "minecraft:red_nether_bricks");
|
||||
assert_eq!(carried["repurposed_structures:pillar_processor"], 2);
|
||||
assert_eq!(carried["betterfortresses:stair_pillar_processor"], 1);
|
||||
assert!(!carried.contains_key("betterfortresses:stair_pillar_processor"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
//! as it is is listed, so the coverage report can say what generates and how
|
||||
//! closely.
|
||||
//!
|
||||
//! YUNG's, Lithostitched's and CTOV's worldgen files are converted by
|
||||
//! YUNG's API's files are carried as written ([`crate::yung`]).
|
||||
//! Lithostitched's and CTOV's worldgen files are converted by
|
||||
//! `legacy_structure_mods`, called from [`upgrade`], [`crate::carry`] and the
|
||||
//! processors.
|
||||
use crate::legacy_structure_mods::{lithostitched, yung};
|
||||
use crate::{legacy_structure_mods::lithostitched, yung};
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
@@ -262,7 +263,7 @@ fn upgrade_types(
|
||||
let lost = &mut report.rules_lost;
|
||||
// YUNG's and Lithostitched's types are converted apart from this code.
|
||||
if let Some(changed) =
|
||||
yung::upgrade(kind, v, set_tags, lost).or_else(|| lithostitched::upgrade(kind, v, lost))
|
||||
yung::upgrade(kind, v, lost).or_else(|| lithostitched::upgrade(kind, v, lost))
|
||||
{
|
||||
report.mapped += usize::from(changed);
|
||||
return changed;
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
//! YUNG's API's worldgen, carried as the mods write it (clean room:
|
||||
//! `cleanroom/structures/spec-yungsapi.md`).
|
||||
//!
|
||||
//! The game reads `yungsapi:yung_jigsaw` structures and
|
||||
//! `yungsapi:yung_single_element` pool elements as they ship
|
||||
//! (`horizon_world::worldgen::structure::jigsaw::Draws::Yung`), so their files
|
||||
//! pass through unchanged. What the game does not run yet is counted by name
|
||||
//! in the carry report: the fields whose behaviour the specification leaves
|
||||
//! open. YUNG's other structure, placement and processor types are left to the
|
||||
//! general code, which lists them as unsupported or drops them.
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
const JIGSAW: &str = "yungsapi:yung_jigsaw";
|
||||
const ELEMENT: &str = "yungsapi:yung_single_element";
|
||||
/// A terrain adaptation that adapts nothing.
|
||||
const NO_ADAPTATION: &str = "yungsapi:none";
|
||||
|
||||
fn count(lost: &mut BTreeMap<String, usize>, rule: String) {
|
||||
*lost.entry(rule).or_default() += 1;
|
||||
}
|
||||
|
||||
/// Whether a field changes generation in a way the game does not run yet:
|
||||
/// present, and not the value that changes nothing.
|
||||
fn active(v: &Value, field: &str) -> bool {
|
||||
match &v[field] {
|
||||
Value::Null | Value::Bool(false) => false,
|
||||
Value::Object(o) => o.get("type").and_then(Value::as_str) != Some(NO_ADAPTATION),
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// A `yung_jigsaw` structure: `Some(false)` (carried as it is), else `None`.
|
||||
pub(crate) fn upgrade(kind: &str, v: &Value, lost: &mut BTreeMap<String, usize>) -> Option<bool> {
|
||||
if kind != "structure" || v["type"] != JIGSAW {
|
||||
return None;
|
||||
}
|
||||
// spec-yungsapi §2: OPEN-Y1, OPEN-Y2 and §5.
|
||||
for field in [
|
||||
"adapt_noise",
|
||||
"max_y",
|
||||
"min_y",
|
||||
"enhanced_terrain_adaptation",
|
||||
] {
|
||||
if active(v, field) {
|
||||
count(lost, format!("{JIGSAW} {field}"));
|
||||
}
|
||||
}
|
||||
Some(false)
|
||||
}
|
||||
|
||||
/// A `yung_single_element` pool element, carried as it is; whether it was one.
|
||||
pub(crate) fn upgrade_pool_element(element: &Value, lost: &mut BTreeMap<String, usize>) -> bool {
|
||||
if element["element_type"] != ELEMENT {
|
||||
return false;
|
||||
}
|
||||
// spec-yungsapi §3: OPEN-Y5 to OPEN-Y9 and §5.
|
||||
for field in [
|
||||
"is_priority",
|
||||
"min_required_depth",
|
||||
"ignore_bounds",
|
||||
"condition",
|
||||
"enhanced_terrain_adaptation",
|
||||
"modifiers",
|
||||
] {
|
||||
if active(element, field) {
|
||||
count(lost, format!("{ELEMENT} {field}"));
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn jigsaws_and_elements_are_carried_as_written() {
|
||||
let mut lost = BTreeMap::new();
|
||||
let dungeon =
|
||||
json!({"type": JIGSAW, "start_pool": "a:b", "size": 10, "adapt_noise": false});
|
||||
assert_eq!(upgrade("structure", &dungeon, &mut lost), Some(false));
|
||||
assert!(lost.is_empty());
|
||||
let stronghold = json!({"type": JIGSAW, "max_y": 0,
|
||||
"enhanced_terrain_adaptation": {"type": "yungsapi:carved"}});
|
||||
assert_eq!(upgrade("structure", &stronghold, &mut lost), Some(false));
|
||||
assert_eq!(lost[&format!("{JIGSAW} max_y")], 1);
|
||||
assert_eq!(lost[&format!("{JIGSAW} enhanced_terrain_adaptation")], 1);
|
||||
assert_eq!(
|
||||
upgrade("structure", &json!({"type": "minecraft:jigsaw"}), &mut lost),
|
||||
None
|
||||
);
|
||||
assert_eq!(upgrade("structure_set", &dungeon, &mut lost), None);
|
||||
|
||||
let mut lost = BTreeMap::new();
|
||||
let pile = json!({"element_type": ELEMENT, "name": "loot_pile_0", "max_count": 1,
|
||||
"enhanced_terrain_adaptation": {"type": NO_ADAPTATION}, "is_priority": false});
|
||||
assert!(upgrade_pool_element(&pile, &mut lost));
|
||||
assert!(lost.is_empty());
|
||||
let gated = json!({"element_type": ELEMENT, "condition": {"type": "yungsapi:not"}});
|
||||
assert!(upgrade_pool_element(&gated, &mut lost));
|
||||
assert_eq!(lost[&format!("{ELEMENT} condition")], 1);
|
||||
let single = json!({"element_type": "minecraft:single_pool_element"});
|
||||
assert!(!upgrade_pool_element(&single, &mut lost));
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ pub mod start_checks;
|
||||
pub mod stronghold;
|
||||
pub mod template;
|
||||
pub mod temple;
|
||||
pub mod yung;
|
||||
|
||||
use crate::{
|
||||
nbt::{self, Limits},
|
||||
|
||||
@@ -306,7 +306,6 @@ mod tests {
|
||||
legacy: false,
|
||||
projection,
|
||||
waterlogging: None,
|
||||
yung: None,
|
||||
enhanced_terrain: own.map(Arc::new),
|
||||
ground_level: 1,
|
||||
})
|
||||
|
||||
@@ -39,9 +39,6 @@ pub enum PoolElement {
|
||||
legacy: bool,
|
||||
projection: Projection,
|
||||
waterlogging: Option<bool>,
|
||||
/// YUNG's API's rules on the piece (`"horizon:yungsapi"`), read only
|
||||
/// by its assembler ([`super::yung`]).
|
||||
yung: Option<Box<super::yung::Rules>>,
|
||||
/// The piece's own terrain kernel in a structure that has one
|
||||
/// (`"horizon:enhanced_terrain_adaptation"`).
|
||||
enhanced_terrain: Option<Arc<super::enhanced_terrain::Adaptation>>,
|
||||
@@ -149,10 +146,6 @@ impl PoolElement {
|
||||
Some("ignore_waterlogging") => Some(false),
|
||||
_ => None,
|
||||
},
|
||||
yung: value
|
||||
.get("horizon:yungsapi")
|
||||
.map(|rules| super::yung::Rules::decode(rules, resources).map(Box::new))
|
||||
.transpose()?,
|
||||
enhanced_terrain: value
|
||||
.get("horizon:enhanced_terrain_adaptation")
|
||||
.map(|a| super::enhanced_terrain::Adaptation::decode(a).map(Arc::new))
|
||||
@@ -248,13 +241,10 @@ impl PoolElement {
|
||||
.first()
|
||||
.map(|e| e.shuffled_jigsaws(resources, position, rotation, random))
|
||||
.unwrap_or_default(),
|
||||
Self::Single { template, yung, .. } => {
|
||||
Self::Single { template, .. } => {
|
||||
let mut jigsaws = template_jigsaws(resources, template, position, rotation);
|
||||
shuffle(&mut jigsaws, random);
|
||||
// YUNG's elements keep the shuffled order.
|
||||
if yung.is_none() {
|
||||
jigsaws.sort_by_key(|j| std::cmp::Reverse(j.selection_priority));
|
||||
}
|
||||
jigsaws.sort_by_key(|j| std::cmp::Reverse(j.selection_priority));
|
||||
jigsaws
|
||||
}
|
||||
}
|
||||
@@ -265,13 +255,6 @@ impl PoolElement {
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
/// YUNG's API's rules, on a piece of one of its structures.
|
||||
pub fn yung(&self) -> Option<&super::yung::Rules> {
|
||||
match self {
|
||||
Self::Single { yung, .. } => yung.as_deref(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
/// The piece's own terrain kernel, in place of its structure's.
|
||||
pub fn enhanced_terrain(&self) -> Option<&super::enhanced_terrain::Adaptation> {
|
||||
match self {
|
||||
|
||||
@@ -1025,7 +1025,6 @@ mod tests {
|
||||
legacy: false,
|
||||
projection: Projection::Rigid,
|
||||
waterlogging: None,
|
||||
yung: None,
|
||||
enhanced_terrain: None,
|
||||
ground_level: 1,
|
||||
});
|
||||
|
||||
@@ -59,8 +59,6 @@ pub struct JigsawDef {
|
||||
waterlogging: bool,
|
||||
/// A carried mod's start rules (`"horizon:start_checks"`).
|
||||
checks: super::start_checks::StartChecks,
|
||||
/// Laid out by YUNG's API's assembler (`"horizon:yungsapi"`).
|
||||
yung: Option<super::yung::Config>,
|
||||
/// Vanilla's draws, or YUNG's API's for `yungsapi:yung_jigsaw`.
|
||||
draws: super::jigsaw::Draws,
|
||||
/// Laid out by Repurposed Structures' and Moog's piece-limited
|
||||
@@ -111,9 +109,6 @@ pub struct StructureDef {
|
||||
/// A structure library's terrain kernel
|
||||
/// (`"horizon:enhanced_terrain_adaptation"`).
|
||||
pub enhanced_terrain: Option<super::enhanced_terrain::StructureAdaptation>,
|
||||
/// Another loaded structure turns this one's type off
|
||||
/// (`"horizon:disables"`): it never starts.
|
||||
pub disabled: bool,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum Reduction {
|
||||
@@ -311,19 +306,8 @@ impl StructureRegistry {
|
||||
biome_set: &dyn Fn(&Value) -> Result<BTreeSet<BiomeId>, DecodeError>,
|
||||
) -> Result<Self, DecodeError> {
|
||||
let mut defs = Vec::new();
|
||||
let (mut types, mut disables) = (Vec::new(), BTreeSet::new());
|
||||
for (name, json) in structures {
|
||||
let v: Value = serde_json::from_str(json)?;
|
||||
types.push(v["type"].as_str().unwrap_or_default().to_owned());
|
||||
// YUNG's mods turn their vanilla counterparts off (the
|
||||
// DisableVanilla*Mixin on ChunkGenerator.tryGenerateStructure).
|
||||
disables.extend(
|
||||
v["horizon:disables"]
|
||||
.as_array()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|t| Some(t.as_str()?.to_owned())),
|
||||
);
|
||||
let draws = match v["type"].as_str() {
|
||||
Some("yungsapi:yung_jigsaw") => super::jigsaw::Draws::Yung,
|
||||
_ => super::jigsaw::Draws::Vanilla,
|
||||
@@ -347,7 +331,6 @@ impl StructureRegistry {
|
||||
padding: pair(v.get("dimension_padding"), "bottom", "top", 0),
|
||||
waterlogging: v["liquid_settings"].as_str() != Some("ignore_waterlogging"),
|
||||
checks: super::start_checks::StartChecks::decode(v.get("horizon:start_checks")),
|
||||
yung: v.get("horizon:yungsapi").map(super::yung::Config::decode),
|
||||
piece_limited: v
|
||||
.get("horizon:piece_limited")
|
||||
.map(super::piece_limited::Config::decode),
|
||||
@@ -408,12 +391,8 @@ impl StructureRegistry {
|
||||
.get("horizon:enhanced_terrain_adaptation")
|
||||
.map(super::enhanced_terrain::StructureAdaptation::decode)
|
||||
.transpose()?,
|
||||
disabled: false,
|
||||
});
|
||||
}
|
||||
for (def, kind) in defs.iter_mut().zip(&types) {
|
||||
def.disabled = disables.contains(kind);
|
||||
}
|
||||
let index = |name: &str| defs.iter().position(|d| d.name == name);
|
||||
let mut out = Vec::new();
|
||||
for (name, json) in sets {
|
||||
@@ -879,9 +858,6 @@ fn try_generate(
|
||||
chunk: ChunkPos,
|
||||
) -> Option<StructureStart> {
|
||||
let def = ®istry.structures[structure];
|
||||
if def.disabled {
|
||||
return None;
|
||||
}
|
||||
context.set_valid_biomes(&def.biomes);
|
||||
let seed = context.seed();
|
||||
let mut random = WorldgenRandom::new(LegacyRandom::new(0));
|
||||
@@ -911,8 +887,8 @@ fn try_generate(
|
||||
padding: jigsaw.padding,
|
||||
waterlogging: jigsaw.waterlogging,
|
||||
};
|
||||
let (position, mut pieces) = match (&jigsaw.yung, &jigsaw.piece_limited) {
|
||||
(_, Some(limits)) => super::piece_limited::assemble(
|
||||
let (position, mut pieces) = match &jigsaw.piece_limited {
|
||||
Some(limits) => super::piece_limited::assemble(
|
||||
&config,
|
||||
limits,
|
||||
context,
|
||||
@@ -922,17 +898,7 @@ fn try_generate(
|
||||
chunk,
|
||||
start,
|
||||
)?,
|
||||
(Some(yung), None) => super::yung::assemble(
|
||||
&config,
|
||||
yung,
|
||||
context,
|
||||
®istry.pools,
|
||||
resources,
|
||||
bindings,
|
||||
&mut random,
|
||||
start,
|
||||
)?,
|
||||
(None, None) => assemble_drawing(
|
||||
None => assemble_drawing(
|
||||
jigsaw.draws,
|
||||
&config,
|
||||
context,
|
||||
@@ -1437,37 +1403,6 @@ mod tests {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_structure_turns_its_vanilla_counterpart_off() {
|
||||
let r = registry(
|
||||
&[
|
||||
(
|
||||
"minecraft:pyramid",
|
||||
json!({"type": "minecraft:desert_pyramid"}),
|
||||
),
|
||||
("minecraft:igloo", json!({"type": "minecraft:igloo"})),
|
||||
(
|
||||
"yung:temple",
|
||||
json!({"type": "minecraft:swamp_hut", "horizon:disables": ["minecraft:desert_pyramid"]}),
|
||||
),
|
||||
],
|
||||
&[],
|
||||
);
|
||||
let disabled: Vec<_> = r
|
||||
.structures
|
||||
.iter()
|
||||
.map(|d| (d.name.as_str(), d.disabled))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
disabled,
|
||||
[
|
||||
("minecraft:igloo", false),
|
||||
("minecraft:pyramid", true),
|
||||
("yung:temple", false)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclusion_zones_may_skip_the_other_sets_own() {
|
||||
let set = |avoid: Value| {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -350,14 +350,14 @@ A pool addition holds `"elements"` as a pool does. This is what lithostitched's
|
||||
NeoForge and Fabric, and `horizon-port-pack carry` writes them from those.
|
||||
|
||||
A structure (and any of its pool elements, in place of the structure's) may
|
||||
hold `horizon:enhanced_terrain_adaptation`: YUNG's API's and
|
||||
MoogsStructureLib's terrain kernel, which carves or buries terrain around the
|
||||
hold `horizon:enhanced_terrain_adaptation`: MoogsStructureLib's terrain
|
||||
kernel, which carves or buries terrain around the
|
||||
pieces on top of vanilla's `terrain_adaptation`. `kernel_size` and
|
||||
`kernel_distance` shape the kernel; `top` and `bottom` (`carve`, `bury` or
|
||||
`none`) act above and below a piece's floor; `bottom_offset`, `padding`
|
||||
(`x`, `top`, `bottom`, `z`) and MoogsStructureLib's `band` (`bottom`, `top`,
|
||||
`piece_heights`) move the box it measures from. On a structure,
|
||||
`rigid_junctions` (YUNG's API) adds rigid pieces' junctions as well and
|
||||
`rigid_junctions` adds rigid pieces' junctions as well and
|
||||
`inflate` grows the start's box by the kernel radius. `kernel_size` 0 is
|
||||
none.
|
||||
|
||||
|
||||
@@ -17,12 +17,11 @@ The oracle's game is 1.21.1 and Horizon's 26.3. KNOWN lists the starts where 26.
|
||||
1.21.1, each checked against 26.3 vanilla (VanillaChunkGenerationOracle's `scan`, HORIZON_PIECES=1), whose
|
||||
pieces equal Horizon's there; they are reported, not failed.
|
||||
|
||||
Mod profiles compare the mod world's `pieces` instead. Horizon then reads the mod's own data from its jar
|
||||
(`.local/structure-oracle/jars/`, or HORIZON_STRUCTURE_JARS): an overlay of the 26.3 resources adds the
|
||||
jar's structures, sets, pools, processor lists, biome tags and templates, as they are written. The mod's
|
||||
own processors are left out of the overlay's lists: they change blocks, never which pieces are placed.
|
||||
Block states are upgraded to 26.3's JSON shape, as the port does.
|
||||
The jars are the player's; nothing of them enters the repository.
|
||||
Mod profiles compare the mod world's `pieces` instead, through the path players' packs take: the mod jars
|
||||
(`.local/structure-oracle/jars/`, or HORIZON_STRUCTURE_JARS) are carried by `horizon-port-pack carry` into
|
||||
a fresh work folder, and an overlay of the 26.3 resources adds the carried structures, sets, pools,
|
||||
processor lists, biome tags and templates. The jars are the player's; nothing of them enters the
|
||||
repository.
|
||||
|
||||
usage: test_structure_layout_parity.py [--profile lithostitched|betterdungeons] [--filter REGEX] [--verbose]
|
||||
"""
|
||||
@@ -34,7 +33,6 @@ import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
@@ -44,12 +42,13 @@ except ImportError:
|
||||
|
||||
ORACLE = Path(os.environ.get('HORIZON_STRUCTURE_ORACLE', lab.LOCAL / 'structure-oracle'))
|
||||
JARS = Path(os.environ.get('HORIZON_STRUCTURE_JARS', ORACLE / 'jars'))
|
||||
# Per profile: which saved pieces are expected, the mod jars whose data Horizon reads, and the structures
|
||||
# compared (a regex; None: every start that has the pieces).
|
||||
# Per profile: which saved pieces are expected, the mods in the pack (id: jar), those carried, and the
|
||||
# structures compared (a regex; None: every start that has the pieces).
|
||||
PROFILES = {
|
||||
'lithostitched': {'side': 'base_pieces', 'jars': [], 'structures': None},
|
||||
'betterdungeons': {'side': 'pieces', 'jars': ['YungsApi-1.21.1-NeoForge-5.1.9.jar', 'YungsBetterDungeons-1.21.1-NeoForge-5.1.4.jar'],
|
||||
'structures': r'^betterdungeons:(small|skeleton|zombie)_dungeon$'},
|
||||
'lithostitched': {'side': 'base_pieces', 'mods': {}, 'carry': [], 'structures': None},
|
||||
'betterdungeons': {'side': 'pieces', 'mods': {'yungsapi': 'YungsApi-1.21.1-NeoForge-5.1.9.jar',
|
||||
'betterdungeons': 'YungsBetterDungeons-1.21.1-NeoForge-5.1.4.jar'},
|
||||
'carry': ['betterdungeons'], 'structures': r'^betterdungeons:(small|skeleton|zombie)_dungeon$'},
|
||||
}
|
||||
BUNDLES = {'structure': 'structures', 'structure_set': 'structure_sets', 'template_pool': 'template_pools',
|
||||
'processor_list': 'processor_lists'}
|
||||
@@ -61,7 +60,7 @@ KNOWN = {
|
||||
('lithostitched', 1234, 'minecraft:village_plains', -24, 47): 'no start in 26.3 vanilla (nor in Horizon)',
|
||||
('lithostitched', 1234, 'minecraft:ruined_portal', -16, 51): RUINED_PORTAL,
|
||||
('lithostitched', 42, 'minecraft:ruined_portal', -19, -38): RUINED_PORTAL,
|
||||
('betterdungeons', 1234, 'betterdungeons:small_dungeon', 162, 10): SULFUR_CAVES,
|
||||
('betterdungeons', 1234, 'betterdungeons:small_dungeon', 162, 10): SULFUR_CAVES,
|
||||
('betterdungeons', 42, 'betterdungeons:small_dungeon', -29, -28): SULFUR_CAVES,
|
||||
**{('lithostitched', seed, 'minecraft:trial_chambers', x, z): TRIAL_CHAMBERS
|
||||
for seed, x, z in [(1234, 13, 21), (2, -28, 13), (2, 11, 16), (2, 146, 44), (42, 17, 7), (42, 38, -25)]},
|
||||
@@ -91,20 +90,34 @@ def expected_piece(saved):
|
||||
return piece
|
||||
|
||||
|
||||
def upgrade_states(value):
|
||||
"""1.21.1's block states (`{"Name", "Properties"}`) in 26.3's shape (`{"id", "properties"}`)."""
|
||||
if isinstance(value, list):
|
||||
return [upgrade_states(v) for v in value]
|
||||
if not isinstance(value, dict):
|
||||
return value
|
||||
if 'Name' in value and set(value) <= {'Name', 'Properties'}:
|
||||
return {'id': value['Name'], **({'properties': value['Properties']} if 'Properties' in value else {})}
|
||||
return {k: upgrade_states(v) for k, v in value.items()}
|
||||
# horizon-port-pack's jar census (inventory.json); the carry does not read it.
|
||||
CENSUS = ['blockstates', 'item_models', 'recipes', 'loot_tables', 'tags', 'worldgen', 'structures', 'languages',
|
||||
'textures', 'sounds', 'classes', 'mixin_configs']
|
||||
|
||||
|
||||
def overlay(profile, jars):
|
||||
"""The 26.3 resources plus the jars' structure data, in a folder the probe reads as its resources."""
|
||||
root = lab.LOCAL / 'structure-layout' / profile
|
||||
def carry(profile, mods, carried):
|
||||
"""`horizon-port-pack carry` of the profile's mods; the carried mods' data folders."""
|
||||
work = lab.LOCAL / 'structure-layout' / profile / 'work'
|
||||
shutil.rmtree(work, ignore_errors=True)
|
||||
work.mkdir(parents=True)
|
||||
records = [{'key': key, 'name': key, 'version': '', 'path': jar, 'tier': 'data', 'confidence': 'known',
|
||||
'class': 'content' if key in carried else 'library', 'source': 'file', 'license': '',
|
||||
'client': '', 'server': '', 'depends': [], 'layer': 0, 'jar': str(JARS / jar),
|
||||
'info': {'mod_id': key, 'name': key, 'version': '', 'license': '', 'depends': [],
|
||||
'census': dict.fromkeys(CENSUS, 0)}}
|
||||
for key, jar in mods.items()]
|
||||
(work / 'inventory.json').write_text(json.dumps(records))
|
||||
subprocess.run(['cargo', 'run', '--release', '-q', '-p', 'horizon-port-pack', '--', 'carry', str(work),
|
||||
'--resources', str(lab.RESOURCES), *carried], check=True, cwd=lab.ROOT, stdout=subprocess.DEVNULL)
|
||||
data = [work / 'mods' / key / 'carry' / 'data' for key in carried]
|
||||
if not all(d.is_dir() for d in data):
|
||||
raise SystemExit(f'horizon-port-pack carried nothing into {work / "mods"}')
|
||||
return data
|
||||
|
||||
|
||||
def overlay(profile, data_dirs):
|
||||
"""The 26.3 resources plus the carried structure data, in a folder the probe reads as its resources."""
|
||||
root = lab.LOCAL / 'structure-layout' / profile / 'resources'
|
||||
shutil.rmtree(root, ignore_errors=True)
|
||||
worldgen = root / 'worldgen'
|
||||
(worldgen / 'structure').mkdir(parents=True)
|
||||
@@ -116,28 +129,24 @@ def overlay(profile, jars):
|
||||
(worldgen / entry.name).symlink_to(entry)
|
||||
for namespace in (source / 'structure').iterdir():
|
||||
(worldgen / 'structure' / namespace.name).symlink_to(namespace)
|
||||
for jar in jars:
|
||||
with zipfile.ZipFile(JARS / jar) as archive:
|
||||
for name in archive.namelist():
|
||||
parts = name.split('/')
|
||||
if parts[0] != 'data' or len(parts) < 4 or name.endswith('/'):
|
||||
continue
|
||||
namespace, kind = parts[1], parts[2]
|
||||
if kind == 'structure' and name.endswith('.nbt'):
|
||||
target = worldgen / 'structure' / namespace / '/'.join(parts[3:])
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_bytes(archive.read(name))
|
||||
elif name.endswith('.json') and kind == 'worldgen' and parts[3] in BUNDLES:
|
||||
value = upgrade_states(json.loads(archive.read(name)))
|
||||
if parts[3] == 'processor_list':
|
||||
value['processors'] = [p for p in value['processors'] if p['processor_type'].startswith('minecraft:')]
|
||||
merged[BUNDLES[parts[3]]][f'{namespace}:' + '/'.join(parts[4:])[:-5]] = json.dumps(value)
|
||||
elif name.endswith('.json') and parts[2:5] == ['tags', 'worldgen', 'biome']:
|
||||
tag = f'{namespace}:' + '/'.join(parts[5:])[:-5]
|
||||
value = json.loads(archive.read(name))
|
||||
if tag in merged['biome_tags'] and not value.get('replace'):
|
||||
value['values'] = json.loads(merged['biome_tags'][tag])['values'] + value['values']
|
||||
merged['biome_tags'][tag] = json.dumps(value)
|
||||
for data in data_dirs:
|
||||
for path in sorted(p for p in data.rglob('*') if p.is_file()):
|
||||
parts = path.relative_to(data).parts
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
namespace, kind = parts[0], parts[1]
|
||||
if kind == 'structure' and path.suffix == '.nbt':
|
||||
target = worldgen / 'structure' / namespace / Path(*parts[2:])
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.symlink_to(path)
|
||||
elif path.suffix == '.json' and kind == 'worldgen' and parts[2] in BUNDLES:
|
||||
merged[BUNDLES[parts[2]]][f'{namespace}:' + '/'.join(parts[3:])[:-5]] = path.read_text()
|
||||
elif path.suffix == '.json' and parts[1:4] == ('tags', 'worldgen', 'biome'):
|
||||
tag = f'{namespace}:' + '/'.join(parts[4:])[:-5]
|
||||
value = json.loads(path.read_text())
|
||||
if tag in merged['biome_tags'] and not value.get('replace'):
|
||||
value['values'] = json.loads(merged['biome_tags'][tag])['values'] + value['values']
|
||||
merged['biome_tags'][tag] = json.dumps(value)
|
||||
for name, bundle in merged.items():
|
||||
(worldgen / f'{name}.json').write_text(json.dumps(bundle))
|
||||
return root
|
||||
@@ -190,11 +199,13 @@ def main():
|
||||
print(f'SKIP: no oracle results in {ORACLE / args.profile} (see this file\'s docstring)')
|
||||
return 0
|
||||
profile = PROFILES[args.profile]
|
||||
missing = [jar for jar in profile['jars'] if not (JARS / jar).exists()]
|
||||
missing = [jar for jar in profile['mods'].values() if not (JARS / jar).exists()]
|
||||
if missing:
|
||||
print(f'SKIP: mod jars not in {JARS}: {", ".join(missing)}')
|
||||
return 0
|
||||
resources = overlay(args.profile, profile['jars']) if profile['jars'] else lab.RESOURCES
|
||||
resources = lab.RESOURCES
|
||||
if profile['carry']:
|
||||
resources = overlay(args.profile, carry(args.profile, profile['mods'], profile['carry']))
|
||||
binary = probe_binary()
|
||||
matched, known, failed = 0, 0, []
|
||||
for path in results:
|
||||
|
||||
Reference in New Issue
Block a user