Created
February 20, 2022 17:18
-
-
Save Commoble/573ac69ac95818daf643d443bf67b260 to your computer and use it in GitHub Desktop.
Adding JSON placedfeatures to vanilla biomes from the BiomeLoadingEvent in Forge for Minecraft 1.18.1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
data/sandbox/worldgen/configured_feature/red_wool.json | |
{ | |
"config": | |
{ | |
"state": | |
{ | |
"Name": "minecraft:red_wool" | |
} | |
}, | |
"type": "sandbox:single_block" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
data/sandbox/worldgen/placed_feature/red_wool.json | |
{ | |
"feature": "sandbox:red_wool", | |
"placement": | |
[ | |
{ | |
"type": "minecraft:in_square" | |
}, | |
{ | |
"heightmap": "MOTION_BLOCKING", | |
"type": "minecraft:heightmap" | |
}, | |
{ | |
"type": "sandbox:biome_category", | |
"category": "desert" | |
} | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package commoble.sandbox; | |
import com.mojang.serialization.Codec; | |
import commoble.sandbox.LazyFeature.LazyFeatureConfig; | |
import net.minecraft.core.Registry; | |
import net.minecraft.resources.ResourceKey; | |
import net.minecraft.world.level.WorldGenLevel; | |
import net.minecraft.world.level.levelgen.feature.Feature; | |
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; | |
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; | |
import net.minecraft.world.level.levelgen.placement.PlacedFeature; | |
/** | |
* Feature that delegates to another placed feature at runtime. | |
* The delegate feature can be defined in json, even if this | |
* feature is added to biomes via BiomeLoadingEvent. | |
* | |
* When used in this manner, the configured and placed lazy features | |
* should be registered to BuiltinRegistries in FMLCommonSetupEvent#enqueueWork, | |
* and have different IDs than the json feature. | |
* | |
* This has three caveats: | |
* | |
* 1) The vanilla BiomeFilter placement modifier doesn't work here, as the | |
* delegate feature doesn't actually belong to any biome. This can be averted by | |
* adding the lazy feature to every biome and using a custom placement modifier | |
* that uses some kind of biome filtering. This sounds bad but it's very similar to | |
* what vanilla already does, which is to run every PlacedFeature that can possibly generate | |
* in the dimension and have each PlacedFeature decide whether it's in the correct biome | |
* after determining where to place itself. This can be further emulated by adding dimension | |
* filtering to the json placedfeature. | |
* | |
* 2) The json features cannot be added to a biome's flower features in this way, which would | |
* be used to determine what flower feature can generate by using bone meal on a grass block. | |
* However, the flower feature system isn't very helpful for mods in any case, as only the first | |
* flower feature added to a biome is used by bonemeal in this way. Custom JSON biomes should | |
* include their flower feature (if any) in the biome json's list of features; mods adding | |
* features to biomes can use forge's bonemeal events. | |
* | |
* 3) Json validation is difficult; the lazy feature's delegate's ID cannot be verified as | |
* a valid, existing feature until after dynamic registries (biomes and features) finish | |
* loading. This implementation simply NPEs during worldgen if no json for the delegate | |
* feature exists; mods that use lazy features should supply their own placed feature jsons. | |
*/ | |
public class LazyFeature extends Feature<LazyFeatureConfig> | |
{ | |
public LazyFeature(Codec<LazyFeatureConfig> codec) | |
{ | |
super(codec); | |
} | |
@Override | |
public boolean place(FeaturePlaceContext<LazyFeatureConfig> context) | |
{ | |
// delegate to our delegate feature | |
WorldGenLevel level = context.level(); | |
ResourceKey<PlacedFeature> feature = context.config().feature(); | |
return level.registryAccess() | |
.registryOrThrow(Registry.PLACED_FEATURE_REGISTRY) | |
.get(feature) | |
.place(level, context.chunkGenerator(), context.random(), context.origin()); | |
} | |
public static record LazyFeatureConfig(ResourceKey<PlacedFeature> feature) implements FeatureConfiguration | |
{ | |
public static final Codec<LazyFeatureConfig> CODEC = ResourceKey.codec(Registry.PLACED_FEATURE_REGISTRY) | |
.xmap(LazyFeatureConfig::new, LazyFeatureConfig::feature) | |
.fieldOf("feature") | |
.codec(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package commoble.sandbox; | |
import java.util.Random; | |
import java.util.function.Consumer; | |
import java.util.function.Supplier; | |
import java.util.stream.Stream; | |
import com.mojang.serialization.Codec; | |
import commoble.sandbox.LazyFeature.LazyFeatureConfig; | |
import net.minecraft.core.BlockPos; | |
import net.minecraft.core.Registry; | |
import net.minecraft.data.BuiltinRegistries; | |
import net.minecraft.resources.ResourceKey; | |
import net.minecraft.resources.ResourceLocation; | |
import net.minecraft.world.level.WorldGenLevel; | |
import net.minecraft.world.level.biome.Biome.BiomeCategory; | |
import net.minecraft.world.level.levelgen.GenerationStep.Decoration; | |
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; | |
import net.minecraft.world.level.levelgen.feature.Feature; | |
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; | |
import net.minecraft.world.level.levelgen.feature.configurations.BlockStateConfiguration; | |
import net.minecraft.world.level.levelgen.placement.BiomeFilter; | |
import net.minecraft.world.level.levelgen.placement.PlacedFeature; | |
import net.minecraft.world.level.levelgen.placement.PlacementContext; | |
import net.minecraft.world.level.levelgen.placement.PlacementFilter; | |
import net.minecraft.world.level.levelgen.placement.PlacementModifier; | |
import net.minecraft.world.level.levelgen.placement.PlacementModifierType; | |
import net.minecraftforge.common.MinecraftForge; | |
import net.minecraftforge.common.util.Lazy; | |
import net.minecraftforge.event.world.BiomeLoadingEvent; | |
import net.minecraftforge.eventbus.api.IEventBus; | |
import net.minecraftforge.fml.common.Mod; | |
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; | |
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; | |
import net.minecraftforge.registries.DeferredRegister; | |
import net.minecraftforge.registries.ForgeRegistries; | |
import net.minecraftforge.registries.RegistryObject; | |
@Mod(Sandbox.MODID) | |
public class Sandbox | |
{ | |
public static final String MODID = "sandbox"; | |
public static final ResourceLocation BIOME_CATEGORY_PLACEMENT = new ResourceLocation(MODID, "biome_category"); | |
public static final ResourceLocation RED_WOOL = new ResourceLocation(MODID, "red_wool"); | |
public static final ResourceLocation LAZY_RED_WOOL = new ResourceLocation(MODID, "lazy_red_wool"); | |
public static final ResourceKey<ConfiguredFeature<?,?>> CONFIGURED_RED_WOOL = ResourceKey.create(Registry.CONFIGURED_FEATURE_REGISTRY, RED_WOOL); | |
public static final ResourceKey<PlacedFeature> PLACED_RED_WOOL = ResourceKey.create(Registry.PLACED_FEATURE_REGISTRY, RED_WOOL); | |
public Sandbox() // invoked by forge due to @Mod | |
{ | |
IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus(); | |
IEventBus forgeBus = MinecraftForge.EVENT_BUS; | |
DeferredRegister<Feature<?>> features = DeferredRegister.create(ForgeRegistries.FEATURES, MODID); | |
features.register(modBus); | |
// block feature for testing, used in a json | |
features.register("single_block", () -> new SingleBlockFeature(BlockStateConfiguration.CODEC)); | |
// lazy feature that delegates to a json | |
RegistryObject<LazyFeature> lazyFeature = features.register("lazy", () -> new LazyFeature(LazyFeature.LazyFeatureConfig.CODEC)); | |
// don't register configured/placed features until common setup | |
Supplier<ConfiguredFeature<?,?>> configuredRedWool = Lazy.of(() -> BuiltinRegistries.register(BuiltinRegistries.CONFIGURED_FEATURE, | |
LAZY_RED_WOOL, | |
lazyFeature.get().configured(new LazyFeatureConfig(PLACED_RED_WOOL)))); | |
Supplier<PlacedFeature> placedRedWool = Lazy.of(() -> BuiltinRegistries.register(BuiltinRegistries.PLACED_FEATURE, | |
LAZY_RED_WOOL, | |
configuredRedWool.get().placed(BiomeFilter.biome()))); | |
Consumer<FMLCommonSetupEvent> onCommonSetup = event -> | |
{ | |
event.enqueueWork(() -> | |
{ | |
// register to vanilla Registries | |
Registry.register(Registry.PLACEMENT_MODIFIERS, BIOME_CATEGORY_PLACEMENT, BiomeCategoryPlacement.TYPE); | |
// register to BuiltinRegistries | |
configuredRedWool.get(); | |
placedRedWool.get(); | |
}); | |
}; | |
modBus.addListener(onCommonSetup); | |
Consumer<BiomeLoadingEvent> onBiomeLoading = event -> | |
{ | |
if (event.getCategory() == BiomeCategory.DESERT) | |
{ | |
event.getGeneration().addFeature(Decoration.VEGETAL_DECORATION, placedRedWool.get()); | |
} | |
}; | |
forgeBus.addListener(onBiomeLoading); | |
} | |
/** | |
* Worldgen feature type that places a single given blockstate at a given | |
* position The closest things to this in vanilla have requirements on where the | |
* block can be placed, which we'd like to ignore so we can be guaranteed to see | |
* the block | |
*/ | |
public static class SingleBlockFeature extends Feature<BlockStateConfiguration> | |
{ | |
public SingleBlockFeature(Codec<BlockStateConfiguration> codec) | |
{ | |
super(codec); | |
} | |
@Override | |
public boolean place(FeaturePlaceContext<BlockStateConfiguration> context) | |
{ | |
WorldGenLevel world = context.level(); | |
BlockPos pos = context.origin(); | |
BlockStateConfiguration config = context.config(); | |
world.setBlock(pos, config.state, 2); | |
return true; | |
} | |
} | |
/** | |
* Example PlacementModifier that only places features in specified biomes | |
* (The vanilla BiomeFilter doesn't work with lazy features) | |
*/ | |
public static class BiomeCategoryPlacement extends PlacementFilter | |
{ | |
public static final Codec<BiomeCategoryPlacement> CODEC = BiomeCategory.CODEC | |
.xmap(BiomeCategoryPlacement::new, BiomeCategoryPlacement::category) | |
.fieldOf("category") | |
.codec(); | |
public static final PlacementModifierType<BiomeCategoryPlacement> TYPE = () -> CODEC; | |
private final BiomeCategory category; | |
public BiomeCategoryPlacement(BiomeCategory category) | |
{ | |
this.category = category; | |
} | |
public BiomeCategory category() | |
{ | |
return this.category; | |
} | |
@Override | |
public PlacementModifierType<?> type() | |
{ | |
return TYPE; | |
} | |
@Override | |
protected boolean shouldPlace(PlacementContext context, Random random, BlockPos pos) | |
{ | |
WorldGenLevel level = context.getLevel(); | |
return level.getBiome(pos).getBiomeCategory() == this.category(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment