diff --git a/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/api/structure/v1/FabricStructurePool.java b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/api/structure/v1/FabricStructurePool.java new file mode 100644 index 000000000..3a72304cc --- /dev/null +++ b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/api/structure/v1/FabricStructurePool.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.structure.v1; + +import net.minecraft.structure.pool.StructurePool; +import net.minecraft.structure.pool.StructurePoolElement; +import net.minecraft.util.Identifier; + +/** + * Represents a modifiable structure pool that has several helper methods for modders. + */ +public interface FabricStructurePool { + /** + * Adds a new {@link StructurePoolElement} to the {@link StructurePool}. + * See the alternative {@link #addStructurePoolElement(StructurePoolElement, int)} for details. + * + * @param element the element to add + */ + void addStructurePoolElement(StructurePoolElement element); + + /** + * Adds a new {@link StructurePoolElement} to the {@link StructurePool}. + * Its weight determines the amount of times an element is added to a list used for sampling during structure generation. + * + * @param element the element to add + * @param weight the weight of the element + */ + void addStructurePoolElement(StructurePoolElement element, int weight); + + /** + * Gets the underlying structure pool. + */ + StructurePool getUnderlyingPool(); + + /** + * Gets the identifier for the pool. + */ + default Identifier getId() { + return getUnderlyingPool().getId(); + } +} diff --git a/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/api/structure/v1/StructurePoolAddCallback.java b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/api/structure/v1/StructurePoolAddCallback.java new file mode 100644 index 000000000..6db736d74 --- /dev/null +++ b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/api/structure/v1/StructurePoolAddCallback.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.structure.v1; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +/** + * A callback for newly added structure pools. + * + *

Word of warning: Mods may be editing on the structure pool from user configured data packs + * instead of the builtin Minecraft or mod resources. + * + *

Example usage: + *

{@code
+ * StructurePoolAddCallback.EVENT.register(structurePool -> {
+ * 			if (structurePool.getId().equals(new Identifier("minecraft:village/desert/houses"))) {
+ * 				structurePool.addStructurePoolElement(StructurePoolElement.ofProcessedLegacySingle("fabric:cactus_farm", StructureProcessorLists.FARM_PLAINS).apply(StructurePool.Projection.RIGID));
+ *          }
+ * });}
+ * 
+ */ +public interface StructurePoolAddCallback { + /** + * Called when structure pools are reloaded at data pack reload time. + */ + Event EVENT = EventFactory.createArrayBacked(StructurePoolAddCallback.class, + listeners -> initialPool -> { + for (StructurePoolAddCallback listener : listeners) { + listener.onAdd(initialPool); + } + } + ); + + void onAdd(FabricStructurePool initialPool); +} diff --git a/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/impl/structure/FabricStructurePoolImpl.java b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/impl/structure/FabricStructurePoolImpl.java new file mode 100644 index 000000000..0bab8b484 --- /dev/null +++ b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/impl/structure/FabricStructurePoolImpl.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.structure; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.collect.ImmutableList; +import com.mojang.datafixers.util.Pair; + +import net.minecraft.structure.pool.StructurePool; +import net.minecraft.structure.pool.StructurePoolElement; + +import net.fabricmc.fabric.api.structure.v1.FabricStructurePool; +import net.fabricmc.fabric.mixin.structure.StructurePoolAccessor; + +public class FabricStructurePoolImpl implements FabricStructurePool { + private final StructurePool pool; + + public FabricStructurePoolImpl(StructurePool pool) { + this.pool = pool; + } + + @Override + public void addStructurePoolElement(StructurePoolElement element) { + addStructurePoolElement(element, 1); + } + + @Override + public void addStructurePoolElement(StructurePoolElement element, int weight) { + if (weight <= 0) { + throw new IllegalArgumentException("weight must be positive"); + } + + //adds to elementCounts list; minecraft makes these immutable lists, so we temporarily replace them with an array list + StructurePoolAccessor poolAccessor = (StructurePoolAccessor) getUnderlyingPool(); + + List> list = new ArrayList<>(poolAccessor.getElementCounts()); + list.add(Pair.of(element, weight)); + poolAccessor.setElementCounts(ImmutableList.copyOf(list)); + + //adds to elements list + for (int i = 0; i < weight; i++) { + poolAccessor.getElements().add(element); + } + } + + @Override + public StructurePool getUnderlyingPool() { + return pool; + } +} diff --git a/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/DynamicRegistryManagerMixin.java b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/DynamicRegistryManagerMixin.java new file mode 100644 index 000000000..687f64a24 --- /dev/null +++ b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/DynamicRegistryManagerMixin.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.structure; + +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +import org.spongepowered.asm.mixin.Mixin; + +import net.minecraft.structure.pool.StructurePool; +import net.minecraft.util.dynamic.RegistryOps; +import net.minecraft.util.registry.DynamicRegistryManager; +import net.minecraft.util.registry.Registry; +import net.minecraft.util.registry.RegistryKey; + +import net.fabricmc.fabric.api.structure.v1.StructurePoolAddCallback; +import net.fabricmc.fabric.impl.structure.FabricStructurePoolImpl; + +@Mixin(DynamicRegistryManager.class) +public abstract class DynamicRegistryManagerMixin { + @Inject(method = "load(Lnet/minecraft/util/dynamic/RegistryOps;Lnet/minecraft/util/registry/DynamicRegistryManager;Lnet/minecraft/util/registry/DynamicRegistryManager$Info;)V", at = @At("TAIL"), locals = LocalCapture.CAPTURE_FAILHARD) + private static void load(RegistryOps ops, DynamicRegistryManager manager, @Coerce Object info, CallbackInfo ci, RegistryKey> registryKey) { + if (registryKey.equals(Registry.STRUCTURE_POOL_KEY)) { + for (E registryEntry : manager.get(registryKey)) { + if (registryEntry instanceof StructurePool pool) { + StructurePoolAddCallback.EVENT.invoker().onAdd(new FabricStructurePoolImpl(pool)); + } + } + } + } +} diff --git a/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/StructurePoolAccessor.java b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/StructurePoolAccessor.java new file mode 100644 index 000000000..074b6cee9 --- /dev/null +++ b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/StructurePoolAccessor.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.structure; + +import java.util.List; + +import com.mojang.datafixers.util.Pair; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.structure.pool.StructurePool; +import net.minecraft.structure.pool.StructurePoolElement; + +@Mixin(StructurePool.class) +public interface StructurePoolAccessor { + @Accessor(value = "elements") + List getElements(); + + @Accessor(value = "elementCounts") + List> getElementCounts(); + + @Mutable + @Accessor(value = "elementCounts") + void setElementCounts(List> list); +} diff --git a/fabric-structure-api-v1/src/main/resources/fabric-structure-api-v1.mixins.json b/fabric-structure-api-v1/src/main/resources/fabric-structure-api-v1.mixins.json index 501a06c9e..3c75ae32e 100644 --- a/fabric-structure-api-v1/src/main/resources/fabric-structure-api-v1.mixins.json +++ b/fabric-structure-api-v1/src/main/resources/fabric-structure-api-v1.mixins.json @@ -4,8 +4,10 @@ "compatibilityLevel": "JAVA_16", "mixins": [ "ChunkSerializerMixin", + "DynamicRegistryManagerMixin", "FlatChunkGeneratorConfigAccessor", "StructureFeatureAccessor", + "StructurePoolAccessor", "StructuresConfigAccessor" ], "client": [ diff --git a/fabric-structure-api-v1/src/testmod/java/net/fabricmc/fabric/test/structure/StructureTest.java b/fabric-structure-api-v1/src/testmod/java/net/fabricmc/fabric/test/structure/StructureTest.java index c6b957ea3..58962a13c 100644 --- a/fabric-structure-api-v1/src/testmod/java/net/fabricmc/fabric/test/structure/StructureTest.java +++ b/fabric-structure-api-v1/src/testmod/java/net/fabricmc/fabric/test/structure/StructureTest.java @@ -24,6 +24,9 @@ import org.apache.logging.log4j.Logger; import net.minecraft.block.Blocks; import net.minecraft.nbt.NbtCompound; +import net.minecraft.structure.pool.StructurePool; +import net.minecraft.structure.pool.StructurePoolElement; +import net.minecraft.structure.processor.StructureProcessorLists; import net.minecraft.structure.StructureManager; import net.minecraft.structure.StructurePieceType; import net.minecraft.structure.StructurePieceWithDimensions; @@ -47,6 +50,7 @@ import net.minecraft.world.gen.feature.DefaultFeatureConfig; import net.minecraft.world.gen.feature.StructureFeature; import net.fabricmc.fabric.api.structure.v1.FabricStructureBuilder; +import net.fabricmc.fabric.api.structure.v1.StructurePoolAddCallback; public class StructureTest { private static final Logger LOGGER = LogManager.getLogger(); @@ -64,6 +68,16 @@ public class StructureTest { .adjustsSurface() .register(); Registry.register(Registry.STRUCTURE_PIECE, new Identifier("fabric", "test_structure_piece"), PIECE); + + //Basic Test of Callback + StructurePoolAddCallback.EVENT.register(structurePool -> LOGGER.info("Structure pool {} added", structurePool.getId())); + + //The ideal usage of this callback is to add structures to a Village. Here, I constructed a Cactus Farm, which will be added to the house pool for deserts. For testing purposes, we will make it very common, and use a plains-style log outline so it is clear that it doesn't belong. + StructurePoolAddCallback.EVENT.register(structurePool -> { + if (structurePool.getId().equals(new Identifier("minecraft:village/desert/houses"))) { + structurePool.addStructurePoolElement(StructurePoolElement.ofProcessedLegacySingle("fabric:cactus_farm", StructureProcessorLists.FARM_PLAINS).apply(StructurePool.Projection.RIGID)); + } + }); } public static class TestStructureFeature extends StructureFeature { diff --git a/fabric-structure-api-v1/src/testmod/resources/data/fabric/structures/cactus_farm.nbt b/fabric-structure-api-v1/src/testmod/resources/data/fabric/structures/cactus_farm.nbt new file mode 100644 index 000000000..8a559dcb1 Binary files /dev/null and b/fabric-structure-api-v1/src/testmod/resources/data/fabric/structures/cactus_farm.nbt differ