Skip to content

Commit c45ea2a

Browse files
authored
Fix imported vanilla ore rules
Keep imported rules inactive with vanilla management off and avoid duplicate defaults when it is on.
2 parents 1732fe6 + dcc3946 commit c45ea2a

11 files changed

Lines changed: 610 additions & 16 deletions

File tree

‎CHANGELOG.txt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
Version 4.1.0.2601021
22

3+
* Leave imported vanilla ores inactive while Manage Vanilla Ores is off.
4+
* Use active imported settings instead of adding a second default placement pass when it is on.
35
* Keep biome and ore-list text visible with Minecraft's ARGB renderer.
46
* Move Help & Guide beside Recommended Defaults and fit both main menus above the footer.
57
* Reject invalid geome IDs without changing their spelling.

‎docs/CONFIGURATION.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ report for the exact decision.
5252
| `flat_bedrock` | object | Opt-in flat bedrock controls |
5353
| `worldgen_aliases` | ID-to-ID object | Replacement output aliases resolved while baking |
5454

55+
Imported vanilla ore rules obey `manage_vanilla_ores`. With it off, their
56+
settings remain saved but vanilla placement is unchanged. With it on, a valid,
57+
enabled import replaces the matching native default in its eligible dimensions.
58+
Saved Ore Sources placement choices still take precedence. This does not change
59+
custom non-imported ore rules or enable retrogen.
60+
5561
## Enumerated Values
5662

5763
| Setting | Accepted values |

‎docs/PLAYER_GUIDE.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ safely instead of breaking world creation.
4848
region.
4949
- **Manage Vanilla Ores** lets OreSpawn replace vanilla ore features with the
5050
configured OreSpawn rules. Leave it off to keep normal Minecraft placement.
51+
Imported vanilla rules stay saved but inactive while it is off. When it is
52+
on, an active import replaces the matching shipped default rather than
53+
adding another placement pass. Existing chunks are not rewritten.
5154
- **Fluid Deposits** appears after strata are enabled or when a mod or pack
5255
supplies a rule. Press **Add** to choose water, lava, or a fluid block from an
5356
installed mod. These are covered underground deposits, not exposed vanilla

‎gradle/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ responsibility so maintainers can find the relevant task quickly.
1212
| `verification/ore-sources.gradle` | Test-only future MMD providers and missing-provider reloads. |
1313
| `verification/performance.gradle` | Matched world-generation checks with optional sibling mods. |
1414
| `verification/biome-override.gradle` | New-terrain biome replacement and no-retrogen probe. |
15-
| `verification/native-ores.gradle` | Managed Nether quartz, tag/block hosts, reloads and wrapper cleanup. |
15+
| `verification/native-ores.gradle` | Managed and imported Nether quartz, vanilla OFF parity, tag/block hosts, reloads and wrapper cleanup. |
1616
| `release/artifacts.gradle` | Release jars, bundled guides, artifact audits and checksums. |
1717
| `release/publishing.gradle` | Maven coordinates, credentials and opt-in publication. |
1818
| `ide/eclipse.gradle` | Generated launches, Buildship setup and production-classpath isolation. |

‎gradle/verification/native-ores.gradle‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ def packagedJar = jar
2222
def serverRuntime = providers.gradleProperty('packagedForgeServerRuntime')
2323
def comparisonJar = providers.gradleProperty('nativeOreSpawnJar')
2424
def finalTasks = []
25-
['Default', 'Explicit'].each { scenario ->
25+
['Default', 'Explicit', 'Imported', 'OffControl', 'OffImported'].each { scenario ->
26+
boolean imported = scenario in ['Imported', 'OffImported']
27+
boolean managed = !scenario.startsWith('Off')
2628
File runDirectory = file("${buildDir}/native-ore-${scenario.toLowerCase()}-run")
2729
def prepare = tasks.register("prepareNativeOre${scenario}") {
2830
dependsOn shared.releaseJar, packagedJar
@@ -71,6 +73,8 @@ max-tick-time=-1
7173
commandLine shared.forge64ServerCommand(file(serverRuntime.get()), runDirectory,
7274
['-XX:ActiveProcessorCount=2',
7375
"-Dnativeoreprobe.phase=${phase.toLowerCase()}",
76+
"-Dnativeoreprobe.manage=${managed}",
77+
"-Dnativeoreprobe.imported=${imported}",
7478
"-Dnativeoreprobe.host=${scenario.toLowerCase()}"])
7579
}
7680
doLast {
@@ -97,7 +101,7 @@ max-tick-time=-1
97101
}
98102
tasks.register('nativeOreIntegrationTest') {
99103
group = 'verification'
100-
description = 'Checks quartz defaults, explicit hosts, exact-save reload and new-terrain-only edits.'
104+
description = 'Checks quartz defaults and imports, vanilla OFF parity, reloads and new-terrain-only edits.'
101105
dependsOn finalTasks
102106
doLast {
103107
['fresh', 'reload', 'edited'].each { phase ->
@@ -114,5 +118,21 @@ tasks.register('nativeOreIntegrationTest') {
114118
}
115119
}
116120
logger.lifecycle('Tag and explicit quartz hosts produce identical terrain, fresh/reload/edited')
121+
[['default', 'imported'], ['offcontrol', 'offimported']].each { pair ->
122+
['fresh', 'reload', 'edited'].each { phase ->
123+
def audits = pair.collect { scenario ->
124+
Properties result = new Properties()
125+
file("${buildDir}/native-ore-${scenario}-run/audit-logs/${phase}.properties")
126+
.withInputStream { result.load(it) }
127+
result
128+
}
129+
['quartz', 'blocks_sha256', 'biomes_sha256', 'new_terrain_quartz', 'water', 'new_terrain_water'].each { key ->
130+
if (audits[0].getProperty(key) != audits[1].getProperty(key)) {
131+
throw new GradleException("Imported quartz changed ${pair}, ${phase}: ${key}")
132+
}
133+
}
134+
}
135+
}
136+
logger.lifecycle('Imported quartz: OFF matches native terrain; ON matches one configured pass; reload and no-retrogen verified')
117137
}
118138
}

‎src/main/java/zone/moddev/mc/orespawn/worldgen/OreSourcePolicies.java‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,11 +196,12 @@ private static Map<String, Group> discover(JsonObject root) {
196196
for (Entry<String, JsonElement> oreEntry : ores.entrySet()) {
197197
if (!oreEntry.getValue().isJsonObject()) continue;
198198
JsonObject ore = oreEntry.getValue().getAsJsonObject();
199-
boolean oreActive = bool(ore, "enabled", true)
200-
&& (!bool(ore, "native_generation", false) || manageVanillaOres);
201199
Identifier blockId = resource(string(ore, "block", oreEntry.getKey()));
202200
Block block = blockId == null ? null : ForgeRegistries.BLOCKS.getValue(blockId);
203201
if (block == null || block == Blocks.AIR) continue;
202+
boolean vanillaRule = bool(ore, "native_generation", false)
203+
|| OreSpawnOreGeneration.isImportedVanillaOre(resource(oreEntry.getKey()), block, ore);
204+
boolean oreActive = bool(ore, "enabled", true) && (!vanillaRule || manageVanillaOres);
204205
int metadata = integer(ore, "metadata", 0);
205206
Inference inferred = inferMaterial(root, blockTags(block));
206207
Identifier material = resource(string(ore, "material", ""));

‎src/main/java/zone/moddev/mc/orespawn/worldgen/OreSpawnOreGeneration.java‎

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -268,9 +268,12 @@ private static BakedOres bakeOres(JsonObject profile, BakedGeomeConfig config) {
268268
if (ruleId == null) ruleId = oreId;
269269
outputBundles.put(ruleId.toString(), new BakedOutputBundle(
270270
output.defaultBlockState(), deepOutput, deepOutputMaxY, outputs));
271-
if (!oreEnabled || (nativeGeneration && !manageVanillaOres)) continue;
271+
boolean importedVanilla = !nativeGeneration && isImportedVanillaOre(ruleId, output, oreJson);
272+
// Keep imported settings, but let vanilla place its own ores while management is off.
273+
if (!oreEnabled || (!manageVanillaOres && (nativeGeneration || importedVanilla))) continue;
272274
Identifier material = OreSourcePolicies.material(profile, oreJson, output);
273-
BakedOreRule rule = new BakedOreRule(output, manageVanillaOres && suppressVanilla);
275+
BakedOreRule rule = new BakedOreRule(ruleId, output,
276+
manageVanillaOres && (importedVanilla || suppressVanilla), nativeGeneration, importedVanilla);
274277

275278
for (Entry<String, JsonElement> dimensionEntry : dimensions.entrySet()) {
276279
Identifier dimensionId = resource(dimensionEntry.getKey());
@@ -333,10 +336,11 @@ private static BakedOres bakeOres(JsonObject profile, BakedGeomeConfig config) {
333336
rule.selector, dimension);
334337
if (selected != null) {
335338
combined.add(selected);
336-
if (rule.suppressVanilla) suppressed.add(rule.output);
339+
if (rule.suppressVanilla) rule.addSuppressedOutputs(suppressed);
337340
}
338341
}
339342
combined = arbitrate(profile, combined, outputBundles);
343+
combined = preferImportedVanilla(rules, combined);
340344
result.put(dimension, combined.toArray(new BakedOre[combined.size()]));
341345
vanillaOutputs.put(dimension, Collections.unmodifiableSet(suppressed));
342346
}
@@ -345,9 +349,10 @@ private static BakedOres bakeOres(JsonObject profile, BakedGeomeConfig config) {
345349
for (BakedOreRule rule : rules) {
346350
if (rule.selector == null) continue;
347351
selectorList.add(rule.selector);
348-
if (rule.suppressVanilla) selectorOutputs.add(rule.output);
352+
if (rule.suppressVanilla) rule.addSuppressedOutputs(selectorOutputs);
349353
}
350354
selectorList = arbitrate(profile, selectorList, outputBundles);
355+
selectorList = preferImportedVanilla(rules, selectorList);
351356
BakedOre[] selectorResult = selectorList.toArray(new BakedOre[selectorList.size()]);
352357
LOGGER.info("Baked {} OreSpawn-managed ore definitions across {} dimensions",
353358
rules.size(), result.size());
@@ -360,6 +365,35 @@ private static BakedOres bakeOres(JsonObject profile, BakedGeomeConfig config) {
360365
Collections.unmodifiableSet(selectorOutputs));
361366
}
362367

368+
static boolean isImportedVanillaOre(Identifier ruleId, Block output, JsonObject ore) {
369+
return (ruleId != null && ruleId.getPath().startsWith("legacy/")
370+
|| "legacy".equals(string(ore, "source_mod", "")))
371+
&& VanillaOreFeatureGate.vanillaOreFamily(output) != null;
372+
}
373+
374+
private static List<BakedOre> preferImportedVanilla(List<BakedOreRule> rules, List<BakedOre> selected) {
375+
Map<Identifier, BakedOreRule> byId = new HashMap<>();
376+
for (BakedOreRule rule : rules) byId.put(rule.ruleId, rule);
377+
Set<Block> importedFamilies = Collections.newSetFromMap(new IdentityHashMap<>());
378+
for (BakedOre ore : selected) {
379+
BakedOreRule rule = byId.get(ore.ruleId);
380+
if (rule != null && rule.importedVanilla) {
381+
importedFamilies.add(VanillaOreFeatureGate.vanillaOreFamily(rule.output));
382+
}
383+
}
384+
if (importedFamilies.isEmpty()) return selected;
385+
List<BakedOre> result = new ArrayList<>();
386+
for (BakedOre ore : selected) {
387+
BakedOreRule rule = byId.get(ore.ruleId);
388+
// Apply this after Ore Sources so an explicit saved placement choice wins.
389+
if (rule == null || !rule.nativeGeneration
390+
|| !importedFamilies.contains(VanillaOreFeatureGate.vanillaOreFamily(rule.output))) {
391+
result.add(ore);
392+
}
393+
}
394+
return result;
395+
}
396+
363397
private static List<BakedOre> arbitrate(JsonObject profile, List<BakedOre> original,
364398
Map<String, BakedOutputBundle> outputBundles) {
365399
if (!profile.has(OreSourcePolicies.SECTION)
@@ -842,15 +876,27 @@ boolean acceptsBiome(ResourceKey<Biome> biome) {
842876
}
843877

844878
private static final class BakedOreRule {
879+
final Identifier ruleId;
845880
final Block output;
846881
final boolean suppressVanilla;
882+
final boolean nativeGeneration;
883+
final boolean importedVanilla;
847884
final Map<ResourceKey<Level>, BakedOre> explicit = new HashMap<>();
848885
final Set<ResourceKey<Level>> explicitDimensions = new HashSet<>();
849886
BakedOre selector;
850887

851-
BakedOreRule(Block output, boolean suppressVanilla) {
888+
BakedOreRule(Identifier ruleId, Block output, boolean suppressVanilla,
889+
boolean nativeGeneration, boolean importedVanilla) {
890+
this.ruleId = ruleId;
852891
this.output = output;
853892
this.suppressVanilla = suppressVanilla;
893+
this.nativeGeneration = nativeGeneration;
894+
this.importedVanilla = importedVanilla;
895+
}
896+
897+
void addSuppressedOutputs(Set<Block> outputs) {
898+
outputs.add(output);
899+
if (importedVanilla) outputs.add(VanillaOreFeatureGate.vanillaOreFamily(output));
854900
}
855901
}
856902

‎src/main/java/zone/moddev/mc/orespawn/worldgen/VanillaOreFeatureGate.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ static Block nativeOutput(ConfiguredFeature<?, ?> feature) {
139139
return output;
140140
}
141141

142-
private static Block vanillaOreFamily(Block block) {
142+
static Block vanillaOreFamily(Block block) {
143143
// Stone and deepslate variants share one managed placement rule.
144144
if (block == Blocks.COAL_ORE || block == Blocks.DEEPSLATE_COAL_ORE) return Blocks.COAL_ORE;
145145
if (block == Blocks.IRON_ORE || block == Blocks.DEEPSLATE_IRON_ORE) return Blocks.IRON_ORE;

‎src/nativeOreIntegrationTest/java/zone/moddev/mc/orespawn/testmod/NativeOreProbeBridge.java‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,30 @@ static void verifyStoppedCaches() {
3838
}
3939
}
4040

41+
static void verifyQuartzPasses(boolean managed, boolean imported) {
42+
try {
43+
Class<?> generation = Class.forName("zone.moddev.mc.orespawn.worldgen.OreSpawnOreGeneration");
44+
Field cache = generation.getDeclaredField("oresByDimension");
45+
cache.setAccessible(true);
46+
Object[] ores = (Object[]) ((Map<?, ?>) cache.get(null)).get(Level.NETHER);
47+
int count = ores == null ? 0 : ores.length;
48+
if (count != (managed ? 1 : 0)) {
49+
throw new IllegalStateException("Unexpected managed quartz pass count: " + count);
50+
}
51+
if (managed) {
52+
Field id = ores[0].getClass().getDeclaredField("ruleId");
53+
id.setAccessible(true);
54+
String expected = imported ? "orespawn:legacy/quartz" : "minecraft:nether_quartz_ore";
55+
if (!expected.equals(id.get(ores[0]).toString())) {
56+
throw new IllegalStateException("Wrong quartz placement source: " + id.get(ores[0]));
57+
}
58+
}
59+
System.out.println("NATIVE_ORE_PROBE managed=" + managed + " imported=" + imported + " passes=" + count);
60+
} catch (ReflectiveOperationException failure) {
61+
throw new IllegalStateException("Could not inspect imported quartz placement", failure);
62+
}
63+
}
64+
4165
static boolean controlsFluid() {
4266
try {
4367
Class<?> generation = Class.forName("zone.moddev.mc.orespawn.worldgen.FluidDepositFeature");

‎src/nativeOreIntegrationTest/java/zone/moddev/mc/orespawn/testmod/NativeOreProbeTestMod.java‎

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ private void prepare(ServerAboutToStartEvent event) {
7171
JsonObject onlyQuartz = new JsonObject();
7272
onlyQuartz.add(QUARTZ, ores.get(QUARTZ));
7373
root.add("ores", onlyQuartz);
74-
root.addProperty("manage_vanilla_ores", true);
74+
root.addProperty("manage_vanilla_ores", managed());
7575
root.addProperty("place_fluid_deposits", true);
7676
root.add("fluid_deposits", fluidProfile());
7777
root.add("terrain_dimensions", new JsonObject());
@@ -82,7 +82,16 @@ private void prepare(ServerAboutToStartEvent event) {
8282
retrogen.addProperty("revision", 0);
8383
root.add("retrogen", retrogen);
8484
}
85-
JsonObject rule = root.getAsJsonObject("ores").getAsJsonObject(QUARTZ)
85+
JsonObject ores = root.getAsJsonObject("ores");
86+
if (imported() && "fresh".equals(phase)) {
87+
JsonObject legacy = ores.getAsJsonObject(QUARTZ).deepCopy();
88+
legacy.addProperty("block", QUARTZ);
89+
legacy.addProperty("source_mod", "legacy");
90+
legacy.addProperty("native_generation", false);
91+
legacy.addProperty("retrogen", false);
92+
ores.add("orespawn:legacy/quartz", legacy);
93+
}
94+
JsonObject rule = ores.getAsJsonObject(imported() ? "orespawn:legacy/quartz" : QUARTZ)
8695
.getAsJsonObject("dimensions").getAsJsonObject(NETHER);
8796
rule.addProperty("min_y", "edited".equals(phase) ? 100 : 24);
8897
rule.addProperty("max_y", "edited".equals(phase) ? 104 : 28);
@@ -114,9 +123,10 @@ private void prepare(ServerAboutToStartEvent event) {
114123
private void audit(ServerStartedEvent event) {
115124
MinecraftServer server = event.getServer();
116125
String phase = System.getProperty("nativeoreprobe.phase", "fresh");
117-
if (!NativeOreProbeBridge.controlsQuartz()) {
118-
throw new IllegalStateException("Quartz is not controlled by OreSpawn before generation");
126+
if (NativeOreProbeBridge.controlsQuartz() != managed()) {
127+
throw new IllegalStateException("Quartz takeover disagrees with Manage Vanilla Ores");
119128
}
129+
NativeOreProbeBridge.verifyQuartzPasses(managed(), imported());
120130
if (!NativeOreProbeBridge.controlsFluid()) {
121131
throw new IllegalStateException("Tag-only fluid was not baked before generation");
122132
}
@@ -183,7 +193,9 @@ private static Properties inspect(ServerLevel world, int origin, int minY, int m
183193
blocks.update((byte) id);
184194
if (state.getBlock() == Blocks.WATER) water++;
185195
if (state.getBlock() == Blocks.NETHER_QUARTZ_ORE) {
186-
if (y < minY || y > maxY) throw new IllegalStateException("Quartz outside managed range at " + cursor);
196+
if (managed() && (y < minY || y > maxY)) {
197+
throw new IllegalStateException("Quartz outside managed range at " + cursor);
198+
}
187199
quartz++;
188200
}
189201
}
@@ -201,6 +213,14 @@ private static Properties inspect(ServerLevel world, int origin, int minY, int m
201213
return result;
202214
}
203215

216+
private static boolean managed() {
217+
return Boolean.parseBoolean(System.getProperty("nativeoreprobe.manage", "true"));
218+
}
219+
220+
private static boolean imported() {
221+
return Boolean.getBoolean("nativeoreprobe.imported");
222+
}
223+
204224
private static JsonObject fluidProfile() {
205225
JsonObject rule = new JsonObject();
206226
rule.addProperty("enabled", true);

0 commit comments

Comments
 (0)