From 516ece7c6e17dbf885200ff091674f95f3335e6a Mon Sep 17 00:00:00 2001
From: Joseph Burton <burtonjae@hotmail.co.uk>
Date: Fri, 21 Aug 2020 17:22:57 +0100
Subject: [PATCH] Structures API (#917)

* Initial structures API implementation

* Improve generics + add a superflatFeature helper method

* Initialize StructuresConfig class early to prevent its assertion failing

* Add a testmod

* Documentation and null assertions

* Apply review suggestions

* Update fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/api/structure/v1/FabricStructureBuilder.java

Co-authored-by: shartte <shartte@users.noreply.github.com>

* Apply review suggestions

* Update to 1.16.2

Co-authored-by: shartte <shartte@users.noreply.github.com>
---
 fabric-structure-api-v1/build.gradle          |   2 +
 .../structure/v1/FabricStructureBuilder.java  | 228 ++++++++++++++++++
 .../impl/structure/FabricStructureUtil.java   |  31 +++
 .../impl/structure/StructuresConfigHooks.java |  21 ++
 .../fabric/mixin/structure/BiomeAccessor.java |  37 +++
 .../FlatChunkGeneratorConfigAccessor.java     |  34 +++
 .../MixinChunkGeneratorSettings.java          |  34 +++
 .../structure/MixinStructuresConfig.java      |  53 ++++
 .../structure/StructureFeatureAccessor.java   |  41 ++++
 .../structure/StructuresConfigAccessor.java   |  35 +++
 .../fabric-structure-api-v1.mixins.json       |  18 ++
 .../src/main/resources/fabric.mod.json        |  26 ++
 .../fabric/test/structure/StructureTest.java  | 117 +++++++++
 .../mixin/MixinDefaultBiomeCreator.java       |  38 +++
 ...abric-structure-api-v1-testmod.mixins.json |  13 +
 .../src/testmod/resources/fabric.mod.json     |  14 ++
 settings.gradle                               |   1 +
 17 files changed, 743 insertions(+)
 create mode 100644 fabric-structure-api-v1/build.gradle
 create mode 100644 fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/api/structure/v1/FabricStructureBuilder.java
 create mode 100644 fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/impl/structure/FabricStructureUtil.java
 create mode 100644 fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/impl/structure/StructuresConfigHooks.java
 create mode 100644 fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/BiomeAccessor.java
 create mode 100644 fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/FlatChunkGeneratorConfigAccessor.java
 create mode 100644 fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/MixinChunkGeneratorSettings.java
 create mode 100644 fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/MixinStructuresConfig.java
 create mode 100644 fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/StructureFeatureAccessor.java
 create mode 100644 fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/StructuresConfigAccessor.java
 create mode 100644 fabric-structure-api-v1/src/main/resources/fabric-structure-api-v1.mixins.json
 create mode 100644 fabric-structure-api-v1/src/main/resources/fabric.mod.json
 create mode 100644 fabric-structure-api-v1/src/testmod/java/net/fabricmc/fabric/test/structure/StructureTest.java
 create mode 100644 fabric-structure-api-v1/src/testmod/java/net/fabricmc/fabric/test/structure/mixin/MixinDefaultBiomeCreator.java
 create mode 100644 fabric-structure-api-v1/src/testmod/resources/fabric-structure-api-v1-testmod.mixins.json
 create mode 100644 fabric-structure-api-v1/src/testmod/resources/fabric.mod.json

diff --git a/fabric-structure-api-v1/build.gradle b/fabric-structure-api-v1/build.gradle
new file mode 100644
index 000000000..94fe96077
--- /dev/null
+++ b/fabric-structure-api-v1/build.gradle
@@ -0,0 +1,2 @@
+archivesBaseName = "fabric-structure-api-v1"
+version = getSubprojectVersion(project, "1.0.0")
diff --git a/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/api/structure/v1/FabricStructureBuilder.java b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/api/structure/v1/FabricStructureBuilder.java
new file mode 100644
index 000000000..dc957adb5
--- /dev/null
+++ b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/api/structure/v1/FabricStructureBuilder.java
@@ -0,0 +1,228 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import net.minecraft.util.Identifier;
+import net.minecraft.util.registry.BuiltinRegistries;
+import net.minecraft.world.biome.Biome;
+import net.minecraft.world.gen.GenerationStep;
+import net.minecraft.world.gen.chunk.StructureConfig;
+import net.minecraft.world.gen.chunk.StructuresConfig;
+import net.minecraft.world.gen.feature.ConfiguredStructureFeature;
+import net.minecraft.world.gen.feature.FeatureConfig;
+import net.minecraft.world.gen.feature.StructureFeature;
+
+import net.fabricmc.fabric.impl.structure.FabricStructureUtil;
+import net.fabricmc.fabric.impl.structure.StructuresConfigHooks;
+import net.fabricmc.fabric.mixin.structure.BiomeAccessor;
+import net.fabricmc.fabric.mixin.structure.FlatChunkGeneratorConfigAccessor;
+import net.fabricmc.fabric.mixin.structure.StructureFeatureAccessor;
+import net.fabricmc.fabric.mixin.structure.StructuresConfigAccessor;
+
+/**
+ * A builder for registering custom structures.
+ *
+ * <p>Example usage:
+ * <pre>{@code
+ * StructureFeature structure = new MyStructure(DefaultFeatureConfig.CODEC);
+ * ConfiguredStructureFeature<DefaultFeatureConfig, ? extends StructureFeature<DefaultFeatureConfig>> configuredStructure
+ *     = structure.configure(new DefaultFeatureConfig());
+ * FabricStructureBuilder.create(new Identifier("mymod:mystructure"), structure)
+ *     .step(GenerationStep.Feature.SURFACE_STRUCTURES) // required
+ *     .defaultConfig(32, 8, 12345) // required
+ *     .superflatFeature(configuredStructure)
+ *     .register();}
+ * </pre></p>
+ *
+ * <p>This class does <i>not</i> add structures to biomes for you, you have to do that yourself. You may also need to
+ * register custom structure pieces yourself.</p>
+ */
+public final class FabricStructureBuilder<FC extends FeatureConfig, S extends StructureFeature<FC>> {
+	private final Identifier id;
+	private final S structure;
+	private GenerationStep.Feature step;
+	private StructureConfig defaultConfig;
+	private ConfiguredStructureFeature<FC, ? extends StructureFeature<FC>> superflatFeature;
+	private boolean adjustsSurface = false;
+
+	private FabricStructureBuilder(Identifier id, S structure) {
+		this.id = id;
+		this.structure = structure;
+	}
+
+	/**
+	 * Creates a new {@code FabricStructureBuilder} for registering a structure.
+	 *
+	 * @param id The structure ID.
+	 * @param structure The {@linkplain StructureFeature} you want to register.
+	 */
+	public static <FC extends FeatureConfig, S extends StructureFeature<FC>> FabricStructureBuilder<FC, S> create(Identifier id, S structure) {
+		Objects.requireNonNull(id, "id must not be null");
+		Objects.requireNonNull(structure, "structure must not be null");
+		return new FabricStructureBuilder<>(id, structure);
+	}
+
+	/**
+	 * Sets the generation step of this structure. The generation step specifies when the structure is generated, to
+	 * ensure they are generated in the correct order to reduce the amount of floating blocks.
+	 *
+	 * <p>The most commonly used values for structures are {@linkplain GenerationStep.Feature#SURFACE_STRUCTURES} and
+	 * {@linkplain GenerationStep.Feature#UNDERGROUND_STRUCTURES}, however technically any value in the
+	 * {@linkplain GenerationStep.Feature} enum may be used.</p>
+	 *
+	 * <p>This is a required option.</p>
+	 */
+	public FabricStructureBuilder<FC, S> step(GenerationStep.Feature step) {
+		Objects.requireNonNull(step, "step must not be null");
+		this.step = step;
+		return this;
+	}
+
+	/**
+	 * Sets the default {@linkplain StructureConfig} for this structure. See the alternative
+	 * {@linkplain #defaultConfig(int, int, int)} for details.
+	 *
+	 * <p>This is a required option.</p>
+	 */
+	public FabricStructureBuilder<FC, S> defaultConfig(StructureConfig config) {
+		Objects.requireNonNull(config, "config must not be null");
+		this.defaultConfig = config;
+		return this;
+	}
+
+	/**
+	 * Sets the default {@linkplain StructureConfig} for this structure. This sets the default configuration of where in
+	 * the world to place structures.
+	 *
+	 * <p>Note: the {@code spacing} and {@code separation} options are subject to other checks for whether the structure
+	 * can spawn, such as biome. If these checks always pass and the structure can spawn in every biome, then the
+	 * description of these values below would be exactly correct.</p>
+	 *
+	 * <p>This is a required option. Vanilla needs it to function.</p>
+	 *
+	 * @param spacing The average distance between 2 structures of this type along the X and Z axes.
+	 * @param separation The minimum distance between 2 structures of this type.
+	 * @param salt The random salt of the structure. This does not affect how common the structure is, but every
+	 *                structure must have an unique {@code salt} in order to spawn in different places.
+	 *
+	 * @see #defaultConfig(StructureConfig)
+	 */
+	public FabricStructureBuilder<FC, S> defaultConfig(int spacing, int separation, int salt) {
+		return defaultConfig(new StructureConfig(spacing, separation, salt));
+	}
+
+	/**
+	 * Sets the structure configuration which spawns in superflat worlds. If unset, this structure will not spawn in
+	 * superflat worlds.
+	 *
+	 * @see #superflatFeature(FeatureConfig)
+	 */
+	public FabricStructureBuilder<FC, S> superflatFeature(ConfiguredStructureFeature<FC, ? extends StructureFeature<FC>> superflatFeature) {
+		Objects.requireNonNull(superflatFeature, "superflatFeature must not be null");
+		this.superflatFeature = superflatFeature;
+		return this;
+	}
+
+	/**
+	 * Sets the structure configuration which spawns in superflat worlds. If unset, this structure will not spawn in
+	 * superflat worlds.
+	 *
+	 * @see #superflatFeature(ConfiguredStructureFeature)
+	 */
+	public FabricStructureBuilder<FC, S> superflatFeature(FC config) {
+		return superflatFeature(structure.configure(config));
+	}
+
+	/**
+	 * Causes structure pieces of this structure to adjust the surface of the world to fit them, so that they don't
+	 * stick out of or into the ground.
+	 */
+	public FabricStructureBuilder<FC, S> adjustsSurface() {
+		this.adjustsSurface = true;
+		return this;
+	}
+
+	/**
+	 * Registers this structure and applies the other changes from the {@linkplain FabricStructureBuilder}.
+	 */
+	public S register() {
+		Objects.requireNonNull(step, "Structure \"" + id + "\" is missing a generation step");
+		Objects.requireNonNull(defaultConfig, "Structure \"" + id + "\" is missing a default config");
+
+		// Ensure StructuresConfig class is initialized, so the assertion in its static {} block doesn't fail
+		StructuresConfig.DEFAULT_STRUCTURES.size();
+
+		StructureFeatureAccessor.callRegister(id.toString(), structure, step);
+
+		if (!id.toString().equals(structure.getName())) {
+			// mods should not be overriding getName, but if they do and it's incorrect, this gives an error
+			throw new IllegalStateException(String.format("Structure \"%s\" has mismatching name \"%s\". Structures should not override \"getName\".", id, structure.getName()));
+		}
+
+		StructuresConfigAccessor.setDefaultStructures(ImmutableMap.<StructureFeature<?>, StructureConfig>builder()
+				.putAll(StructuresConfig.DEFAULT_STRUCTURES)
+				.put(structure, defaultConfig)
+				.build());
+
+		if (superflatFeature != null) {
+			FlatChunkGeneratorConfigAccessor.getStructureToFeatures().put(structure, superflatFeature);
+		}
+
+		if (adjustsSurface) {
+			StructureFeatureAccessor.setSurfaceAdjustingStructures(ImmutableList.<StructureFeature<?>>builder()
+					.addAll(StructureFeature.field_24861)
+					.add(structure)
+					.build());
+		}
+
+		// update existing structures configs
+		for (StructuresConfig structuresConfig : FabricStructureUtil.DEFAULT_STRUCTURES_CONFIGS) {
+			((StructuresConfigHooks) structuresConfig).fabric_updateDefaultEntries();
+		}
+
+		// update builtin biomes, just to be safe
+		for (Biome biome : BuiltinRegistries.BIOME) {
+			BiomeAccessor biomeAccessor = (BiomeAccessor) (Object) biome;
+			Map<Integer, List<StructureFeature<?>>> structureLists = biomeAccessor.getStructureLists();
+
+			if (!(structureLists instanceof HashMap)) {
+				// not guaranteed by the standard to be a mutable map
+				((BiomeAccessor) (Object) biome).setStructureLists(structureLists = new HashMap<>(structureLists));
+			}
+
+			// not guaranteed by the standard to be mutable lists
+			structureLists.compute(step.ordinal(), (k, v) -> makeMutable(v)).add(structure);
+		}
+
+		return structure;
+	}
+
+	private static List<StructureFeature<?>> makeMutable(List<StructureFeature<?>> mapValue) {
+		if (mapValue == null) return new ArrayList<>();
+		if (!(mapValue instanceof ArrayList)) return new ArrayList<>(mapValue);
+		return mapValue;
+	}
+}
diff --git a/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/impl/structure/FabricStructureUtil.java b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/impl/structure/FabricStructureUtil.java
new file mode 100644
index 000000000..b4fab1eed
--- /dev/null
+++ b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/impl/structure/FabricStructureUtil.java
@@ -0,0 +1,31 @@
+/*
+ * 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.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+import net.minecraft.world.gen.chunk.StructuresConfig;
+
+public final class FabricStructureUtil {
+	private FabricStructureUtil() { }
+
+	// This tracks all StructuresConfig objects that have been created with the default set of structures
+	// in order to add mod-created structures that are registered later
+	public static final Set<StructuresConfig> DEFAULT_STRUCTURES_CONFIGS = Collections.newSetFromMap(new WeakHashMap<>());
+}
diff --git a/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/impl/structure/StructuresConfigHooks.java b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/impl/structure/StructuresConfigHooks.java
new file mode 100644
index 000000000..6eba0c190
--- /dev/null
+++ b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/impl/structure/StructuresConfigHooks.java
@@ -0,0 +1,21 @@
+/*
+ * 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;
+
+public interface StructuresConfigHooks {
+	void fabric_updateDefaultEntries();
+}
diff --git a/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/BiomeAccessor.java b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/BiomeAccessor.java
new file mode 100644
index 000000000..a56951eb2
--- /dev/null
+++ b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/BiomeAccessor.java
@@ -0,0 +1,37 @@
+/*
+ * 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 java.util.Map;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Mutable;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+import net.minecraft.world.biome.Biome;
+import net.minecraft.world.gen.feature.StructureFeature;
+
+@Mixin(Biome.class)
+public interface BiomeAccessor {
+	@Accessor("field_26634")
+	Map<Integer, List<StructureFeature<?>>> getStructureLists();
+
+	@Mutable
+	@Accessor("field_26634")
+	void setStructureLists(Map<Integer, List<StructureFeature<?>>> field_26634);
+}
diff --git a/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/FlatChunkGeneratorConfigAccessor.java b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/FlatChunkGeneratorConfigAccessor.java
new file mode 100644
index 000000000..162717921
--- /dev/null
+++ b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/FlatChunkGeneratorConfigAccessor.java
@@ -0,0 +1,34 @@
+/*
+ * 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.Map;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+import net.minecraft.world.gen.chunk.FlatChunkGeneratorConfig;
+import net.minecraft.world.gen.feature.ConfiguredStructureFeature;
+import net.minecraft.world.gen.feature.StructureFeature;
+
+@Mixin(FlatChunkGeneratorConfig.class)
+public interface FlatChunkGeneratorConfigAccessor {
+	@Accessor("STRUCTURE_TO_FEATURES")
+	static Map<StructureFeature<?>, ConfiguredStructureFeature<?, ?>> getStructureToFeatures() {
+		throw new AssertionError("Untransformed accessor");
+	}
+}
diff --git a/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/MixinChunkGeneratorSettings.java b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/MixinChunkGeneratorSettings.java
new file mode 100644
index 000000000..41f561c88
--- /dev/null
+++ b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/MixinChunkGeneratorSettings.java
@@ -0,0 +1,34 @@
+/*
+ * 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.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+import net.minecraft.world.gen.chunk.ChunkGeneratorSettings;
+
+import net.fabricmc.fabric.impl.structure.FabricStructureUtil;
+
+@Mixin(ChunkGeneratorSettings.class)
+public class MixinChunkGeneratorSettings {
+	@Inject(method = "createUndergroundSettings", at = @At("RETURN"))
+	private static void onCreateCavesType(CallbackInfoReturnable<ChunkGeneratorSettings> cir) {
+		FabricStructureUtil.DEFAULT_STRUCTURES_CONFIGS.add(cir.getReturnValue().getStructuresConfig());
+	}
+}
diff --git a/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/MixinStructuresConfig.java b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/MixinStructuresConfig.java
new file mode 100644
index 000000000..105093d5e
--- /dev/null
+++ b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/MixinStructuresConfig.java
@@ -0,0 +1,53 @@
+/*
+ * 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.Map;
+
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import net.minecraft.world.gen.chunk.StructureConfig;
+import net.minecraft.world.gen.chunk.StructuresConfig;
+import net.minecraft.world.gen.feature.StructureFeature;
+
+import net.fabricmc.fabric.impl.structure.FabricStructureUtil;
+import net.fabricmc.fabric.impl.structure.StructuresConfigHooks;
+
+@Mixin(StructuresConfig.class)
+public class MixinStructuresConfig implements StructuresConfigHooks {
+	@Shadow
+	@Final
+	private Map<StructureFeature<?>, StructureConfig> structures;
+
+	// This constructor of StructuresConfig initializes it with the default set of structures.
+	// Since a mod can register its structures later, we need to keep track of the object created
+	// here, so that we can add new structures to it later.
+	@Inject(method = "<init>(Z)V", at = @At("RETURN"))
+	private void onDefaultInit(CallbackInfo ci) {
+		FabricStructureUtil.DEFAULT_STRUCTURES_CONFIGS.add((StructuresConfig) (Object) this);
+	}
+
+	@Override
+	public void fabric_updateDefaultEntries() {
+		StructuresConfig.DEFAULT_STRUCTURES.forEach(structures::putIfAbsent);
+	}
+}
diff --git a/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/StructureFeatureAccessor.java b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/StructureFeatureAccessor.java
new file mode 100644
index 000000000..4f6dcf1ae
--- /dev/null
+++ b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/StructureFeatureAccessor.java
@@ -0,0 +1,41 @@
+/*
+ * 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 org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Mutable;
+import org.spongepowered.asm.mixin.gen.Accessor;
+import org.spongepowered.asm.mixin.gen.Invoker;
+
+import net.minecraft.world.gen.GenerationStep;
+import net.minecraft.world.gen.feature.StructureFeature;
+
+@Mixin(StructureFeature.class)
+public interface StructureFeatureAccessor {
+	@Accessor("field_24861")
+	@Mutable
+	static void setSurfaceAdjustingStructures(List<StructureFeature<?>> surfaceAdjustingStructures) {
+		throw new AssertionError("Untransformed accessor");
+	}
+
+	@Invoker
+	static <F extends StructureFeature<?>> F callRegister(String name, F structureFeature, GenerationStep.Feature step) {
+		throw new AssertionError("Untransformed accessor");
+	}
+}
diff --git a/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/StructuresConfigAccessor.java b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/StructuresConfigAccessor.java
new file mode 100644
index 000000000..8405941bf
--- /dev/null
+++ b/fabric-structure-api-v1/src/main/java/net/fabricmc/fabric/mixin/structure/StructuresConfigAccessor.java
@@ -0,0 +1,35 @@
+/*
+ * 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 com.google.common.collect.ImmutableMap;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Mutable;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+import net.minecraft.world.gen.chunk.StructureConfig;
+import net.minecraft.world.gen.chunk.StructuresConfig;
+import net.minecraft.world.gen.feature.StructureFeature;
+
+@Mixin(StructuresConfig.class)
+public interface StructuresConfigAccessor {
+	@Mutable
+	@Accessor("DEFAULT_STRUCTURES")
+	static void setDefaultStructures(ImmutableMap<StructureFeature<?>, StructureConfig> defaultStructures) {
+		throw new AssertionError("Untransformed accessor");
+	}
+}
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
new file mode 100644
index 000000000..f92380d76
--- /dev/null
+++ b/fabric-structure-api-v1/src/main/resources/fabric-structure-api-v1.mixins.json
@@ -0,0 +1,18 @@
+{
+  "required": true,
+  "package": "net.fabricmc.fabric.mixin.structure",
+  "compatibilityLevel": "JAVA_8",
+  "mixins": [
+    "BiomeAccessor",
+    "FlatChunkGeneratorConfigAccessor",
+    "MixinChunkGeneratorSettings",
+    "MixinStructuresConfig",
+    "StructureFeatureAccessor",
+    "StructuresConfigAccessor"
+  ],
+  "client": [
+  ],
+  "injectors": {
+    "defaultRequire": 1
+  }
+}
diff --git a/fabric-structure-api-v1/src/main/resources/fabric.mod.json b/fabric-structure-api-v1/src/main/resources/fabric.mod.json
new file mode 100644
index 000000000..7e597d4d9
--- /dev/null
+++ b/fabric-structure-api-v1/src/main/resources/fabric.mod.json
@@ -0,0 +1,26 @@
+{
+  "schemaVersion": 1,
+  "id": "fabric-structure-api-v1",
+  "name": "Fabric Structure API (v1)",
+  "version": "${version}",
+  "environment": "*",
+  "license": "Apache-2.0",
+  "icon": "assets/fabric-structure-api-v1/icon.png",
+  "contact": {
+    "homepage": "https://fabricmc.net",
+    "irc": "irc://irc.esper.net:6667/fabric",
+    "issues": "https://github.com/FabricMC/fabric/issues",
+    "sources": "https://github.com/FabricMC/fabric"
+  },
+  "authors": [
+    "FabricMC"
+  ],
+  "depends": {
+    "fabricloader": ">=0.8.0",
+    "fabric-api-base": "*"
+  },
+  "description": "Hooks for registering custom structures.",
+  "mixins": [
+    "fabric-structure-api-v1.mixins.json"
+  ]
+}
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
new file mode 100644
index 000000000..31b42ea38
--- /dev/null
+++ b/fabric-structure-api-v1/src/testmod/java/net/fabricmc/fabric/test/structure/StructureTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.test.structure;
+
+import java.util.Random;
+
+import com.mojang.serialization.Codec;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import net.minecraft.block.Blocks;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.structure.StructureManager;
+import net.minecraft.structure.StructurePieceType;
+import net.minecraft.structure.StructurePieceWithDimensions;
+import net.minecraft.structure.StructureStart;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.math.BlockBox;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.ChunkPos;
+import net.minecraft.util.registry.DynamicRegistryManager;
+import net.minecraft.util.registry.Registry;
+import net.minecraft.world.Heightmap;
+import net.minecraft.world.StructureWorldAccess;
+import net.minecraft.world.biome.Biome;
+import net.minecraft.world.gen.GenerationStep;
+import net.minecraft.world.gen.StructureAccessor;
+import net.minecraft.world.gen.chunk.ChunkGenerator;
+import net.minecraft.world.gen.feature.ConfiguredStructureFeature;
+import net.minecraft.world.gen.feature.DefaultFeatureConfig;
+import net.minecraft.world.gen.feature.StructureFeature;
+
+import net.fabricmc.fabric.api.structure.v1.FabricStructureBuilder;
+
+public class StructureTest {
+	private static final Logger LOGGER = LogManager.getLogger();
+
+	public static final StructureFeature<DefaultFeatureConfig> STRUCTURE = new TestStructureFeature(DefaultFeatureConfig.CODEC);
+	public static final ConfiguredStructureFeature<DefaultFeatureConfig, ? extends StructureFeature<DefaultFeatureConfig>> CONFIGURED_STRUCTURE = STRUCTURE.configure(new DefaultFeatureConfig());
+	public static final StructurePieceType PIECE = TestStructureGenerator::new;
+
+	static {
+		LOGGER.info("Registering test structure");
+		FabricStructureBuilder.create(new Identifier("fabric", "test_structure"), STRUCTURE)
+				.step(GenerationStep.Feature.SURFACE_STRUCTURES)
+				.defaultConfig(32, 8, 12345)
+				.superflatFeature(CONFIGURED_STRUCTURE)
+				.adjustsSurface()
+				.register();
+		Registry.register(Registry.STRUCTURE_PIECE, new Identifier("fabric", "test_structure_piece"), PIECE);
+	}
+
+	public static class TestStructureFeature extends StructureFeature<DefaultFeatureConfig> {
+		public TestStructureFeature(Codec<DefaultFeatureConfig> codec) {
+			super(codec);
+		}
+
+		@Override
+		public StructureStartFactory<DefaultFeatureConfig> getStructureStartFactory() {
+			return Start::new;
+		}
+
+		public static class Start extends StructureStart<DefaultFeatureConfig> {
+			public Start(StructureFeature<DefaultFeatureConfig> feature, int chunkX, int chunkZ, BlockBox box, int references, long seed) {
+				super(feature, chunkX, chunkZ, box, references, seed);
+			}
+
+			@Override
+			public void init(DynamicRegistryManager dynamicRegistryManager, ChunkGenerator chunkGenerator, StructureManager structureManager, int chunkX, int chunkZ, Biome biome, DefaultFeatureConfig featureConfig) {
+				int blockX = chunkX * 16;
+				int blockZ = chunkZ * 16;
+				int blockY = chunkGenerator.getHeight(blockX, blockZ, Heightmap.Type.WORLD_SURFACE_WG);
+
+				TestStructureGenerator generator = new TestStructureGenerator(random, blockX, blockY, blockZ);
+				this.children.add(generator);
+				setBoundingBoxFromChildren();
+			}
+		}
+	}
+
+	public static class TestStructureGenerator extends StructurePieceWithDimensions {
+		protected TestStructureGenerator(Random random, int x, int y, int z) {
+			super(PIECE, random, x, y, z, 48, 16, 48);
+		}
+
+		protected TestStructureGenerator(StructureManager structureManager, CompoundTag compoundTag) {
+			super(PIECE, compoundTag);
+		}
+
+		@Override
+		public boolean generate(StructureWorldAccess structureWorldAccess, StructureAccessor structureAccessor, ChunkGenerator chunkGenerator, Random random, BlockBox boundingBox, ChunkPos chunkPos, BlockPos blockPos) {
+			for (int x = 0; x < 48; x++) {
+				for (int z = 0; z < 48; z++) {
+					for (int y = 0; y < 16; y++) {
+						this.addBlock(structureWorldAccess, Blocks.DIAMOND_BLOCK.getDefaultState(), x, y, z, boundingBox);
+					}
+				}
+			}
+
+			return true;
+		}
+	}
+}
diff --git a/fabric-structure-api-v1/src/testmod/java/net/fabricmc/fabric/test/structure/mixin/MixinDefaultBiomeCreator.java b/fabric-structure-api-v1/src/testmod/java/net/fabricmc/fabric/test/structure/mixin/MixinDefaultBiomeCreator.java
new file mode 100644
index 000000000..d23a97cfb
--- /dev/null
+++ b/fabric-structure-api-v1/src/testmod/java/net/fabricmc/fabric/test/structure/mixin/MixinDefaultBiomeCreator.java
@@ -0,0 +1,38 @@
+/*
+ * 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.test.structure.mixin;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.ModifyVariable;
+
+import net.minecraft.world.biome.DefaultBiomeCreator;
+import net.minecraft.world.biome.GenerationSettings;
+
+import net.fabricmc.fabric.test.structure.StructureTest;
+
+@Mixin(DefaultBiomeCreator.class)
+public class MixinDefaultBiomeCreator {
+	@ModifyVariable(method = "createPlains", ordinal = 0, at = @At(value = "STORE", ordinal = 0))
+	private static GenerationSettings.Builder addCustomStructure(GenerationSettings.Builder builder, boolean sunflower) {
+		if (!sunflower) {
+			builder.structureFeature(StructureTest.CONFIGURED_STRUCTURE);
+		}
+
+		return builder;
+	}
+}
diff --git a/fabric-structure-api-v1/src/testmod/resources/fabric-structure-api-v1-testmod.mixins.json b/fabric-structure-api-v1/src/testmod/resources/fabric-structure-api-v1-testmod.mixins.json
new file mode 100644
index 000000000..22c796648
--- /dev/null
+++ b/fabric-structure-api-v1/src/testmod/resources/fabric-structure-api-v1-testmod.mixins.json
@@ -0,0 +1,13 @@
+{
+  "required": true,
+  "package": "net.fabricmc.fabric.test.structure.mixin",
+  "compatibilityLevel": "JAVA_8",
+  "mixins": [
+    "MixinDefaultBiomeCreator"
+  ],
+  "client": [
+  ],
+  "injectors": {
+    "defaultRequire": 1
+  }
+}
diff --git a/fabric-structure-api-v1/src/testmod/resources/fabric.mod.json b/fabric-structure-api-v1/src/testmod/resources/fabric.mod.json
new file mode 100644
index 000000000..13256fba5
--- /dev/null
+++ b/fabric-structure-api-v1/src/testmod/resources/fabric.mod.json
@@ -0,0 +1,14 @@
+{
+  "schemaVersion": 1,
+  "id": "fabric-structure-api-v1-testmod",
+  "name": "Fabric Structure API (v1) Test Mod",
+  "version": "1.0.0",
+  "environment": "*",
+  "license": "Apache-2.0",
+  "depends": {
+    "fabric-structure-api-v1": "*"
+  },
+  "mixins": [
+    "fabric-structure-api-v1-testmod.mixins.json"
+  ]
+}
diff --git a/settings.gradle b/settings.gradle
index 7e536e231..cb2c4cc51 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -48,6 +48,7 @@ include 'fabric-rendering-data-attachment-v1'
 include 'fabric-rendering-fluids-v1'
 include 'fabric-resource-loader-v0'
 include 'fabric-screen-handler-api-v1'
+include 'fabric-structure-api-v1'
 include 'fabric-tag-extensions-v0'
 include 'fabric-textures-v0'
 include 'fabric-tool-attribute-api-v1'