Skip to content

Commit

Permalink
Add compatibility for TerraBlender's new End biome functionality.
Browse files Browse the repository at this point in the history
- Add compatibility for TerraBlender's new End biome functionality
- Smooth out vanilla End biome transitions
* Prevent FAPI from double-placing biomes
  • Loading branch information
gniftygnome committed Jan 28, 2024
1 parent bcfef15 commit 4cea9b7
Show file tree
Hide file tree
Showing 18 changed files with 420 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
import java.util.Optional;

public class BiomeCoordinator {
public static final DimensionBiomePlacement END = new EndBiomePlacement();
public static final DimensionBiomePlacement NETHER = new NetherBiomePlacement();
public static final DimensionBiomePlacement OVERWORLD = new OverworldBiomePlacement();
public static final EndBiomePlacement END = new EndBiomePlacement();
public static final NetherBiomePlacement NETHER = new NetherBiomePlacement();
public static final OverworldBiomePlacement OVERWORLD = new OverworldBiomePlacement();
private static boolean registeredWithTerrablender = false;

private static BiolithState END_STATE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ public void writeBiomeParameters(Consumer<Pair<MultiNoiseUtil.NoiseHypercube, Re
.forEach(biome -> parameters.accept(Pair.of(OUT_OF_RANGE, biome)));
}

// TODO: This should be replaced with a more robust noise implementation, perhaps also more similar to vanilla.
public MultiNoiseUtil.NoiseValuePoint sampleEndNoise(int x, int y, int z, MultiNoiseUtil.MultiNoiseSampler originalNoise, RegistryEntry<Biome> originalBiome) {
double erosion = originalNoise.erosion().sample(new DensityFunction.UnblendedNoisePos(
(ChunkSectionPos.getSectionCoord(BiomeCoords.toBlock(x)) * 2 + 1) * 8,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,11 @@ public interface InterfaceBiomeSource {
default @Nullable MultiNoiseUtil.Entries<RegistryEntry<Biome>> biolith$getBiomeEntries() {
return null;
}

default boolean biolith$getBypass() {
return false;
}

default void biolith$setBypass(boolean value) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.terraformersmc.biolith.impl.Biolith;
import com.terraformersmc.biolith.impl.biome.BiolithFittestNodes;
import com.terraformersmc.biolith.impl.biome.BiomeCoordinator;
import com.terraformersmc.biolith.impl.biome.EndBiomePlacement;
import com.terraformersmc.biolith.impl.biome.SubBiomeMatcherImpl;
import com.terraformersmc.biolith.impl.biome.*;
import com.terraformersmc.biolith.impl.compat.BiolithCompat;
import com.terraformersmc.biolith.impl.compat.VanillaCompat;
import com.terraformersmc.biolith.impl.platform.Services;
Expand All @@ -20,15 +17,11 @@
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkSectionPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeKeys;
import net.minecraft.world.biome.source.BiomeCoords;
import net.minecraft.world.biome.source.BiomeSource;
import net.minecraft.world.biome.source.util.MultiNoiseUtil;
import net.minecraft.world.dimension.DimensionTypes;
import net.minecraft.world.gen.densityfunction.DensityFunction;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector2f;

Expand Down Expand Up @@ -120,32 +113,21 @@ private static int atBlockPos(CommandContext<ServerCommandSource> context, Block
replacementScale = Biolith.getConfigManager().getGeneralConfig().getNetherReplacementScale();
describeBiomeData = BiomeCoordinator.NETHER.getBiomeData(biomeX, biomeY, biomeZ, noisePoint, fittestNodes);
} else if (world.getDimensionEntry().matchesKey(DimensionTypes.THE_END)) {
RegistryEntry<Biome> original;

// We can't call the unadulterated End getBiome, so fake up something similar.
// TODO: Try harder to find some way to get this from vanilla instead.
if (MathHelper.square(ChunkSectionPos.getSectionCoord(pos.getX())) +
MathHelper.square(ChunkSectionPos.getSectionCoord(pos.getZ())) <= 4096L) {
original = BiomeCoordinator.getBiomeLookupOrThrow().getOrThrow(BiomeKeys.THE_END);
} else {
double erosion = noise.erosion().sample(
new DensityFunction.UnblendedNoisePos(
(ChunkSectionPos.getSectionCoord(pos.getX()) * 2 + 1) * 8,
pos.getY(),
(ChunkSectionPos.getSectionCoord(pos.getZ()) * 2 + 1) * 8));
if (erosion > 0.25) {
original = BiomeCoordinator.getBiomeLookupOrThrow().getOrThrow(BiomeKeys.END_HIGHLANDS);
} else if (erosion >= -0.0625) {
original = BiomeCoordinator.getBiomeLookupOrThrow().getOrThrow(BiomeKeys.END_MIDLANDS);
} else if (erosion < -0.21875) {
original = BiomeCoordinator.getBiomeLookupOrThrow().getOrThrow(BiomeKeys.SMALL_END_ISLANDS);
} else {
original = BiomeCoordinator.getBiomeLookupOrThrow().getOrThrow(BiomeKeys.END_BARRENS);
}
RegistryEntry<Biome> original = VanillaCompat.getOriginalEndBiome(biomeX, biomeY, biomeZ, noise);
noisePoint = BiomeCoordinator.END.sampleEndNoise(biomeX, biomeY, biomeZ, noise, original);
vanillaFittestNodes = new BiolithFittestNodes<>(
new MultiNoiseUtil.SearchTree.TreeLeafNode<>(DimensionBiomePlacement.OUT_OF_RANGE,
VanillaCompat.getOriginalEndBiome(biomeX, biomeY, biomeZ, noise)), 0L);
if (BiolithCompat.COMPAT_TERRABLENDER) {
biomeSource.biolith$setBypass(true);
fittestNodes = terrablenderFittestNodes = new BiolithFittestNodes<>(
new MultiNoiseUtil.SearchTree.TreeLeafNode<>(DimensionBiomePlacement.OUT_OF_RANGE,
biomeSource.getBiome(biomeX, biomeY, biomeZ, noise)), 0L);
biomeSource.biolith$setBypass(false);
}
if (fittestNodes == null) {
fittestNodes = vanillaFittestNodes;
}

noisePoint = ((EndBiomePlacement) BiomeCoordinator.END).sampleEndNoise(biomeX, biomeY, biomeZ, noise, original);
fittestNodes = vanillaFittestNodes = VanillaCompat.getEndBiome(noisePoint, biomeEntries, original);
replacementNoise = BiomeCoordinator.END.getLocalNoise(biomeX, biomeY, biomeZ);
replacementScale = Biolith.getConfigManager().getGeneralConfig().getEndReplacementScale();
describeBiomeData = BiomeCoordinator.END.getBiomeData(biomeX, biomeY, biomeZ, noisePoint, fittestNodes);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.terraformersmc.biolith.impl.compat;

import com.terraformersmc.biolith.impl.biome.BiolithFittestNodes;
import com.terraformersmc.biolith.impl.biome.BiomeCoordinator;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.util.math.ChunkSectionPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeKeys;
import net.minecraft.world.biome.source.BiomeCoords;
import net.minecraft.world.biome.source.util.MultiNoiseUtil;
import net.minecraft.world.gen.densityfunction.DensityFunction;

public class VanillaCompat {
@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -56,6 +61,33 @@ public static BiolithFittestNodes<RegistryEntry<Biome>> getEndBiome(MultiNoiseUt
return fittestNodes;
}

// This is a smoothed version of vanilla's End biome placement.
public static RegistryEntry<Biome> getOriginalEndBiome(int biomeX, int biomeY, int biomeZ, MultiNoiseUtil.MultiNoiseSampler noise) {
RegistryEntry<Biome> biomeEntry;

int x = BiomeCoords.toBlock(biomeX);
int y = BiomeCoords.toBlock(biomeY);
int z = BiomeCoords.toBlock(biomeZ);

if (MathHelper.square((long) ChunkSectionPos.getSectionCoord(x)) +
MathHelper.square((long) ChunkSectionPos.getSectionCoord(z)) <= 4096L) {
biomeEntry = BiomeCoordinator.getBiomeLookupOrThrow().getOrThrow(BiomeKeys.THE_END);
} else {
double erosion = noise.erosion().sample(new DensityFunction.UnblendedNoisePos(x, y, z));
if (erosion > 0.25) {
biomeEntry = BiomeCoordinator.getBiomeLookupOrThrow().getOrThrow(BiomeKeys.END_HIGHLANDS);
} else if (erosion >= -0.0625) {
biomeEntry = BiomeCoordinator.getBiomeLookupOrThrow().getOrThrow(BiomeKeys.END_MIDLANDS);
} else if (erosion < -0.21875) {
biomeEntry = BiomeCoordinator.getBiomeLookupOrThrow().getOrThrow(BiomeKeys.SMALL_END_ISLANDS);
} else {
biomeEntry = BiomeCoordinator.getBiomeLookupOrThrow().getOrThrow(BiomeKeys.END_BARRENS);
}
}

return biomeEntry;
}

private static MultiNoiseUtil.NoiseHypercube createNoiseHypercube(MultiNoiseUtil.ParameterRange... parameters) {
assert parameters.length == 6;
return MultiNoiseUtil.createNoiseHypercube(parameters[0], parameters[1], parameters[2], parameters[3], parameters[4], parameters[5], 0L);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@

import com.google.common.collect.Streams;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.mojang.datafixers.util.Pair;
import com.terraformersmc.biolith.impl.biome.*;
import com.terraformersmc.biolith.impl.compat.BiolithCompat;
import com.terraformersmc.biolith.impl.compat.VanillaCompat;
import net.minecraft.registry.*;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.source.BiomeCoords;
import net.minecraft.world.biome.source.BiomeSource;
import net.minecraft.world.biome.source.TheEndBiomeSource;
import net.minecraft.world.biome.source.util.MultiNoiseUtil;
import net.minecraft.world.gen.densityfunction.DensityFunction;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
Expand All @@ -21,7 +25,7 @@
import java.util.List;
import java.util.stream.Stream;

@Mixin(value = TheEndBiomeSource.class, priority = 900)
@Mixin(TheEndBiomeSource.class)
public abstract class MixinTheEndBiomeSource extends BiomeSource {
private static RegistryEntryLookup<Biome> biolith$biomeLookup;
private static MultiNoiseUtil.Entries<RegistryEntry<Biome>> biolith$biomeEntries;
Expand Down Expand Up @@ -68,20 +72,31 @@ public abstract class MixinTheEndBiomeSource extends BiomeSource {
).distinct();
}

@Inject(method = "getBiome", at = @At("RETURN"), cancellable = true)
private void biolith$getBiome(int x, int y, int z, MultiNoiseUtil.MultiNoiseSampler noise, CallbackInfoReturnable<RegistryEntry<Biome>> cir) {
@ModifyReturnValue(method = "getBiome", at = @At("RETURN"))
@SuppressWarnings("unused")
private RegistryEntry<Biome> biolith$getBiome(RegistryEntry<Biome> original, int x, int y, int z, MultiNoiseUtil.MultiNoiseSampler noise) {
// For the End we go to some lengths to mock up a multi-noise placement regime.
// Still, we try to let other mods do whatever they do to place things in the End too.
RegistryEntry<Biome> original = cir.getReturnValue();

// Fake up a noise point for sub biome placement.
MultiNoiseUtil.NoiseValuePoint noisePoint = ((EndBiomePlacement) BiomeCoordinator.END).sampleEndNoise(x, y, z, noise, original);
MultiNoiseUtil.NoiseValuePoint noisePoint = BiomeCoordinator.END.sampleEndNoise(x, y, z, noise, original);

// Select noise biome
BiolithFittestNodes<RegistryEntry<Biome>> fittestNodes = VanillaCompat.getEndBiome(noisePoint, biolith$biomeEntries, original);

// Process any replacements or sub-biomes.
cir.setReturnValue(BiomeCoordinator.END.getReplacement(x, y, z, noisePoint, fittestNodes));
return BiomeCoordinator.END.getReplacement(x, y, z, noisePoint, fittestNodes);
}

@WrapOperation(method = "getBiome",
at = @At(
value = "NEW",
target = "net/minecraft/world/gen/densityfunction/DensityFunction$UnblendedNoisePos"
)
)
@SuppressWarnings("unused")
private DensityFunction.UnblendedNoisePos biolith$smoothEndNoise(int blockX, int blockY, int blockZ, Operation<DensityFunction.UnblendedNoisePos> original, int x, int y, int z) {
return (new DensityFunction.UnblendedNoisePos(BiomeCoords.toBlock(x), BiomeCoords.toBlock(y), BiomeCoords.toBlock(z)));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public class BiolithFabricMixinConfigPlugin implements IMixinConfigPlugin {
private static final Supplier<Boolean> TRUE = () -> true;

private static final Map<String, Supplier<Boolean>> CONDITIONS = ImmutableMap.of(
"com.terraformersmc.biolith.impl.mixin.MixinMBBiomeSource", () -> FabricLoader.getInstance().isModLoaded("modern_beta")
"com.terraformersmc.biolith.impl.mixin.MixinMBBiomeSource", () -> FabricLoader.getInstance().isModLoaded("modern_beta"),
"com.terraformersmc.biolith.impl.mixin.MixinTBTheEndBiomeSource", () -> FabricLoader.getInstance().isModLoaded("terrablender")
);

@Override
Expand All @@ -27,7 +28,6 @@ public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {

@Override
public void onLoad(String mixinPackage) {

}

@Override
Expand All @@ -37,7 +37,6 @@ public String getRefMapperConfig() {

@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {

}

@Override
Expand All @@ -47,11 +46,9 @@ public List<String> getMixins() {

@Override
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {

}

@Override
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,35 @@

@Mixin(TheEndBiomes.class)
public class MixinFapiTheEndBiomes {
@Inject(method = "addMainIslandBiome", at = @At("TAIL"))
@Inject(method = "addMainIslandBiome", at = @At("HEAD"), cancellable = true)
private static void biolith$scrapeEndMainIslandReplacement(RegistryKey<Biome> variant, double weight, CallbackInfo ci) {
BiomePlacement.replaceEnd(BiomeKeys.THE_END, variant, weight / (1d + weight));
ci.cancel();
}

@Inject(method = "addHighlandsBiome", at = @At("TAIL"))
@Inject(method = "addHighlandsBiome", at = @At("HEAD"), cancellable = true)
private static void biolith$scrapeEndHighlandsReplacement(RegistryKey<Biome> variant, double weight, CallbackInfo ci) {
BiomePlacement.replaceEnd(BiomeKeys.END_HIGHLANDS, variant, weight / (1d + weight));
ci.cancel();
}

@Inject(method = "addSmallIslandsBiome", at = @At("TAIL"))
@Inject(method = "addSmallIslandsBiome", at = @At("HEAD"), cancellable = true)
private static void biolith$scrapeEndSmallIslandsReplacement(RegistryKey<Biome> variant, double weight, CallbackInfo ci) {
BiomePlacement.replaceEnd(BiomeKeys.SMALL_END_ISLANDS, variant, weight / (1d + weight));
ci.cancel();
}

@Inject(method = "addMidlandsBiome", at = @At("TAIL"))
@Inject(method = "addMidlandsBiome", at = @At("HEAD"), cancellable = true)
private static void biolith$scrapeEndMidlandsReplacement(RegistryKey<Biome> highlands, RegistryKey<Biome> variant, double weight, CallbackInfo ci) {
BiomePlacement.addSubEnd(BiomeKeys.END_MIDLANDS, variant,
SubBiomeMatcher.of(SubBiomeMatcher.Criterion.ofAlternate(SubBiomeMatcher.CriterionTargets.ALTERNATE, highlands, BiomeKeys.END_HIGHLANDS, false)));
ci.cancel();
}

@Inject(method = "addBarrensBiome", at = @At("TAIL"))
@Inject(method = "addBarrensBiome", at = @At("HEAD"), cancellable = true)
private static void biolith$scrapeBarrensReplacement(RegistryKey<Biome> highlands, RegistryKey<Biome> variant, double weight, CallbackInfo ci) {
BiomePlacement.addSubEnd(BiomeKeys.END_BARRENS, variant,
SubBiomeMatcher.of(SubBiomeMatcher.Criterion.ofAlternate(SubBiomeMatcher.CriterionTargets.ALTERNATE, highlands, BiomeKeys.END_HIGHLANDS, false)));
ci.cancel();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.terraformersmc.biolith.impl.mixin;

import com.terraformersmc.biolith.impl.biome.BiolithFittestNodes;
import com.terraformersmc.biolith.impl.biome.BiomeCoordinator;
import com.terraformersmc.biolith.impl.compat.VanillaCompat;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.source.BiomeSource;
import net.minecraft.world.biome.source.TheEndBiomeSource;
import net.minecraft.world.biome.source.util.MultiNoiseUtil;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

/*
* This entire mixin is basically a work-around for TerraBlender's HEAD mixin and @Unique variables.
* If TerraBlender gets a proper API for End generation, maybe we can toss this in favor of something better.
* Unlike most Biolith overlays, this one may select our biomes with preference over TerraBlender's biomes.
*
* This mixin is duplicated for each loader because the platform services are not available at common mixin
* load and the refmaps for the platforms do not include the refs of mixins defined in the common project.
* TODO: Review this when upgrading to arch loom 1.5.
*
* Priority 900 places this mixin in front of both TerraBlender and our main TheEndBiomeSource mixin.
*/
@Mixin(value = TheEndBiomeSource.class, priority = 900)
public abstract class MixinTBTheEndBiomeSource extends BiomeSource {
@Unique
private static final ThreadLocal<Boolean> bypass = ThreadLocal.withInitial(() -> false);

public boolean biolith$getBypass() {
return bypass.get();
}

public void biolith$setBypass(boolean value) {
bypass.set(value);
}

@Inject(method = "getBiome", at = @At("HEAD"), cancellable = true)
private void biolith$getBiome(int x, int y, int z, MultiNoiseUtil.MultiNoiseSampler noise, CallbackInfoReturnable<RegistryEntry<Biome>> cir) {
// Allows us to call unmodified (by us) getBiome() to get TerraBlender values.
if (bypass.get()) {
return;
}

// Fetch whatever TerraBlender thinks the biome should be, which we will call the original biome.
bypass.set(true);
RegistryEntry<Biome> original = this.getBiome(x, y, z, noise);
bypass.set(false);

// Fake up a noise point for sub biome placement.
MultiNoiseUtil.NoiseValuePoint noisePoint = BiomeCoordinator.END.sampleEndNoise(x, y, z, noise, original);

// Select noise biome
BiolithFittestNodes<RegistryEntry<Biome>> fittestNodes = VanillaCompat.getEndBiome(noisePoint, this.biolith$getBiomeEntries(), original);

// Process any replacements or sub-biomes.
cir.setReturnValue(BiomeCoordinator.END.getReplacement(x, y, z, noisePoint, fittestNodes));
}

/*
* Under normal conditions, TerraBlender will have already canceled, but if bclib is present, it may not.
* This ensures our main getBiome() mixin will not be called when bypass is true.
*/
@Inject(method = "getBiome", at = @At("RETURN"), cancellable = true)
private void biolith$cancelGetBiome(int x, int y, int z, MultiNoiseUtil.MultiNoiseSampler noise, CallbackInfoReturnable<RegistryEntry<Biome>> cir) {
// Allows us to call unmodified (by us) getBiome() to get TerraBlender values.
if (bypass.get()) {
cir.setReturnValue(cir.getReturnValue());
}
}
}
3 changes: 2 additions & 1 deletion fabric/src/main/resources/biolith.fabric.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"mixins": [
"MixinFapiTheEndBiomes",
"MixinMBBiomeSource",
"MixinSaveLoader"
"MixinSaveLoader",
"MixinTBTheEndBiomeSource"
],
"client": [
],
Expand Down
Loading

0 comments on commit 4cea9b7

Please sign in to comment.