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:
Ludovic Marie
2026-10-25 06:19:43 +01:00
parent 066f07968b
commit 379692f355
15 changed files with 190 additions and 2333 deletions
-1
View File
@@ -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
+1
View File
@@ -20,3 +20,4 @@ pub mod pipeline;
pub mod processors;
pub mod report;
pub mod worldgen;
mod yung;
+10 -9
View File
@@ -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"));
}
}
+4 -3
View File
@@ -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;
+107
View File
@@ -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 = &registry.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,
&registry.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
View File
@@ -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.
+60 -49
View File
@@ -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: