Skip to content

Instantly share code, notes, and snippets.

@Commoble
Created February 20, 2022 17:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Commoble/573ac69ac95818daf643d443bf67b260 to your computer and use it in GitHub Desktop.
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
data/sandbox/worldgen/configured_feature/red_wool.json
{
"config":
{
"state":
{
"Name": "minecraft:red_wool"
}
},
"type": "sandbox:single_block"
}
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"
}
]
}
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();
}
}
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