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'