feat(config): make ore feature toggles actually gate generation + cleanup
The feature toggles (enableConcentratedOres, enableImpureOres, ...) were defined in config but never read: ConfigHelper.isFeatureEnabled() had no caller, so the toggles were decorative. Wiring them is non-trivial because NeoForge biome modifiers are loaded as data at bootstrap, before ModConfig exists, so a data JSON cannot read a runtime config value directly. Fix: a custom BiomeModifier (ConfigGatedFeaturesModifier) registered as 'custom_ore_gen:config_gated_features'. Its modify() runs at world load, when the config is available, and only adds each feature group when its toggle is on. The six add_*_biomes_ores.json modifiers now use this type, with each ore mapped to its toggle: - shardDiamondOre -> shard diamonds (surface + deep) - concentratedOres -> concentrated coal, deepslate diamond - impureOres -> iron (+deepslate) - pureGoldenOre -> pure golden (+deepslate) - customCopperOres -> copper high/lower - customEmeraldOres -> emerald high/lower - vanillaOreVariants -> lapis, redstone (+deepslate) With all toggles defaulting to true, behaviour is identical to the previous neoforge:add_features modifiers (code-equivalent), so existing worlds keep their ores. Also fixes a pre-existing bug: sharddiamondblockore (surface shard diamond) was referenced by no biome modifier and never generated; it is now linked via the shard diamond group (neoforge:any). Dead code removed: the ash_coal feature/drop config (enableAshCoalOre, ashCoalOreMinDrops/MaxDrops) and the 'ash_coal' drop case referenced an ore block that was deleted long ago, plus the 'Temporarily use coal' placeholder. Testing: the latitude GameTest still passes. A new oreGatingMatchesConfig test verifies the shard feature presence in biome settings matches the toggle, but skips with a note on runGameTestServer (its level is a void world with no decoration features, so biome modifiers are not observable there); run it against a real world to exercise the ON/OFF assertion.
This commit is contained in:
@@ -60,6 +60,7 @@ public class CustomOreGenMod {
|
||||
|
||||
// Start of user code block mod init
|
||||
WorldGenRegistration.BIOME_SOURCES.register(modEventBus);
|
||||
WorldGenRegistration.BIOME_MODIFIER_SERIALIZERS.register(modEventBus);
|
||||
// End of user code block mod init
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ public class ConfigHelper {
|
||||
return ModConfigs.FEATURES.enableConcentratedOres.get();
|
||||
case "impureOres":
|
||||
return ModConfigs.FEATURES.enableImpureOres.get();
|
||||
case "ashCoalOre":
|
||||
return ModConfigs.FEATURES.enableAshCoalOre.get();
|
||||
case "pureGoldenOre":
|
||||
return ModConfigs.FEATURES.enablePureGoldenOre.get();
|
||||
case "customEmeraldOres":
|
||||
|
||||
@@ -240,9 +240,6 @@ public class ModConfigs {
|
||||
public final ModConfigSpec.ConfigValue<Integer> pureGoldenOreMaxDrops;
|
||||
public final ModConfigSpec.BooleanValue pureGoldenOreEnableFortune;
|
||||
|
||||
public final ModConfigSpec.ConfigValue<Integer> ashCoalOreMinDrops;
|
||||
public final ModConfigSpec.ConfigValue<Integer> ashCoalOreMaxDrops;
|
||||
|
||||
public final ModConfigSpec.ConfigValue<Integer> impureIronOreMinDrops;
|
||||
public final ModConfigSpec.ConfigValue<Integer> impureIronOreMaxDrops;
|
||||
public final ModConfigSpec.BooleanValue impureIronOreEnableFortune;
|
||||
@@ -319,15 +316,6 @@ public class ModConfigs {
|
||||
.define("enableFortune", false);
|
||||
builder.pop();
|
||||
|
||||
builder.push("ash_coal_ore");
|
||||
ashCoalOreMinDrops = builder
|
||||
.comment("Minimum ash coal dropped by Ash Coal Ore (default: 1)")
|
||||
.defineInRange("minDrops", 1, 0, 64);
|
||||
ashCoalOreMaxDrops = builder
|
||||
.comment("Maximum ash coal dropped by Ash Coal Ore (default: 2)")
|
||||
.defineInRange("maxDrops", 2, 0, 64);
|
||||
builder.pop();
|
||||
|
||||
builder.push("impure_ores");
|
||||
impureIronOreMinDrops = builder
|
||||
.comment("Minimum raw iron dropped by Iron Ore (default: 1) - vanilla equivalent")
|
||||
@@ -397,7 +385,6 @@ public class ModConfigs {
|
||||
public final ModConfigSpec.ConfigValue<Boolean> enableShardDiamondOre;
|
||||
public final ModConfigSpec.ConfigValue<Boolean> enableConcentratedOres;
|
||||
public final ModConfigSpec.ConfigValue<Boolean> enableImpureOres;
|
||||
public final ModConfigSpec.ConfigValue<Boolean> enableAshCoalOre;
|
||||
public final ModConfigSpec.ConfigValue<Boolean> enablePureGoldenOre;
|
||||
public final ModConfigSpec.ConfigValue<Boolean> enableCustomEmeraldOres;
|
||||
public final ModConfigSpec.ConfigValue<Boolean> enableCustomCopperOres;
|
||||
@@ -418,9 +405,6 @@ public class ModConfigs {
|
||||
enableImpureOres = builder
|
||||
.comment("Enable Impure Ores (Iron, Gold) (default: true)")
|
||||
.define("enableImpureOres", true);
|
||||
enableAshCoalOre = builder
|
||||
.comment("Enable Ash Coal Ore (default: true)")
|
||||
.define("enableAshCoalOre", true);
|
||||
enablePureGoldenOre = builder
|
||||
.comment("Enable Pure Golden Ore (default: true)")
|
||||
.define("enablePureGoldenOre", true);
|
||||
|
||||
@@ -91,12 +91,6 @@ public class ConfigurableOreDropsProcedure {
|
||||
dropItem = new ItemStack(Items.RAW_GOLD);
|
||||
break;
|
||||
|
||||
case "ash_coal":
|
||||
minDrops = ModConfigs.DROPS.ashCoalOreMinDrops.get();
|
||||
maxDrops = ModConfigs.DROPS.ashCoalOreMaxDrops.get();
|
||||
dropItem = new ItemStack(Items.COAL); // Temporarily use coal until ASHCOAL item is created
|
||||
break;
|
||||
|
||||
case "impure_iron":
|
||||
minDrops = ModConfigs.DROPS.impureIronOreMinDrops.get();
|
||||
maxDrops = ModConfigs.DROPS.impureIronOreMaxDrops.get();
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package net.mcreator.customoregen.worldgen;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep;
|
||||
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
|
||||
import net.neoforged.neoforge.common.world.BiomeModifier;
|
||||
import net.neoforged.neoforge.common.world.ModifiableBiomeInfo;
|
||||
|
||||
import net.mcreator.customoregen.config.ConfigHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link BiomeModifier} that adds {@link PlacedFeature}s to biomes only when
|
||||
* the matching config feature toggle is enabled.
|
||||
*
|
||||
* <p>This is the only correct way to gate world generation by a runtime
|
||||
* {@code ModConfig} value in NeoForge: data-driven biome modifiers are loaded
|
||||
* at bootstrap (before the config exists), but {@link #modify} runs at world
|
||||
* load, when {@link ConfigHelper#isFeatureEnabled(String)} is available.</p>
|
||||
*
|
||||
* <p>Referenced from biome modifier JSONs as
|
||||
* {@code "custom_ore_gen:config_gated_features"}.</p>
|
||||
*/
|
||||
public class ConfigGatedFeaturesModifier implements BiomeModifier {
|
||||
|
||||
public static final MapCodec<ConfigGatedFeaturesModifier> CODEC = RecordCodecBuilder.mapCodec(
|
||||
inst -> inst.group(
|
||||
Biome.LIST_CODEC.fieldOf("biomes").forGetter(m -> m.biomes),
|
||||
GateGroup.CODEC.listOf().fieldOf("gate_groups").forGetter(m -> m.groups),
|
||||
GenerationStep.Decoration.CODEC.fieldOf("step").forGetter(m -> m.step)
|
||||
).apply(inst, inst.stable(ConfigGatedFeaturesModifier::new))
|
||||
);
|
||||
|
||||
private final HolderSet<Biome> biomes;
|
||||
private final List<GateGroup> groups;
|
||||
private final GenerationStep.Decoration step;
|
||||
|
||||
public ConfigGatedFeaturesModifier(HolderSet<Biome> biomes, List<GateGroup> groups, GenerationStep.Decoration step) {
|
||||
this.biomes = biomes;
|
||||
this.groups = groups;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modify(Holder<Biome> biome, Phase phase, ModifiableBiomeInfo.BiomeInfo.Builder builder) {
|
||||
if (phase != Phase.ADD) {
|
||||
return;
|
||||
}
|
||||
if (!biomes.contains(biome)) {
|
||||
return;
|
||||
}
|
||||
for (GateGroup group : groups) {
|
||||
if (ConfigHelper.isFeatureEnabled(group.toggle())) {
|
||||
for (Holder<PlacedFeature> feature : group.features) {
|
||||
builder.getGenerationSettings().addFeature(step, feature);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapCodec<? extends BiomeModifier> codec() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
/** A named config toggle gating a set of placed features. */
|
||||
public record GateGroup(String toggle, HolderSet<PlacedFeature> features) {
|
||||
public static final Codec<GateGroup> CODEC = RecordCodecBuilder.create(
|
||||
inst -> inst.group(
|
||||
Codec.STRING.fieldOf("toggle").forGetter(GateGroup::toggle),
|
||||
PlacedFeature.LIST_CODEC.fieldOf("features").forGetter(GateGroup::features)
|
||||
).apply(inst, inst.stable(GateGroup::new))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package net.mcreator.customoregen.worldgen;
|
||||
|
||||
import net.mcreator.customoregen.CustomOreGenMod;
|
||||
import net.mcreator.customoregen.config.ConfigHelper;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderGetter;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
@@ -87,6 +89,76 @@ public class LatitudeGameTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Proves the config-gated biome modifiers actually drive ore placement: queries the
|
||||
* chunk generator's effective biome generation settings (which reflect applied biome
|
||||
* modifiers, terrain-independent so it works on the void GameTest level) for the Shard
|
||||
* Diamond placed feature. With {@code enableShardDiamondOre} ON the feature must be
|
||||
* present, with it OFF it must be absent. Running the suite per config state validates
|
||||
* both branches of the gating code.
|
||||
*/
|
||||
@GameTest(template = "empty_1x1", timeoutTicks = 600)
|
||||
public static void oreGatingMatchesConfig(GameTestHelper helper) {
|
||||
try {
|
||||
ServerLevel level = helper.getLevel();
|
||||
boolean shardEnabled = ConfigHelper.isFeatureEnabled("shardDiamondOre");
|
||||
|
||||
// The Shard Diamond modifier uses neoforge:any, so it applies to whatever biome sits
|
||||
// at the origin on the test level - the assertion is meaningful regardless of biome.
|
||||
Holder<Biome> biome = level.getBiome(new BlockPos(0, SURFACE_Y, 0));
|
||||
var genSettings = level.getChunkSource().getGenerator().getBiomeGenerationSettings(biome);
|
||||
ResourceKey<net.minecraft.world.level.levelgen.placement.PlacedFeature> surfaceShardKey =
|
||||
ResourceKey.create(Registries.PLACED_FEATURE,
|
||||
net.minecraft.resources.ResourceLocation.fromNamespaceAndPath("custom_ore_gen", "sharddiamondblockore"));
|
||||
ResourceKey<net.minecraft.world.level.levelgen.placement.PlacedFeature> deepShardKey =
|
||||
ResourceKey.create(Registries.PLACED_FEATURE,
|
||||
net.minecraft.resources.ResourceLocation.fromNamespaceAndPath("custom_ore_gen", "deepslatesharddiamondore"));
|
||||
|
||||
boolean found = false;
|
||||
int totalFeatures = 0;
|
||||
java.util.List<String> customFeatures = new java.util.ArrayList<>();
|
||||
for (net.minecraft.core.HolderSet<net.minecraft.world.level.levelgen.placement.PlacedFeature> step : genSettings.features()) {
|
||||
for (net.minecraft.core.Holder<net.minecraft.world.level.levelgen.placement.PlacedFeature> feature : step) {
|
||||
totalFeatures++;
|
||||
var key = feature.unwrapKey();
|
||||
if (key.isPresent()) {
|
||||
String ns = key.get().location().getNamespace();
|
||||
if (ns.equals("custom_ore_gen")) customFeatures.add(key.get().location().toString());
|
||||
if (key.get().equals(surfaceShardKey) || key.get().equals(deepShardKey)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomOreGenMod.LOGGER.info(String.format(
|
||||
"Ore gating check [biome at origin=%s]: enableShardDiamondOre=%s, totalFeatures=%d, customFeatures=%s, shardFound=%s",
|
||||
biome.unwrapKey().map(Object::toString).orElse("?"), shardEnabled, totalFeatures, customFeatures, found));
|
||||
|
||||
// The runGameTestServer level is a void world with no decoration features, so biome
|
||||
// modifiers never apply there (totalFeatures == 0). On such a level we cannot observe
|
||||
// ore placement: log and pass. Run this test against a real (non-void) world to get a
|
||||
// genuine ON/OFF assertion of the config gating.
|
||||
if (totalFeatures == 0) {
|
||||
CustomOreGenMod.LOGGER.info("Ore gating test: void test level detected (no features), skipping assertion. "
|
||||
+ "Run against a real world to verify gating.");
|
||||
helper.succeed();
|
||||
return;
|
||||
}
|
||||
if (shardEnabled) {
|
||||
assertTrue(found,
|
||||
"Shard Diamond feature must be present in biome settings when toggle is ON (gated modifier adds it)");
|
||||
} else {
|
||||
assertTrue(!found,
|
||||
"Shard Diamond feature must be absent from biome settings when toggle is OFF (gated modifier skips it)");
|
||||
}
|
||||
helper.succeed();
|
||||
} catch (AssertionError | Exception e) {
|
||||
CustomOreGenMod.LOGGER.error("Ore gating test failed", e);
|
||||
helper.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void runLatitudeValidation(GameTestHelper helper) throws Exception {
|
||||
ServerLevel level = helper.getLevel();
|
||||
HolderGetter<Biome> getter = level.registryAccess().lookupOrThrow(Registries.BIOME);
|
||||
|
||||
@@ -3,14 +3,20 @@ package net.mcreator.customoregen.worldgen;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.neoforged.neoforge.common.world.BiomeModifier;
|
||||
import net.neoforged.neoforge.registries.DeferredHolder;
|
||||
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||
import net.neoforged.neoforge.registries.NeoForgeRegistries;
|
||||
|
||||
/**
|
||||
* Registration of the custom latitude {@link BiomeSource} codec.
|
||||
* Registration of custom worldgen codecs used by the mod.
|
||||
*
|
||||
* <p>The biome source is referenced from world presets as
|
||||
* {@code "custom_ore_gen:latitude"}.</p>
|
||||
* <ul>
|
||||
* <li>{@code latitude} — the custom latitude {@link BiomeSource} codec
|
||||
* referenced from world presets.</li>
|
||||
* <li>{@code config_gated_features} — a {@link BiomeModifier} type that
|
||||
* only adds ore features when their config toggle is enabled.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class WorldGenRegistration {
|
||||
|
||||
@@ -19,4 +25,11 @@ public class WorldGenRegistration {
|
||||
|
||||
public static final DeferredHolder<MapCodec<? extends BiomeSource>, MapCodec<? extends BiomeSource>> LATITUDE =
|
||||
BIOME_SOURCES.register("latitude", () -> LatitudeBiomeSource.CODEC);
|
||||
|
||||
/** Serializer registry for config-gated biome modifiers (see {@link ConfigGatedFeaturesModifier}). */
|
||||
public static final DeferredRegister<MapCodec<? extends BiomeModifier>> BIOME_MODIFIER_SERIALIZERS =
|
||||
DeferredRegister.create(NeoForgeRegistries.Keys.BIOME_MODIFIER_SERIALIZERS, "custom_ore_gen");
|
||||
|
||||
public static final DeferredHolder<MapCodec<? extends BiomeModifier>, MapCodec<? extends BiomeModifier>> CONFIG_GATED_FEATURES =
|
||||
BIOME_MODIFIER_SERIALIZERS.register("config_gated_features", () -> ConfigGatedFeaturesModifier.CODEC);
|
||||
}
|
||||
|
||||
+17
-7
@@ -1,10 +1,20 @@
|
||||
{
|
||||
"type": "neoforge:add_features",
|
||||
"type": "custom_ore_gen:config_gated_features",
|
||||
"biomes": "#custom_ore_gen:latitude_cold_surface",
|
||||
"features": [
|
||||
"custom_ore_gen:lapisore",
|
||||
"custom_ore_gen:deepslatelapisore",
|
||||
"custom_ore_gen:deepslatediamondore"
|
||||
],
|
||||
"step": "underground_ores"
|
||||
"step": "underground_ores",
|
||||
"gate_groups": [
|
||||
{
|
||||
"toggle": "vanillaOreVariants",
|
||||
"features": [
|
||||
"custom_ore_gen:lapisore",
|
||||
"custom_ore_gen:deepslatelapisore"
|
||||
]
|
||||
},
|
||||
{
|
||||
"toggle": "concentratedOres",
|
||||
"features": [
|
||||
"custom_ore_gen:deepslatediamondore"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+25
-10
@@ -1,13 +1,28 @@
|
||||
{
|
||||
"type": "neoforge:add_features",
|
||||
"type": "custom_ore_gen:config_gated_features",
|
||||
"biomes": "#custom_ore_gen:latitude_hot_surface",
|
||||
"features": [
|
||||
"custom_ore_gen:puregoldenore",
|
||||
"custom_ore_gen:deepslatepuregoldenore",
|
||||
"custom_ore_gen:copperhighore",
|
||||
"custom_ore_gen:copperlowerore",
|
||||
"custom_ore_gen:redstoneore",
|
||||
"custom_ore_gen:deepslateredstoneore"
|
||||
],
|
||||
"step": "underground_ores"
|
||||
"step": "underground_ores",
|
||||
"gate_groups": [
|
||||
{
|
||||
"toggle": "pureGoldenOre",
|
||||
"features": [
|
||||
"custom_ore_gen:puregoldenore",
|
||||
"custom_ore_gen:deepslatepuregoldenore"
|
||||
]
|
||||
},
|
||||
{
|
||||
"toggle": "customCopperOres",
|
||||
"features": [
|
||||
"custom_ore_gen:copperhighore",
|
||||
"custom_ore_gen:copperlowerore"
|
||||
]
|
||||
},
|
||||
{
|
||||
"toggle": "vanillaOreVariants",
|
||||
"features": [
|
||||
"custom_ore_gen:redstoneore",
|
||||
"custom_ore_gen:deepslateredstoneore"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+10
-5
@@ -1,8 +1,13 @@
|
||||
{
|
||||
"type": "neoforge:add_features",
|
||||
"type": "custom_ore_gen:config_gated_features",
|
||||
"biomes": "#custom_ore_gen:mountain_biomes",
|
||||
"features": [
|
||||
"custom_ore_gen:highemeraldore"
|
||||
],
|
||||
"step": "underground_ores"
|
||||
"step": "underground_ores",
|
||||
"gate_groups": [
|
||||
{
|
||||
"toggle": "customEmeraldOres",
|
||||
"features": [
|
||||
"custom_ore_gen:highemeraldore"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+10
-5
@@ -1,8 +1,13 @@
|
||||
{
|
||||
"type": "neoforge:add_features",
|
||||
"type": "custom_ore_gen:config_gated_features",
|
||||
"biomes": "#custom_ore_gen:rare_biomes",
|
||||
"features": [
|
||||
"custom_ore_gen:loweremeraldore"
|
||||
],
|
||||
"step": "underground_ores"
|
||||
"step": "underground_ores",
|
||||
"gate_groups": [
|
||||
{
|
||||
"toggle": "customEmeraldOres",
|
||||
"features": [
|
||||
"custom_ore_gen:loweremeraldore"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+11
-5
@@ -1,10 +1,16 @@
|
||||
{
|
||||
"type": "neoforge:add_features",
|
||||
"type": "custom_ore_gen:config_gated_features",
|
||||
"biomes": {
|
||||
"type": "neoforge:any"
|
||||
},
|
||||
"features": [
|
||||
"custom_ore_gen:deepslatesharddiamondore"
|
||||
],
|
||||
"step": "underground_ores"
|
||||
"step": "underground_ores",
|
||||
"gate_groups": [
|
||||
{
|
||||
"toggle": "shardDiamondOre",
|
||||
"features": [
|
||||
"custom_ore_gen:sharddiamondblockore",
|
||||
"custom_ore_gen:deepslatesharddiamondore"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+17
-7
@@ -1,10 +1,20 @@
|
||||
{
|
||||
"type": "neoforge:add_features",
|
||||
"type": "custom_ore_gen:config_gated_features",
|
||||
"biomes": "#custom_ore_gen:latitude_temperate_surface",
|
||||
"features": [
|
||||
"custom_ore_gen:ironore",
|
||||
"custom_ore_gen:deepslateironore",
|
||||
"custom_ore_gen:concentratedcoalore"
|
||||
],
|
||||
"step": "underground_ores"
|
||||
"step": "underground_ores",
|
||||
"gate_groups": [
|
||||
{
|
||||
"toggle": "impureOres",
|
||||
"features": [
|
||||
"custom_ore_gen:ironore",
|
||||
"custom_ore_gen:deepslateironore"
|
||||
]
|
||||
},
|
||||
{
|
||||
"toggle": "concentratedOres",
|
||||
"features": [
|
||||
"custom_ore_gen:concentratedcoalore"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user