Skip to content

Instantly share code, notes, and snippets.

@TelepathicGrunt
Last active September 12, 2024 13:05
Show Gist options
  • Save TelepathicGrunt/4fdbc445ebcbcbeb43ac748f4b18f342 to your computer and use it in GitHub Desktop.
Save TelepathicGrunt/4fdbc445ebcbcbeb43ac748f4b18f342 to your computer and use it in GitHub Desktop.
How to add new buildings to villages and other jigsaw structures
// Note: Lithostitch mod allows you to add to template pools easily! No code needed.
// https://github.com/Apollounknowndev/lithostitched/wiki/Worldgen-Modifiers#add_template_pool_elements
// This gist code is public domain. Take it! Steal it! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!!!!!!!!!!!!
// 1.18.2 code at top. 1.16.5 version farther down
private static final ResourceKey<StructureProcessorList> EMPTY_PROCESSOR_LIST_KEY = ResourceKey.create(
Registry.PROCESSOR_LIST_REGISTRY, new ResourceLocation("minecraft", "empty"));
public ExampleMod() {
MinecraftForge.EVENT_BUS.addListener(this::addNewVillageBuilding);
}
/**
* Adds the building to the targeted pool.
* We will call this in addNewVillageBuilding method further down to add to every village.
*
* Note: This is an additive operation which means multiple mods can do this and they stack with each other safely.
*/
private static void addBuildingToPool(Registry<StructureTemplatePool> templatePoolRegistry,
Registry<StructureProcessorList> processorListRegistry,
ResourceLocation poolRL,
String nbtPieceRL,
int weight) {
// Grabs the processor list we want to use along with our piece.
// This is a requirement as using the ProcessorLists.EMPTY field will cause the game to throw errors.
// The reason why is the empty processor list in the world's registry is not the same instance as in that field once the world is started up.
Holder<StructureProcessorList> emptyProcessorList = processorListRegistry.getHolderOrThrow(EMPTY_PROCESSOR_LIST_KEY);
// Grab the pool we want to add to
StructureTemplatePool pool = templatePoolRegistry.get(poolRL);
if (pool == null) return;
// Grabs the nbt piece and creates a SinglePoolElement of it that we can add to a structure's pool.
// Use .legacy( for villages/outposts and .single( for everything else
SinglePoolElement piece = SinglePoolElement.legacy(nbtPieceRL, emptyProcessorList).apply(StructureTemplatePool.Projection.RIGID);
// Use AccessTransformer or Accessor Mixin to make StructureTemplatePool's templates field public for us to see.
// Weight is handled by how many times the entry appears in this list.
// We do not need to worry about immutability as this field is created using Lists.newArrayList(); which makes a mutable list.
for (int i = 0; i < weight; i++) {
pool.templates.add(piece);
}
// Use AccessTransformer or Accessor Mixin to make StructureTemplatePool's rawTemplates field public for us to see.
// This list of pairs of pieces and weights is not used by vanilla by default but another mod may need it for efficiency.
// So lets add to this list for completeness. We need to make a copy of the array as it can be an immutable list.
// NOTE: This is a com.mojang.datafixers.util.Pair. It is NOT a fastUtil pair class. Use the mojang class.
List<Pair<StructurePoolElement, Integer>> listOfPieceEntries = new ArrayList<>(pool.rawTemplates);
listOfPieceEntries.add(new Pair<>(piece, weight));
pool.rawTemplates = listOfPieceEntries;
}
/**
* We use FMLServerAboutToStartEvent as the dynamic registry exists now and all JSON worldgen files were parsed.
* Mod compat is best done here.
*/
public void addNewVillageBuilding(final ServerAboutToStartEvent event) {
Registry<StructureTemplatePool> templatePoolRegistry = event.getServer().registryAccess().registry(Registry.TEMPLATE_POOL_REGISTRY).orElseThrow();
Registry<StructureProcessorList> processorListRegistry = event.getServer().registryAccess().registry(Registry.PROCESSOR_LIST_REGISTRY).orElseThrow();
// Adds our piece to all village houses pool
// Note, the resourcelocation is getting the pool files from the data folder. Not assets folder.
addBuildingToPool(templatePoolRegistry, processorListRegistry,
new ResourceLocation("minecraft:village/plains/houses"),
"modid:structure_nbt_resourcelocation", 5);
addBuildingToPool(templatePoolRegistry, processorListRegistry,
new ResourceLocation("minecraft:village/snowy/houses"),
"modid:structure_nbt_resourcelocation", 5);
addBuildingToPool(templatePoolRegistry, processorListRegistry,
new ResourceLocation("minecraft:village/savanna/houses"),
"modid:structure_nbt_resourcelocation", 5);
addBuildingToPool(templatePoolRegistry, processorListRegistry,
new ResourceLocation("minecraft:village/taiga/houses"),
"modid:structure_nbt_resourcelocation", 5);
addBuildingToPool(templatePoolRegistry, processorListRegistry,
new ResourceLocation("minecraft:village/desert/houses"),
"modid:structure_nbt_resourcelocation", 5);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 1.16.5 code
public ExampleMod() {
MinecraftForge.EVENT_BUS.addListener(this::addNewVillageBuilding);
}
/**
* Adds the building to the targeted pool.
* We will call this in addNewVillageBuilding method further down to add to every village.
*
* Note: This is an additive operation which means multiple mods can do this and they stack with each other safely.
*/
private static void addBuildingToPool(MutableRegistry<JigsawPattern> templatePoolRegistry, ResourceLocation poolRL, String nbtPieceRL, int weight) {
// Grab the pool we want to add to
JigsawPattern pool = templatePoolRegistry.get(poolRL);
if (pool == null) return;
// Grabs the nbt piece and creates a SingleJigsawPiece of it that we can add to a structure's pool.
SingleJigsawPiece piece = SingleJigsawPiece.single(nbtPieceRL).apply(JigsawPattern.PlacementBehaviour.RIGID);
// AccessTransformer to make JigsawPattern's templates field public for us to see.
// public net.minecraft.world.gen.feature.jigsaw.JigsawPattern field_214953_e #templates
// Weight is handled by how many times the entry appears in this list.
// We do not need to worry about immutability as this field is created using Lists.newArrayList(); which makes a mutable list.
for (int i = 0; i < weight; i++) {
pool.templates.add(piece);
}
// AccessTransformer to make JigsawPattern's rawTemplates field public for us to see.
// net.minecraft.world.gen.feature.jigsaw.JigsawPattern field_214952_d #rawTemplates
// This list of pairs of pieces and weights is not used by vanilla by default but another mod may need it for efficiency.
// So lets add to this list for completeness. We need to make a copy of the array as it can be an immutable list.
List<Pair<JigsawPiece, Integer>> listOfPieceEntries = new ArrayList<>(pool.rawTemplates);
listOfPieceEntries.add(new Pair<>(piece, weight));
pool.rawTemplates = listOfPieceEntries;
}
/**
* We use FMLServerAboutToStartEvent as the dynamic registry exists now and all JSON worldgen files were parsed.
* Mod compat is best done here.
*/
public void addNewVillageBuilding(final FMLServerAboutToStartEvent event) {
MutableRegistry<JigsawPattern> templatePoolRegistry = event.getServer().registryAccess().registry(Registry.TEMPLATE_POOL_REGISTRY).get();
// Adds our piece to all village houses pool
// Note, the resourcelocation is getting the pool files from the data folder. Not assets folder.
addBuildingToPool(templatePoolRegistry, new ResourceLocation("minecraft:village/plains/houses"),
"modid:structure_nbt_resourcelocation", 5);
addBuildingToPool(templatePoolRegistry, new ResourceLocation("minecraft:village/snowy/houses"),
"modid:structure_nbt_resourcelocation", 5);
addBuildingToPool(templatePoolRegistry, new ResourceLocation("minecraft:village/savanna/houses"),
"modid:structure_nbt_resourcelocation", 5);
addBuildingToPool(templatePoolRegistry, new ResourceLocation("minecraft:village/taiga/houses"),
"modid:structure_nbt_resourcelocation", 5);
addBuildingToPool(templatePoolRegistry, new ResourceLocation("minecraft:village/desert/houses"),
"modid:structure_nbt_resourcelocation", 5);
}
// Note: Lithostitch mod allows you to add to template pools easily! No code needed.
// https://github.com/Apollounknowndev/lithostitched/wiki/Worldgen-Modifiers#add_template_pool_elements
@Override
public void onInitialize() {
ServerLifecycleEvents.SERVER_STARTING.register(this::addNewVillageBuilding);
}
private static final RegistryKey<StructureProcessorList> EMPTY_PROCESSOR_LIST_KEY = RegistryKey.of(
RegistryKeys.PROCESSOR_LIST, new Identifier("minecraft", "empty"));
/**
* Adds the building to the targeted pool.
* We will call this in addNewVillageBuilding method further down to add to every village.
*
* Note: This is an additive operation which means multiple mods can do this and they stack with each other safely.
*/
private static void addBuildingToPool(Registry<StructurePool> templatePoolRegistry,
Registry<StructureProcessorList> processorListRegistry,
Identifier poolRL,
String nbtPieceRL,
int weight) {
// Grabs the processor list we want to use along with our piece.
// This is a requirement as using the ProcessorLists.EMPTY field will cause the game to throw errors.
// The reason why is the empty processor list in the world's registry is not the same instance as in that field once the world is started up.
RegistryEntry<StructureProcessorList> emptyProcessorList = processorListRegistry.getEntry(EMPTY_PROCESSOR_LIST_KEY).get();
// Grab the pool we want to add to
StructurePool pool = templatePoolRegistry.get(poolRL);
if (pool == null) return;
// Grabs the nbt piece and creates a SinglePoolElement of it that we can add to a structure's pool.
// Use .ofProcessedLegacySingle( for villages/outposts and .ofProcessedSingle( for everything else
SinglePoolElement piece = SinglePoolElement.ofProcessedLegacySingle(nbtPieceRL, emptyProcessorList).apply(StructurePool.Projection.RIGID);
// Use AccessWideners or Accessor Mixin to make StructurePool's templates field public for us to see.
// Weight is handled by how many times the entry appears in this list.
// We do not need to worry about immutability as this field is created using Lists.newArrayList(); which makes a mutable list.
for (int i = 0; i < weight; i++) {
((StructurePoolAccessor)pool).getElements().add(piece);
}
// Use AccessWideners or Accessor Mixin to make StructurePool's elementCounts field public for us to see.
// This list of pairs of pieces and weights is not used by vanilla by default but another mod may need it for efficiency.
// So lets add to this list for completeness. We need to make a copy of the array as it can be an immutable list.
// NOTE: This is a com.mojang.datafixers.util.Pair. It is NOT a fastUtil pair class. Use the mojang class.
List<Pair<StructurePoolElement, Integer>> listOfPieceEntries = new ArrayList<>(((StructurePoolAccessor)pool).getElementCounts());
listOfPieceEntries.add(new Pair<>(piece, weight));
((StructurePoolAccessor)pool).setElementCounts(listOfPieceEntries);
}
/**
* We use ServerLifecycleEvents.SERVER_STARTING as the dynamic registry exists now and all JSON worldgen files were parsed.
* Mod compat is best done here.
*/
public void addNewVillageBuilding(final MinecraftServer event) {
Registry<StructurePool> templatePoolRegistry = event.getRegistryManager().get(RegistryKeys.TEMPLATE_POOL);
Registry<StructureProcessorList> processorListRegistry = event.getRegistryManager().get(RegistryKeys.PROCESSOR_LIST);
// Adds our piece to all village houses pool
// Note, the resourcelocation is getting the pool files from the data folder. Not assets folder.
addBuildingToPool(templatePoolRegistry, processorListRegistry,
new Identifier("minecraft:village/plains/houses"),
"modid:structure_nbt_resourcelocation", 5);
addBuildingToPool(templatePoolRegistry, processorListRegistry,
new Identifier("minecraft:village/snowy/houses"),
"modid:structure_nbt_resourcelocation", 5);
addBuildingToPool(templatePoolRegistry, processorListRegistry,
new Identifier("minecraft:village/savanna/houses"),
"modid:structure_nbt_resourcelocation", 5);
addBuildingToPool(templatePoolRegistry, processorListRegistry,
new Identifier("minecraft:village/taiga/houses"),
"modid:structure_nbt_resourcelocation", 5);
addBuildingToPool(templatePoolRegistry, processorListRegistry,
new Identifier("minecraft:village/desert/houses"),
"modid:structure_nbt_resourcelocation", 5);
}
@FlamingoeZpZ
Copy link

For any confused chaps like me in 1.19 having problems related to private variables:

  1. Join this discord https://discord.gg/forge // it's the official forge discord
  2. in bot commands type !moj varName // for example: !moj rawTemplates
  3. Copy the value that says AT: ... // like public net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool f_210559_ # rawTemplates
  4. if the variable is final, add -f after the world public
  5. in build.gradle uncomment this line accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
  6. Follow that path and add and open the accesstransformer.cfg file
  7. Paste any copied values
  8. Rebuild gradle // It's the little refresh button on the right side in the gradle tab

Happy modding!

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