Skip to content

Instantly share code, notes, and snippets.

@TelepathicGrunt
Last active January 4, 2024 18:31
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TelepathicGrunt/c02333993a1c35dea26fdb98fead5074 to your computer and use it in GitHub Desktop.
Save TelepathicGrunt/c02333993a1c35dea26fdb98fead5074 to your computer and use it in GitHub Desktop.
how to add to village farms in a mod/datapack compatible way that can stack without overwriting each other.
// This gist code is public domain. Take it! Steal it! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!!!!!!!!!!!!
// 1.18.2+
public ExampleMod() {
MinecraftForge.EVENT_BUS.addListener(this::addNewVillageCrop);
}
public void addNewVillageCrop(final ServerAboutToStartEvent event) {
// Dynamic registry of processor lists. We will edit the processor lists from here as this is what the world will actually use.
Registry<StructureProcessorList> processorListRegistry = event.getServer().registryAccess().registry(Registry.PROCESSOR_LIST_REGISTRY).orElseThrow();
// Create a new processor to add to the processor lists
StructureProcessor newCropProcessor = new RuleProcessor(List.of(new ProcessorRule(
// We replace the vanilla Wheat block with Sweet Berry Bush 50% of the time.
// Note, Potatoes and Beetroot will also be avaliable to be replaced too by our processor.
new RandomBlockMatchTest(Blocks.WHEAT, 0.5F),
// Location predicate. Keep this as always true for most use-cases.
AlwaysTrueTest.INSTANCE,
// The modded block to use.
Blocks.SWEET_BERRY_BUSH.defaultBlockState()
)));
// Now add new rule processor to vanilla crop processors
addNewRuleToProcessorList(new ResourceLocation("minecraft:farm_plains"), newCropProcessor, processorListRegistry);
addNewRuleToProcessorList(new ResourceLocation("minecraft:farm_savanna"), newCropProcessor, processorListRegistry);
addNewRuleToProcessorList(new ResourceLocation("minecraft:farm_snowy"), newCropProcessor, processorListRegistry);
addNewRuleToProcessorList(new ResourceLocation("minecraft:farm_taiga"), newCropProcessor, processorListRegistry);
addNewRuleToProcessorList(new ResourceLocation("minecraft:farm_desert"), newCropProcessor, processorListRegistry);
// Can target other mod's processor lists
addNewRuleToProcessorList(new ResourceLocation("repurposed_structures:villages/badlands/crop_replacement"), newCropProcessor, processorListRegistry);
addNewRuleToProcessorList(new ResourceLocation("repurposed_structures:villages/birch/crop_randomizer"), newCropProcessor, processorListRegistry);
addNewRuleToProcessorList(new ResourceLocation("repurposed_structures:villages/dark_forest/crop_randomizer"), newCropProcessor, processorListRegistry);
addNewRuleToProcessorList(new ResourceLocation("repurposed_structures:villages/giant_taiga/crop_randomizer"), newCropProcessor, processorListRegistry);
addNewRuleToProcessorList(new ResourceLocation("repurposed_structures:villages/jungle/crop_randomizer"), newCropProcessor, processorListRegistry);
addNewRuleToProcessorList(new ResourceLocation("repurposed_structures:villages/mountains/crop_randomizer"), newCropProcessor, processorListRegistry);
addNewRuleToProcessorList(new ResourceLocation("repurposed_structures:villages/oak/crop_randomizer"), newCropProcessor, processorListRegistry);
addNewRuleToProcessorList(new ResourceLocation("repurposed_structures:villages/swamp/crop_randomizer"), newCropProcessor, processorListRegistry);
}
private static void addNewRuleToProcessorList(ResourceLocation targetProcessorList, StructureProcessor processorToAdd, Registry<StructureProcessorList> processorListRegistry) {
processorListRegistry.getOptional(targetProcessorList)
.ifPresent(processorList -> {
// The list of processor is an immutable list.
// So we will create a new list, add to it, and set the field to that new list.
List<StructureProcessor> newSafeList = new ArrayList<>(processorList.list());
newSafeList.add(processorToAdd);
processorList.list = newSafeList; // Use an Access Transformer or Accessor Mixin to set the private list field.
});
}
////////////////////////////////////////////////////////
// 1.16.5 - (this old way is outdated and probably entirely unnecessary. Try the method done above instead and see if that works in 1.16.5)
public ExampleMod() {
MinecraftForge.EVENT_BUS.addListener(this::addNewVillageCrop);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::createProcessors);
}
// We will create the processor in FMLCommonSetupEvent as we know our
// block will be created and registered by the time this event fires.
public static RuleStructureProcessor MODDED_CROP_PROCESSOR;
public void createProcessors(final FMLCommonSetupEvent event) {
MODDED_CROP_PROCESSOR = new RuleStructureProcessor(ImmutableList.of(
new RuleEntry(
// We replace the vanilla Wheat block with Sweet Berry Bush 50% of the time.
// Note, Potatoes and Beetroot will also be avaliable to be replaced too by our processor.
new RandomBlockMatchRuleTest(Blocks.WHEAT, 0.5F),
// Location predicate. Keep this as always true for most use-cases.
AlwaysTrueRuleTest.INSTANCE,
// The modded block to use.
Blocks.SWEET_BERRY_BUSH.defaultBlockState()
)
));
}
/**
* Appends the given processor to the end of the targeted pieces in the targeted pools.
* We will call this in addNewVillageCrop method further down to add to every village farm pool entry.
*
* Note: This is an additive operation which means multiple mods can do this and they stack with each other safely.
*/
private static void addCropToPoolElement(MutableRegistry<JigsawPattern> templatePoolRegistry, ResourceLocation poolRL, List<ResourceLocation> nbtPieceRLList) {
JigsawPattern pool = templatePoolRegistry.get(poolRL);
if (pool == null) return;
// AccessTransformer to make JigsawPattern's templates field public for us to see.
// public net.minecraft.world.gen.feature.jigsaw.JigsawPattern field_214953_e #templates
pool.templates.forEach(entry -> {
if (entry instanceof SingleJigsawPiece) {
SingleJigsawPiece jigsawPiece = (SingleJigsawPiece) entry;
// AccessTransformer to make SingleJigsawPiece's template field public for us to see.
// public net.minecraft.world.gen.feature.jigsaw.SingleJigsawPiece field_236839_c_ #template
if (jigsawPiece.template.left().isPresent()) {
// Found the piece entry we want to modify.
ResourceLocation currentNBTPiece = jigsawPiece.template.left().get();
if (nbtPieceRLList.stream().anyMatch(currentNBTPiece::equals)) {
// AccessTransformer to make SingleJigsawPiece's processors field public and non-final for us to see and swap out.
// public-f net.minecraft.world.gen.feature.jigsaw.SingleJigsawPiece field_214862_b #processors
StructureProcessorList originalStructureProcessorList = jigsawPiece.processors.get();
// Make a mutable list as this is an immutable list originally
List<StructureProcessor> mutableProcessorList = new ArrayList<>(originalStructureProcessorList.list());
// Add our processor to the end so it runs after everything else
mutableProcessorList.add(MODDED_CROP_PROCESSOR);
StructureProcessorList newStructureProcessorList = new StructureProcessorList(mutableProcessorList);
// Override the original field with our new instance. This is an additive operation and safe to stack with other mods
jigsawPiece.processors = () -> newStructureProcessorList;
}
}
}
});
}
/**
* We use FMLServerAboutToStartEvent as the dynamic registry exists now and all JSON worldgen files were parsed.
* Mod compat is best done here.
*/
public void addNewVillageCrop(final FMLServerAboutToStartEvent event) {
MutableRegistry<JigsawPattern> templatePoolRegistry = event.getServer().registryAccess().registry(Registry.TEMPLATE_POOL_REGISTRY).get();
// Adds the crop processor to all vanilla farm village pieces
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("minecraft:village/plains/houses"), ImmutableList.of(
new ResourceLocation("minecraft:village/plains/houses/plains_large_farm_1"),
new ResourceLocation("minecraft:village/plains/houses/plains_small_farm_1")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("minecraft:village/plains/zombie/houses"), ImmutableList.of(
new ResourceLocation("minecraft:village/plains/houses/plains_large_farm_1"),
new ResourceLocation("minecraft:village/plains/houses/plains_small_farm_1")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("minecraft:village/snowy/houses"), ImmutableList.of(
new ResourceLocation("minecraft:village/snowy/houses/snowy_farm_1"),
new ResourceLocation("minecraft:village/snowy/houses/snowy_farm_2")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("minecraft:village/snowy/zombie/houses"), ImmutableList.of(
new ResourceLocation("minecraft:village/snowy/houses/snowy_farm_1"),
new ResourceLocation("minecraft:village/snowy/houses/snowy_farm_2")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("minecraft:village/savanna/houses"), ImmutableList.of(
new ResourceLocation("minecraft:village/savanna/houses/savanna_large_farm_1"),
new ResourceLocation("minecraft:village/savanna/houses/savanna_large_farm_2"),
new ResourceLocation("minecraft:village/savanna/houses/savanna_small_farm")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("minecraft:village/savanna/zombie/houses"), ImmutableList.of(
new ResourceLocation("minecraft:village/savanna/houses/savanna_large_farm_1"),
new ResourceLocation("minecraft:village/savanna/zombie/houses/savanna_large_farm_2"),
new ResourceLocation("minecraft:village/savanna/houses/savanna_small_farm")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("minecraft:village/taiga/houses"), ImmutableList.of(
new ResourceLocation("minecraft:village/taiga/houses/taiga_large_farm_1"),
new ResourceLocation("minecraft:village/taiga/houses/taiga_large_farm_2"),
new ResourceLocation("minecraft:village/taiga/houses/taiga_small_farm_1")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("minecraft:village/taiga/zombie/houses"), ImmutableList.of(
new ResourceLocation("minecraft:village/taiga/houses/taiga_large_farm_1"),
new ResourceLocation("minecraft:village/taiga/zombie/houses/taiga_large_farm_2"),
new ResourceLocation("minecraft:village/taiga/houses/taiga_small_farm_1")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("minecraft:village/desert/houses"), ImmutableList.of(
new ResourceLocation("minecraft:village/desert/houses/desert_large_farm_1"),
new ResourceLocation("minecraft:village/desert/houses/desert_farm_1"),
new ResourceLocation("minecraft:village/desert/houses/desert_farm_2")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("minecraft:village/desert/zombie/houses"), ImmutableList.of(
new ResourceLocation("minecraft:village/desert/houses/desert_large_farm_1"),
new ResourceLocation("minecraft:village/desert/houses/desert_farm_1"),
new ResourceLocation("minecraft:village/desert/houses/desert_farm_2")
));
// Since we only use Resourcelocations to target where to add our processor, there's no hard dependency with other mod's code.
// A simple check to see if the targeted mod exists is sufficient before actaully attempting to add.
if (ModList.get().isLoaded("repurposed_structures")) {
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("repurposed_structures:villages/badlands/houses"), ImmutableList.of(
new ResourceLocation("repurposed_structures:villages/badlands/houses/farm_1"),
new ResourceLocation("repurposed_structures:villages/badlands/houses/farm_2")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("repurposed_structures:villages/badlands/zombie/houses"), ImmutableList.of(
new ResourceLocation("repurposed_structures:villages/badlands/houses/farm_1"),
new ResourceLocation("repurposed_structures:villages/badlands/houses/farm_2")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("repurposed_structures:villages/birch/houses"), ImmutableList.of(
new ResourceLocation("repurposed_structures:villages/birch/houses/large_farm_1"),
new ResourceLocation("repurposed_structures:villages/birch/houses/small_farm_1")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("repurposed_structures:villages/birch/zombie/houses"), ImmutableList.of(
new ResourceLocation("repurposed_structures:villages/birch/houses/large_farm_1"),
new ResourceLocation("repurposed_structures:villages/birch/houses/small_farm_1")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("repurposed_structures:villages/dark_forest/houses"), ImmutableList.of(
new ResourceLocation("repurposed_structures:villages/dark_forest/houses/large_farm_1"),
new ResourceLocation("repurposed_structures:villages/dark_forest/houses/small_farm_1")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("repurposed_structures:villages/dark_forest/zombie/houses"), ImmutableList.of(
new ResourceLocation("repurposed_structures:villages/dark_forest/houses/large_farm_1"),
new ResourceLocation("repurposed_structures:villages/dark_forest/houses/small_farm_1")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("repurposed_structures:villages/giant_tree_taiga/houses"), ImmutableList.of(
new ResourceLocation("repurposed_structures:villages/giant_taiga/houses/farm_1"),
new ResourceLocation("repurposed_structures:villages/giant_taiga/houses/farm_2")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("repurposed_structures:villages/giant_tree_taiga/zombie/houses"), ImmutableList.of(
new ResourceLocation("repurposed_structures:villages/giant_taiga/houses/farm_1"),
new ResourceLocation("repurposed_structures:villages/giant_taiga/houses/farm_2")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("repurposed_structures:villages/jungle/houses"), ImmutableList.of(
new ResourceLocation("repurposed_structures:villages/jungle/houses/large_farm_1"),
new ResourceLocation("repurposed_structures:villages/jungle/houses/large_farm_2"),
new ResourceLocation("repurposed_structures:villages/jungle/houses/small_farm")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("repurposed_structures:villages/jungle/zombie/houses"), ImmutableList.of(
new ResourceLocation("repurposed_structures:villages/jungle/houses/large_farm_1"),
new ResourceLocation("repurposed_structures:villages/jungle/zombie/houses/large_farm_2"),
new ResourceLocation("repurposed_structures:villages/jungle/houses/small_farm")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("repurposed_structures:villages/mountains/houses"), ImmutableList.of(
new ResourceLocation("repurposed_structures:villages/mountains/houses/large_farm_1"),
new ResourceLocation("repurposed_structures:villages/mountains/houses/large_farm_2"),
new ResourceLocation("repurposed_structures:villages/mountains/houses/small_farm_1")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("repurposed_structures:villages/mountains/zombie/houses"), ImmutableList.of(
new ResourceLocation("repurposed_structures:villages/mountains/houses/large_farm_1"),
new ResourceLocation("repurposed_structures:villages/mountains/zombie/houses/large_farm_2"),
new ResourceLocation("repurposed_structures:villages/mountains/houses/small_farm_1")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("repurposed_structures:villages/mushroom/houses"), ImmutableList.of(
new ResourceLocation("repurposed_structures:villages/mushroom/houses/large_farm_1"),
new ResourceLocation("repurposed_structures:villages/mushroom/houses/small_farm_1")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("repurposed_structures:villages/oak/houses"), ImmutableList.of(
new ResourceLocation("repurposed_structures:villages/oak/houses/large_farm_1"),
new ResourceLocation("repurposed_structures:villages/oak/houses/small_farm_1")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("repurposed_structures:villages/oak/zombie/houses"), ImmutableList.of(
new ResourceLocation("repurposed_structures:villages/oak/houses/large_farm_1"),
new ResourceLocation("repurposed_structures:villages/oak/houses/small_farm_1")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("repurposed_structures:village/swamp/houses"), ImmutableList.of(
new ResourceLocation("repurposed_structures:villages/swamp/houses/farm_1"),
new ResourceLocation("repurposed_structures:villages/swamp/houses/farm_2")
));
addCropToPoolElement(templatePoolRegistry, new ResourceLocation("repurposed_structures:villages/swamp/zombie/houses"), ImmutableList.of(
new ResourceLocation("repurposed_structures:villages/swamp/houses/farm_1"),
new ResourceLocation("repurposed_structures:villages/swamp/houses/farm_2")
));
}
}
@BladeAndFeather
Copy link

BladeAndFeather commented Oct 22, 2021

public void createProcessors(final FMLCommonSetupEvent event) {
        MODDED_CROP_PROCESSOR = new RuleStructureProcessor(ImmutableList.of(
            new RuleEntry(
                    // We replace the vanilla Wheat block with Sweet Berry Bush 50% of the time.
                    // Note, Potatoes and Beetroot will also be avaliable to be replaced too by our processor.
                    new RandomBlockMatchRuleTest(Blocks.WHEAT, 0.5F),
                    // Location predicate. Keep this as always true for most use-cases.
                    AlwaysTrueRuleTest.INSTANCE,
                    // The modded block to use.
                    Blocks.SWEET_BERRY_BUSH.defaultBlockState()
            )
        ));    //This is the only line with an issue, you need to add a second closing parentheses
    }

Edit: I added the second closing parentheses in my code snippet.

@TelepathicGrunt
Copy link
Author

Fixed. Thanks!

@BladeAndFeather
Copy link

One last thing, "public static final RuleStructureProcessor MODDED_CROP_PROCESSOR;" cannot be final. It would have to be created immediately outside of any method, because it has to be declared on class declaration, because it is static and final. And you cannot define it outside of the method it is in because it would trigger an exception because defaultBlockState would not be defined yet, especially for custom crops.
It is a little sad I did not end up using this code though, it is well documented and straight forward. What I did was I added my own structure to the game. Which I then looked at your addNewBuildingsVillages code, which is also well documented, and it taught me how to get a NBT file to actually load into the game.
But sadly again I ended up not using that code again, and instead made my structure a surface structure that was required to spawn within 5 chunks of a village. (It is the reverse of a Pillager Outpost that has to be further away than 10 chunks from a village) Which let me have one structure have a chance of spawning on the outskirts of an village regardless of the type of village. Which had the added benefit of making my outdoor theater structure spawn in the open areas next to the villages, rather than in the middle of the villages with no audience space.
In summary, there is one small fix needed, and I wanted to give you credit for the useful code you have posted that greatly helped me solve the issues I was having with the code I was writing. Keep up the good work, and continue to share useful code.

@TelepathicGrunt
Copy link
Author

Fixed again and thanks! I’m guessing you looked at my StructureTutorialMod for creating a new structure. But yeah, doing a reverse pillager outpost placing is smart for having a nearby building to villages without actually being connected. I’m imagining a village barn and stuff lol. Good luck with your mod!

@BladeAndFeather
Copy link

I just checked, and yep I got inspiration from the StructureTutorialMod, I did not even realize that was also yours.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment