Move biome modification into the MinecraftServer constructor ()

* Move biome modification into the MinecraftServer constructor to have access to the actual level generation settings for the level.

* Move biome modification into the MinecraftServer constructor to have access to the actual level generation settings for the level.

* Move biome modification into the MinecraftServer constructor to have access to the actual level generation settings for the level.

* Ensure the feature ordering in BiomeSource is updated after modifying biomes.

* Don't use var

* Use the opportunity to check the real chunk generators for
the specified structure.
This commit is contained in:
shartte 2022-01-30 19:57:57 +01:00 committed by GitHub
parent e66b59e98c
commit b02b2d58e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 126 additions and 166 deletions

View file

@ -20,6 +20,7 @@ import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import net.minecraft.world.dimension.DimensionOptions;
import net.minecraft.world.gen.feature.PlacedFeature;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
@ -30,8 +31,6 @@ import net.fabricmc.fabric.impl.biome.modification.BuiltInRegistryKeys;
/**
* Context given to a biome selector for deciding whether it applies to a biome or not.
*
* <p><b>Experimental feature</b>, may be removed or changed without further notice.
*/
public interface BiomeSelectionContext {
RegistryKey<Biome> getBiomeKey();
@ -114,7 +113,7 @@ public interface BiomeSelectionContext {
/**
* Returns true if the given built-in configured structure from {@link net.minecraft.util.registry.BuiltinRegistries}
* can start in this biome.
* can start in this biome in any of the chunk generators used by the current world-save.
*
* <p>This method is intended for use with the Vanilla configured structures found in {@link net.minecraft.world.gen.feature.ConfiguredStructureFeatures}.
*/
@ -124,8 +123,8 @@ public interface BiomeSelectionContext {
}
/**
* Returns true if the configured structure with the given key can start in this biome in any configured
* chunk generator.
* Returns true if the configured structure with the given key can start in this biome in any chunk generator
* used by the current world-save.
*/
boolean hasStructure(RegistryKey<ConfiguredStructureFeature<?, ?>> key);
@ -135,4 +134,12 @@ public interface BiomeSelectionContext {
* from this biomes feature list.
*/
Optional<RegistryKey<ConfiguredStructureFeature<?, ?>>> getStructureKey(ConfiguredStructureFeature<?, ?> configuredStructure);
/**
* Tries to determine whether this biome generates in a specific dimension, based on the {@link net.minecraft.world.gen.GeneratorOptions}
* used by the current world-save.
*
* <p>If no dimension options exist for the given dimension key, <code>false</code> is returned.
*/
boolean canGenerateIn(RegistryKey<DimensionOptions> dimensionKey);
}

View file

@ -31,8 +31,7 @@ import net.minecraft.util.registry.BuiltinRegistries;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.SpawnSettings;
import net.fabricmc.fabric.impl.biome.OverworldBiomeData;
import net.minecraft.world.dimension.DimensionOptions;
/**
* Provides several convenient biome selectors that can be used with {@link BiomeModifications}.
@ -73,7 +72,7 @@ public final class BiomeSelectors {
* assuming Vanilla's default biome source is used.
*/
public static Predicate<BiomeSelectionContext> foundInOverworld() {
return context -> OverworldBiomeData.canGenerateInOverworld(context.getBiomeKey());
return context -> context.canGenerateIn(DimensionOptions.OVERWORLD);
}
/**
@ -83,7 +82,7 @@ public final class BiomeSelectors {
* <p>This selector will also match modded biomes that have been added to the nether using {@link NetherBiomes}.
*/
public static Predicate<BiomeSelectionContext> foundInTheNether() {
return context -> NetherBiomes.canGenerateInNether(context.getBiomeKey());
return context -> context.canGenerateIn(DimensionOptions.NETHER);
}
/**
@ -91,7 +90,7 @@ public final class BiomeSelectors {
* assuming Vanilla's default End biome source is used.
*/
public static Predicate<BiomeSelectionContext> foundInTheEnd() {
return context -> TheEndBiomes.canGenerateInTheEnd(context.getBiomeKey());
return context -> context.canGenerateIn(DimensionOptions.END);
}
/**

View file

@ -1,54 +0,0 @@
/*
* 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.biome;
import java.util.HashSet;
import java.util.Set;
import org.jetbrains.annotations.ApiStatus;
import net.minecraft.util.registry.BuiltinRegistries;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.source.MultiNoiseBiomeSource;
/**
* Internal data for modding Vanilla's {@link MultiNoiseBiomeSource.Preset#OVERWORLD}.
*/
@ApiStatus.Internal
public final class OverworldBiomeData {
private static final Set<RegistryKey<Biome>> OVERWORLD_BIOMES = new HashSet<>();
private OverworldBiomeData() {
}
public static boolean canGenerateInOverworld(RegistryKey<Biome> biome) {
if (OVERWORLD_BIOMES.isEmpty()) {
MultiNoiseBiomeSource source = MultiNoiseBiomeSource.Preset.OVERWORLD.getBiomeSource(BuiltinRegistries.BIOME);
for (Biome netherBiome : source.getBiomes()) {
BuiltinRegistries.BIOME.getKey(netherBiome).ifPresent(OVERWORLD_BIOMES::add);
}
}
return OVERWORLD_BIOMES.contains(biome);
}
public static void clearOverworldCache() {
OVERWORLD_BIOMES.clear();
}
}

View file

@ -21,11 +21,9 @@ import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import com.google.common.base.Stopwatch;
import org.apache.logging.log4j.LogManager;
@ -38,6 +36,9 @@ import net.minecraft.util.registry.DynamicRegistryManager;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.source.BiomeSource;
import net.minecraft.world.dimension.DimensionOptions;
import net.minecraft.world.level.LevelProperties;
import net.fabricmc.fabric.api.biome.v1.BiomeModificationContext;
import net.fabricmc.fabric.api.biome.v1.BiomeSelectionContext;
@ -104,14 +105,14 @@ public class BiomeModificationImpl {
}
@SuppressWarnings("ConstantConditions")
public void modifyBiomes(DynamicRegistryManager.Impl impl) {
public void finalizeWorldGen(DynamicRegistryManager.Impl impl, LevelProperties levelProperties) {
Stopwatch sw = Stopwatch.createStarted();
// Vanilla will sometimes call RegistryOps.of twice for the same dynamic registry manager,
// which usually will not result in a reload of objects, since it reuses existing objects
// from the manager when they are being referenced.
BiomeModificationTracker modificationTracker = (BiomeModificationTracker) (Object) impl;
Set<Biome> modifiedBiomes = modificationTracker.fabric_getModifiedBiomes();
// Now that we apply biome modifications inside the MinecraftServer constructor, we should only ever do
// this once for a dynamic registry manager. Marking the dynamic registry manager as modified ensures a crash
// if the precondition is violated.
BiomeModificationMarker modificationTracker = (BiomeModificationMarker) (Object) impl;
modificationTracker.fabric_markModified();
Registry<Biome> biomes = impl.get(Registry.BIOME_KEY);
@ -120,7 +121,7 @@ public class BiomeModificationImpl {
List<RegistryKey<Biome>> keys = biomes.getEntries().stream()
.map(Map.Entry::getKey)
.sorted(Comparator.comparingInt(key -> biomes.getRawId(biomes.getOrThrow(key))))
.collect(Collectors.toList());
.toList();
List<ModifierRecord> sortedModifiers = getSortedModifiers();
@ -131,15 +132,11 @@ public class BiomeModificationImpl {
for (RegistryKey<Biome> key : keys) {
Biome biome = biomes.getOrThrow(key);
if (!modifiedBiomes.add(biome)) {
continue; // Do not modify the same biome twice
}
biomesProcessed++;
// Make a copy of the biome to allow selection contexts to see it unmodified,
// But do so only once it's known anything wants to modify the biome at all
BiomeSelectionContext context = new BiomeSelectionContextImpl(impl, key, biome);
BiomeSelectionContext context = new BiomeSelectionContextImpl(impl, levelProperties, key, biome);
BiomeModificationContextImpl modificationContext = null;
for (ModifierRecord modifier : sortedModifiers) {
@ -164,6 +161,14 @@ public class BiomeModificationImpl {
}
if (biomesProcessed > 0) {
// Rebuild caches within biome sources after modifying feature lists
for (DimensionOptions dimension : levelProperties.getGeneratorOptions().getDimensions()) {
// The Biome source has a total ordering of feature generation that might have changed
// by us adding or removing features from biomes.
BiomeSource biomeSource = dimension.getChunkGenerator().getBiomeSource();
biomeSource.field_34469 = biomeSource.method_39525(new ArrayList<>(biomeSource.getBiomes()), true);
}
LOGGER.info("Applied {} biome modifications to {} of {} new biomes in {}", modifiersApplied, biomesChanged,
biomesProcessed, sw);
}

View file

@ -16,17 +16,13 @@
package net.fabricmc.fabric.impl.biome.modification;
import java.util.Set;
import org.jetbrains.annotations.ApiStatus;
import net.minecraft.world.biome.Biome;
/**
* Prevents double-modification of biomes in the same dynamic registry manager from occuring and fails-fast
* Prevents double-modification of biomes in the same dynamic registry manager from occurring and fails-fast
* if it does occur.
*/
@ApiStatus.Internal
public interface BiomeModificationTracker {
Set<Biome> fabric_getModifiedBiomes();
public interface BiomeModificationMarker {
void fabric_markModified();
}

View file

@ -16,33 +16,35 @@
package net.fabricmc.fabric.impl.biome.modification;
import java.util.Map;
import java.util.Optional;
import org.jetbrains.annotations.ApiStatus;
import net.minecraft.world.gen.feature.PlacedFeature;
import net.minecraft.util.registry.DynamicRegistryManager;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.gen.chunk.ChunkGeneratorSettings;
import net.minecraft.world.dimension.DimensionOptions;
import net.minecraft.world.gen.chunk.StructuresConfig;
import net.minecraft.world.gen.feature.ConfiguredFeature;
import net.minecraft.world.gen.feature.ConfiguredStructureFeature;
import net.minecraft.world.gen.feature.PlacedFeature;
import net.minecraft.world.level.LevelProperties;
import net.fabricmc.fabric.api.biome.v1.BiomeSelectionContext;
@ApiStatus.Internal
public class BiomeSelectionContextImpl implements BiomeSelectionContext {
private final DynamicRegistryManager dynamicRegistries;
private final LevelProperties levelProperties;
private final RegistryKey<Biome> key;
private final Biome biome;
private final DynamicRegistryManager dynamicRegistries;
public BiomeSelectionContextImpl(DynamicRegistryManager dynamicRegistries, RegistryKey<Biome> key, Biome biome) {
public BiomeSelectionContextImpl(DynamicRegistryManager dynamicRegistries, LevelProperties levelProperties, RegistryKey<Biome> key, Biome biome) {
this.dynamicRegistries = dynamicRegistries;
this.levelProperties = levelProperties;
this.key = key;
this.biome = biome;
this.dynamicRegistries = dynamicRegistries;
}
@Override
@ -75,13 +77,10 @@ public class BiomeSelectionContextImpl implements BiomeSelectionContext {
return false;
}
// Since the biome->structure mapping is now stored in the chunk generator configurations, it's no longer
// trivial to detect if a given biome _could_ spawn a structure. To still support the API, we now do this on a
// per-chunk-generator level.
Registry<ChunkGeneratorSettings> chunkGeneratorSettings = dynamicRegistries.get(Registry.CHUNK_GENERATOR_SETTINGS_KEY);
for (Map.Entry<RegistryKey<ChunkGeneratorSettings>, ChunkGeneratorSettings> entry : chunkGeneratorSettings.getEntries()) {
StructuresConfig structuresConfig = entry.getValue().getStructuresConfig();
// Since the biome->structure mapping is now stored in the chunk generator configuration, we check every
// chunk generator used by the current world-save.
for (DimensionOptions dimension : levelProperties.getGeneratorOptions().getDimensions()) {
StructuresConfig structuresConfig = dimension.getChunkGenerator().getStructuresConfig();
if (structuresConfig.getConfiguredStructureFeature(instance.feature).get(instance).contains(getBiomeKey())) {
return true;
@ -96,4 +95,15 @@ public class BiomeSelectionContextImpl implements BiomeSelectionContext {
Registry<ConfiguredStructureFeature<?, ?>> registry = dynamicRegistries.get(Registry.CONFIGURED_STRUCTURE_FEATURE_KEY);
return registry.getKey(configuredStructure);
}
@Override
public boolean canGenerateIn(RegistryKey<DimensionOptions> dimensionKey) {
DimensionOptions dimension = levelProperties.getGeneratorOptions().getDimensions().get(dimensionKey);
if (dimension == null) {
return false;
}
return dimension.getChunkGenerator().getBiomeSource().getBiomes().contains(biome);
}
}

View file

@ -16,28 +16,28 @@
package net.fabricmc.fabric.mixin.biome.modification;
import java.util.HashSet;
import java.util.Set;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import net.minecraft.util.registry.DynamicRegistryManager;
import net.minecraft.world.biome.Biome;
import net.fabricmc.fabric.impl.biome.modification.BiomeModificationTracker;
import net.fabricmc.fabric.impl.biome.modification.BiomeModificationMarker;
/**
* This Mixin allows us to track which biomes already ran through {@link net.fabricmc.fabric.impl.biome.modification.BiomeModificationImpl}
* on a per-DynamicRegistryManager basis.
* This Mixin allows us to keep backup copies of biomes for
* {@link net.fabricmc.fabric.impl.biome.modification.BiomeModificationImpl} on a per-DynamicRegistryManager basis.
*/
@Mixin(DynamicRegistryManager.Impl.class)
public class DynamicRegistryManagerImplMixin implements BiomeModificationTracker {
public class DynamicRegistryManagerImplMixin implements BiomeModificationMarker {
@Unique
private final Set<Biome> modifiedBiomes = new HashSet<>();
private boolean modified;
@Override
public Set<Biome> fabric_getModifiedBiomes() {
return this.modifiedBiomes;
public void fabric_markModified() {
if (modified) {
throw new IllegalStateException("This dynamic registries instance has already been modified");
}
modified = true;
}
}

View file

@ -0,0 +1,48 @@
/*
* 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.biome.modification;
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.server.MinecraftServer;
import net.minecraft.util.registry.DynamicRegistryManager;
import net.minecraft.world.SaveProperties;
import net.minecraft.world.level.LevelProperties;
import net.fabricmc.fabric.impl.biome.modification.BiomeModificationImpl;
@Mixin(MinecraftServer.class)
public class MinecraftServerMixin {
@Shadow
private DynamicRegistryManager.Impl registryManager;
@Shadow
private SaveProperties saveProperties;
@Inject(method = "<init>", at = @At(value = "RETURN"))
private void finalizeWorldGen(CallbackInfo ci) {
if (!(saveProperties instanceof LevelProperties levelProperties)) {
throw new RuntimeException("Incompatible SaveProperties passed to MinecraftServer: " + saveProperties);
}
BiomeModificationImpl.INSTANCE.finalizeWorldGen(registryManager, levelProperties);
}
}

View file

@ -1,56 +0,0 @@
/*
* 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.biome.modification;
import com.mojang.serialization.DynamicOps;
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.resource.ResourceManager;
import net.minecraft.util.dynamic.RegistryOps;
import net.minecraft.util.registry.DynamicRegistryManager;
import net.fabricmc.fabric.impl.biome.modification.BiomeModificationImpl;
/**
* This Mixin hooks int the creation of RegistryOps, which will currently load data pack contents into
* the supplied dynamic registry manager, making it the point at which we should apply biome modifications.
*
* <p>There is generally the following order:
* <ol>
* <li>{@link DynamicRegistryManager#create()} is used to create a dynamic registry manager with just
* entries from {@link net.minecraft.util.registry.BuiltinRegistries}</li>
* <li>Sometimes, Vanilla Minecraft will stop here, and use the {@link DynamicRegistryManager} as-is (examples: server.properties parsing, world creation screen).</li>
* <li>{@link RegistryOps#ofLoaded(DynamicOps, ResourceManager, DynamicRegistryManager)} gets called with the manager, and a
* resource manager that contains the loaded data packs. This will pull in all worldgen objects from datapacks into the
* dynamic registry manager.</li>
* <li>After the worldgen objects are pulled in from the datapacks, this mixin will call the biome modification callback.</li>
* <li>In most cases, Vanilla will stop here and now use the dynamic registy manager to instantiate a server.</li>
* <li>Sometimes, i.e. when using the "re-create world feature", and a datapack throws an error, Vanilla will sometimes
* repeat the {@link RegistryOps#ofLoaded(DynamicOps, ResourceManager, DynamicRegistryManager)} call on the same
* dynamic registry manager. We guard against this using {@link net.fabricmc.fabric.impl.biome.modification.BiomeModificationTracker}.</li>
* </ol>
*/
@Mixin(RegistryOps.class)
public class RegistryOpsMixin {
@Inject(method = "ofLoaded", at = @At("RETURN"))
private static <T> void afterCreation(DynamicOps<T> dynamicOps, ResourceManager resourceManager, DynamicRegistryManager impl, CallbackInfoReturnable<RegistryOps<T>> cir) {
BiomeModificationImpl.INSTANCE.modifyBiomes((DynamicRegistryManager.Impl) impl);
}
}

View file

@ -3,6 +3,11 @@ accessible class net/minecraft/world/biome/Biome$Weather
accessible field net/minecraft/world/gen/chunk/StructuresConfig configuredStructures Lcom/google/common/collect/ImmutableMap;
mutable field net/minecraft/world/gen/chunk/StructuresConfig configuredStructures Lcom/google/common/collect/ImmutableMap;
# Rebuilding biome source feature lists
accessible method net/minecraft/world/biome/source/BiomeSource method_39525 (Ljava/util/List;Z)Ljava/util/List;
accessible field net/minecraft/world/biome/source/BiomeSource field_34469 Ljava/util/List;
mutable field net/minecraft/world/biome/source/BiomeSource field_34469 Ljava/util/List;
# Top-Level Biome Fields Access
accessible field net/minecraft/world/biome/Biome weather Lnet/minecraft/world/biome/Biome$Weather;
accessible field net/minecraft/world/biome/Biome generationSettings Lnet/minecraft/world/biome/GenerationSettings;

View file

@ -4,7 +4,7 @@
"compatibilityLevel": "JAVA_16",
"mixins": [
"modification.DynamicRegistryManagerImplMixin",
"modification.RegistryOpsMixin",
"modification.MinecraftServerMixin",
"MixinMultiNoiseBiomeSource",
"MixinTheEndBiomeSource"
],