mirror of
https://github.com/FabricMC/fabric.git
synced 2025-04-21 03:10:54 -04:00
Fabric Biomes API (#200)
Co-authored-by: Prospector <prospectordev@gmail.com> Co-authored-by: coderbot16 <coderbot16@gmail.com>
This commit is contained in:
parent
43028fa68d
commit
896c7fbb2d
19 changed files with 1286 additions and 0 deletions
fabric-biomes-v1
build.gradle
settings.gradlesrc/main
java/net/fabricmc/fabric
api/biomes/v1
impl/biomes
BiomeVariant.javaContinentalBiomeEntry.javaInternalBiomeData.javaInternalBiomeUtils.javaVariantTransformer.javaWeightedBiomePicker.java
mixin/biomes
resources
2
fabric-biomes-v1/build.gradle
Normal file
2
fabric-biomes-v1/build.gradle
Normal file
|
@ -0,0 +1,2 @@
|
|||
archivesBaseName = "fabric-biomes-v1"
|
||||
version = getSubprojectVersion(project, "0.1.0")
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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.biomes.v1;
|
||||
|
||||
import net.fabricmc.fabric.impl.biomes.InternalBiomeData;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
|
||||
/**
|
||||
* General API that applies to all biome sources
|
||||
*/
|
||||
public final class FabricBiomes {
|
||||
|
||||
private FabricBiomes() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows players to naturally spawn in this biome
|
||||
*
|
||||
* @param biome a biome the player should be able to spawn in
|
||||
*/
|
||||
public static void addSpawnBiome(Biome biome) {
|
||||
InternalBiomeData.addSpawnBiome(biome);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* 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.biomes.v1;
|
||||
|
||||
import net.fabricmc.fabric.impl.biomes.InternalBiomeData;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
|
||||
/**
|
||||
* API that exposes some internals of the minecraft default biome source for the overworld
|
||||
*/
|
||||
public final class OverworldBiomes {
|
||||
|
||||
private OverworldBiomes() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the biome to the specified climate group, with the specified weight. This is only for the biomes that make up the initial continents in generation.
|
||||
*
|
||||
* @param biome the biome to be added
|
||||
* @param climate the climate group whereto the biome is added
|
||||
* @param weight the weight of the entry. The weight in this method corresponds to its selection likelihood, with
|
||||
* heavier biomes being more likely to be selected and lighter biomes being selected with less likelihood.
|
||||
* @see OverworldClimate for a list of vanilla biome weights
|
||||
*/
|
||||
public static void addContinentalBiome(Biome biome, OverworldClimate climate, double weight) {
|
||||
InternalBiomeData.addOverworldContinentalBiome(climate, biome, weight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the biome as a hills variant of the parent biome, with the specified weight
|
||||
*
|
||||
* @param parent the biome to where the hills variant is added
|
||||
* @param hills the biome to be set as a hills variant
|
||||
* @param weight the weight of the entry. The weight in this method corresponds to its selection likelihood, with
|
||||
* heavier biomes being more likely to be selected and lighter biomes being selected with less likelihood.
|
||||
* Mods should use 1.0 as the default/normal weight.
|
||||
*/
|
||||
public static void addHillsBiome(Biome parent, Biome hills, double weight) {
|
||||
InternalBiomeData.addOverworldHillsBiome(parent, hills, weight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the biome as a shore/beach biome for the parent biome, with the specified weight
|
||||
*
|
||||
* @param parent the base biome to where the shore biome is added
|
||||
* @param shore the biome to be added as a shore biome
|
||||
* @param weight the weight of the entry. The weight in this method corresponds to its selection likelihood, with
|
||||
* heavier biomes being more likely to be selected and lighter biomes being selected with less likelihood.
|
||||
* Mods should use 1.0 as the default/normal weight.
|
||||
*/
|
||||
public static void addShoreBiome(Biome parent, Biome shore, double weight) {
|
||||
InternalBiomeData.addOverworldShoreBiome(parent, shore, weight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the biome as an an edge biome (excluding as a beach) of the parent biome, with the specified weight
|
||||
*
|
||||
* @param parent the base biome to where the edge biome is added
|
||||
* @param edge the biome to be added as an edge biome
|
||||
* @param weight the weight of the entry. The weight in this method corresponds to its selection likelihood, with
|
||||
* heavier biomes being more likely to be selected and lighter biomes being selected with less likelihood.
|
||||
* Mods should use 1.0 as the default/normal weight.
|
||||
*/
|
||||
public static void addEdgeBiome(Biome parent, Biome edge, double weight) {
|
||||
InternalBiomeData.addOverworldEdgeBiome(parent, edge, weight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a 'variant' biome which replaces another biome on occasion.
|
||||
* For example, addBiomeVariant(Biomes.JUNGLE, Biomes.DESERT, 0.2) will replace 20% of jungles with deserts.
|
||||
* This method is rather useful for replacing biomes not generated through standard methods, such as oceans,
|
||||
* deep oceans, jungles, mushroom islands, etc. When replacing ocean and deep ocean biomes, one must specify
|
||||
* the biome without temperature (Biomes.OCEAN / Biomes.DEEP_OCEAN) only, as ocean temperatures have not been
|
||||
* assigned; additionally, one must not specify climates for oceans, deep oceans, or mushroom islands, as they do not have
|
||||
* any climate assigned at this point in the generation.
|
||||
*
|
||||
* @param replaced the base biome that is replaced by a variant
|
||||
* @param variant the biome to be added as a variant
|
||||
* @param chance the chance of replacement of the biome into the variant
|
||||
* @param climates the climates in which the variants will occur in (none listed = add variant to all climates)
|
||||
*/
|
||||
public static void addBiomeVariant(Biome replaced, Biome variant, double chance, OverworldClimate... climates) {
|
||||
InternalBiomeData.addOverworldBiomeReplacement(replaced, variant, chance, climates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the river type that will generate in the biome. If null is passed as the river biome, then rivers will not
|
||||
* generate in this biome.
|
||||
*
|
||||
* @param parent the base biome in which the river biome is to be set
|
||||
* @param river the river biome for this biome
|
||||
*/
|
||||
public static void setRiverBiome(Biome parent, Biome river) {
|
||||
InternalBiomeData.setOverworldRiverBiome(parent, river);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.biomes.v1;
|
||||
|
||||
/**
|
||||
* Represents the climates of biomes on the overworld continents
|
||||
*/
|
||||
public enum OverworldClimate {
|
||||
|
||||
/**
|
||||
* Includes Snowy Tundra (with a weight of 3) and Snowy Taiga (with a weight of 1)
|
||||
*/
|
||||
SNOWY,
|
||||
|
||||
/**
|
||||
* Includes Forest, Taiga, Mountains, and Plains (all with weights of 1)
|
||||
*/
|
||||
COOL,
|
||||
|
||||
/**
|
||||
* Includes Forest, Dark Forest, Mountains, Plains, Birch Forest, and Swamp (all with weights of 1)
|
||||
*/
|
||||
TEMPERATE,
|
||||
|
||||
/**
|
||||
* Includes Desert (with a weight of 3), Savanna (with a weight of 2), and Plains (with a weight of 1)
|
||||
*/
|
||||
DRY
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.biomes;
|
||||
|
||||
import net.minecraft.world.biome.Biome;
|
||||
|
||||
/**
|
||||
* Represents a biome variant and its corresponding chance
|
||||
*/
|
||||
final class BiomeVariant {
|
||||
|
||||
private final Biome variant;
|
||||
private final double chance;
|
||||
|
||||
/**
|
||||
* @param variant the variant biome
|
||||
* @param chance the chance of replacement of the biome into the variant
|
||||
*/
|
||||
BiomeVariant(final Biome variant, final double chance) {
|
||||
this.variant = variant;
|
||||
this.chance = chance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the variant biome
|
||||
*/
|
||||
Biome getVariant() {
|
||||
return variant;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the chance of replacement of the biome into the variant
|
||||
*/
|
||||
double getChance() {
|
||||
return chance;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.biomes;
|
||||
|
||||
import net.minecraft.world.biome.Biome;
|
||||
|
||||
/**
|
||||
* Represents a biome and its corresponding weight
|
||||
*/
|
||||
final class ContinentalBiomeEntry {
|
||||
private final Biome biome;
|
||||
private final double weight;
|
||||
private final double upperWeightBound;
|
||||
|
||||
/**
|
||||
* @param biome the biome
|
||||
* @param weight how often a biome will be chosen
|
||||
* @param upperWeightBound the upper weight bound within the context of the other entries, used for the binary search
|
||||
*/
|
||||
ContinentalBiomeEntry(final Biome biome, final double weight, final double upperWeightBound) {
|
||||
this.biome = biome;
|
||||
this.weight = weight;
|
||||
this.upperWeightBound = upperWeightBound;
|
||||
}
|
||||
|
||||
Biome getBiome() {
|
||||
return biome;
|
||||
}
|
||||
|
||||
double getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the upper weight boundary for the search
|
||||
*/
|
||||
double getUpperWeightBound() {
|
||||
return upperWeightBound;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* 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.biomes;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import net.fabricmc.fabric.api.biomes.v1.OverworldClimate;
|
||||
import net.minecraft.util.registry.Registry;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.biome.Biomes;
|
||||
import net.minecraft.world.biome.layer.BiomeLayers;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Lists and maps for internal use only! Stores data that is used by the various mixins into the world generation
|
||||
*/
|
||||
public final class InternalBiomeData {
|
||||
|
||||
private InternalBiomeData() {
|
||||
}
|
||||
|
||||
private static final EnumMap<OverworldClimate, WeightedBiomePicker> OVERWORLD_MODDED_CONTINENTAL_BIOME_PICKERS = new EnumMap<>(OverworldClimate.class);
|
||||
private static final Map<Biome, WeightedBiomePicker> OVERWORLD_HILLS_MAP = new HashMap<>();
|
||||
private static final Map<Biome, WeightedBiomePicker> OVERWORLD_SHORE_MAP = new HashMap<>();
|
||||
private static final Map<Biome, WeightedBiomePicker> OVERWORLD_EDGE_MAP = new HashMap<>();
|
||||
private static final Map<Biome, VariantTransformer> OVERWORLD_VARIANT_TRANSFORMERS = new HashMap<>();
|
||||
private static final Map<Biome, Biome> OVERWORLD_RIVER_MAP = new HashMap<>();
|
||||
private static final List<Biome> OVERWORLD_INJECTED_BIOMES = new ArrayList<>();
|
||||
|
||||
private static final Set<Biome> SPAWN_BIOMES = new HashSet<>();
|
||||
|
||||
public static void addOverworldContinentalBiome(OverworldClimate climate, Biome biome, double weight) {
|
||||
Preconditions.checkArgument(climate != null, "Climate is null");
|
||||
Preconditions.checkArgument(biome != null, "Biome is null");
|
||||
Preconditions.checkArgument(!Double.isNaN(weight), "Weight is NaN");
|
||||
Preconditions.checkArgument(weight > 0.0, "Weight is less than or equal to 0.0 (%s)", weight);
|
||||
OVERWORLD_MODDED_CONTINENTAL_BIOME_PICKERS.computeIfAbsent(climate, k -> new WeightedBiomePicker()).addBiome(biome, weight);
|
||||
OVERWORLD_INJECTED_BIOMES.add(biome);
|
||||
}
|
||||
|
||||
public static void addOverworldHillsBiome(Biome primary, Biome hills, double weight) {
|
||||
Preconditions.checkArgument(primary != null, "Primary biome is null");
|
||||
Preconditions.checkArgument(hills != null, "Hills biome is null");
|
||||
Preconditions.checkArgument(!Double.isNaN(weight), "Weight is NaN");
|
||||
Preconditions.checkArgument(weight > 0.0, "Weight is less than or equal to 0.0 (%s)", weight);
|
||||
OVERWORLD_HILLS_MAP.computeIfAbsent(primary, biome -> DefaultHillsData.injectDefaultHills(primary, new WeightedBiomePicker())).addBiome(hills, weight);
|
||||
OVERWORLD_INJECTED_BIOMES.add(hills);
|
||||
}
|
||||
|
||||
public static void addOverworldShoreBiome(Biome primary, Biome shore, double weight) {
|
||||
Preconditions.checkArgument(primary != null, "Primary biome is null");
|
||||
Preconditions.checkArgument(shore != null, "Shore biome is null");
|
||||
Preconditions.checkArgument(!Double.isNaN(weight), "Weight is NaN");
|
||||
Preconditions.checkArgument(weight > 0.0, "Weight is less than or equal to 0.0 (%s)", weight);
|
||||
OVERWORLD_SHORE_MAP.computeIfAbsent(primary, biome -> new WeightedBiomePicker()).addBiome(shore, weight);
|
||||
OVERWORLD_INJECTED_BIOMES.add(shore);
|
||||
}
|
||||
|
||||
public static void addOverworldEdgeBiome(Biome primary, Biome edge, double weight) {
|
||||
Preconditions.checkArgument(primary != null, "Primary biome is null");
|
||||
Preconditions.checkArgument(edge != null, "Edge biome is null");
|
||||
Preconditions.checkArgument(!Double.isNaN(weight), "Weight is NaN");
|
||||
Preconditions.checkArgument(weight > 0.0, "Weight is less than or equal to 0.0 (%s)", weight);
|
||||
OVERWORLD_EDGE_MAP.computeIfAbsent(primary, biome -> new WeightedBiomePicker()).addBiome(edge, weight);
|
||||
OVERWORLD_INJECTED_BIOMES.add(edge);
|
||||
}
|
||||
|
||||
public static void addOverworldBiomeReplacement(Biome replaced, Biome variant, double chance, OverworldClimate[] climates) {
|
||||
Preconditions.checkArgument(replaced != null, "Replaced biome is null");
|
||||
Preconditions.checkArgument(variant != null, "Variant biome is null");
|
||||
Preconditions.checkArgument(chance > 0 && chance <= 1, "Chance is not greater than 0 or less than or equal to 1");
|
||||
OVERWORLD_VARIANT_TRANSFORMERS.computeIfAbsent(replaced, biome -> new VariantTransformer()).addBiome(variant, chance, climates);
|
||||
OVERWORLD_INJECTED_BIOMES.add(variant);
|
||||
}
|
||||
|
||||
public static void setOverworldRiverBiome(Biome primary, Biome river) {
|
||||
Preconditions.checkArgument(primary != null, "Primary biome is null");
|
||||
OVERWORLD_RIVER_MAP.put(primary, river);
|
||||
if (river != null) {
|
||||
OVERWORLD_INJECTED_BIOMES.add(river);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addSpawnBiome(Biome biome) {
|
||||
Preconditions.checkArgument(biome != null, "Biome is null");
|
||||
SPAWN_BIOMES.add(biome);
|
||||
}
|
||||
|
||||
public static List<Biome> getOverworldInjectedBiomes() {
|
||||
return OVERWORLD_INJECTED_BIOMES;
|
||||
}
|
||||
|
||||
public static Set<Biome> getSpawnBiomes() {
|
||||
return SPAWN_BIOMES;
|
||||
}
|
||||
|
||||
public static Map<Biome, WeightedBiomePicker> getOverworldHills() {
|
||||
return OVERWORLD_HILLS_MAP;
|
||||
}
|
||||
|
||||
public static Map<Biome, WeightedBiomePicker> getOverworldShores() {
|
||||
return OVERWORLD_SHORE_MAP;
|
||||
}
|
||||
|
||||
public static Map<Biome, WeightedBiomePicker> getOverworldEdges() {
|
||||
return OVERWORLD_EDGE_MAP;
|
||||
}
|
||||
|
||||
public static Map<Biome, Biome> getOverworldRivers() {
|
||||
return OVERWORLD_RIVER_MAP;
|
||||
}
|
||||
|
||||
public static EnumMap<OverworldClimate, WeightedBiomePicker> getOverworldModdedContinentalBiomePickers() {
|
||||
return OVERWORLD_MODDED_CONTINENTAL_BIOME_PICKERS;
|
||||
}
|
||||
|
||||
public static Map<Biome, VariantTransformer> getOverworldVariantTransformers() {
|
||||
return OVERWORLD_VARIANT_TRANSFORMERS;
|
||||
}
|
||||
|
||||
private static class DefaultHillsData {
|
||||
private static final ImmutableMap<Biome, Biome> DEFAULT_HILLS;
|
||||
|
||||
static WeightedBiomePicker injectDefaultHills(Biome base, WeightedBiomePicker picker) {
|
||||
Biome defaultHill = DEFAULT_HILLS.get(base);
|
||||
|
||||
if (defaultHill != null) {
|
||||
picker.addBiome(defaultHill, 1);
|
||||
} else if (BiomeLayers.areSimilar(Registry.BIOME.getRawId(base), Registry.BIOME.getRawId(Biomes.WOODED_BADLANDS_PLATEAU))) {
|
||||
picker.addBiome(Biomes.BADLANDS, 1);
|
||||
} else if (base == Biomes.DEEP_OCEAN || base == Biomes.DEEP_LUKEWARM_OCEAN || base == Biomes.DEEP_COLD_OCEAN) {
|
||||
picker.addBiome(Biomes.PLAINS, 1);
|
||||
picker.addBiome(Biomes.FOREST, 1);
|
||||
} else if (base == Biomes.DEEP_FROZEN_OCEAN) {
|
||||
// Note: Vanilla Deep Frozen Oceans only have a 1/3 chance of having default hills.
|
||||
// This is a clever trick that ensures that when a mod adds hills with a weight of 1, the 1/3 chance is fulfilled.
|
||||
// 0.5 + 1.0 = 1.5, and 0.5 / 1.5 = 1/3.
|
||||
|
||||
picker.addBiome(Biomes.PLAINS, 0.25);
|
||||
picker.addBiome(Biomes.FOREST, 0.25);
|
||||
} else if (base == Biomes.PLAINS) {
|
||||
picker.addBiome(Biomes.WOODED_HILLS, 1);
|
||||
picker.addBiome(Biomes.FOREST, 2);
|
||||
}
|
||||
|
||||
return picker;
|
||||
}
|
||||
|
||||
static {
|
||||
ImmutableMap.Builder<Biome, Biome> builder = ImmutableMap.builder();
|
||||
builder.put(Biomes.DESERT, Biomes.DESERT_HILLS);
|
||||
builder.put(Biomes.FOREST, Biomes.WOODED_HILLS);
|
||||
builder.put(Biomes.BIRCH_FOREST, Biomes.BIRCH_FOREST_HILLS);
|
||||
builder.put(Biomes.DARK_FOREST, Biomes.PLAINS);
|
||||
builder.put(Biomes.TAIGA, Biomes.TAIGA_HILLS);
|
||||
builder.put(Biomes.GIANT_TREE_TAIGA, Biomes.GIANT_TREE_TAIGA_HILLS);
|
||||
builder.put(Biomes.SNOWY_TAIGA, Biomes.SNOWY_TAIGA_HILLS);
|
||||
builder.put(Biomes.SNOWY_TUNDRA, Biomes.SNOWY_MOUNTAINS);
|
||||
builder.put(Biomes.JUNGLE, Biomes.JUNGLE_HILLS);
|
||||
builder.put(Biomes.BAMBOO_JUNGLE, Biomes.BAMBOO_JUNGLE_HILLS);
|
||||
builder.put(Biomes.OCEAN, Biomes.DEEP_OCEAN);
|
||||
builder.put(Biomes.LUKEWARM_OCEAN, Biomes.DEEP_LUKEWARM_OCEAN);
|
||||
builder.put(Biomes.COLD_OCEAN, Biomes.DEEP_COLD_OCEAN);
|
||||
builder.put(Biomes.FROZEN_OCEAN, Biomes.DEEP_FROZEN_OCEAN);
|
||||
builder.put(Biomes.MOUNTAINS, Biomes.WOODED_MOUNTAINS);
|
||||
builder.put(Biomes.SAVANNA, Biomes.SAVANNA_PLATEAU);
|
||||
DEFAULT_HILLS = builder.build();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* 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.biomes;
|
||||
|
||||
import net.fabricmc.fabric.api.biomes.v1.OverworldClimate;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.registry.Registry;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.biome.layer.LayerRandomnessSource;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
/**
|
||||
* Internal utilities used for biome sampling
|
||||
*/
|
||||
public final class InternalBiomeUtils {
|
||||
|
||||
private InternalBiomeUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param north raw id of the biome to the north
|
||||
* @param east raw id of the biome to the east
|
||||
* @param south raw id of the biome to the south
|
||||
* @param west raw id of the biome to the west
|
||||
* @param center central biome that comparisons are relative to
|
||||
* @return whether the central biome is an edge of a biome
|
||||
*/
|
||||
public static boolean isEdge(int north, int east, int south, int west, int center) {
|
||||
return areUnsimilar(center, north) || areUnsimilar(center, east) || areUnsimilar(center, south) || areUnsimilar(center, west);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mainBiomeId the main raw biome id in comparison
|
||||
* @param secondaryBiomeId the secondary raw biome id in comparison
|
||||
* @return whether the two biomes are unsimilar
|
||||
*/
|
||||
private static boolean areUnsimilar(int mainBiomeId, int secondaryBiomeId) {
|
||||
if (mainBiomeId == secondaryBiomeId) { // for efficiency, determine if the ids are equal first
|
||||
return false;
|
||||
} else {
|
||||
Biome secondaryBiome = Registry.BIOME.get(secondaryBiomeId);
|
||||
Biome mainBiome = Registry.BIOME.get(mainBiomeId);
|
||||
|
||||
boolean isUnsimilar = secondaryBiome.hasParent() ? !(mainBiomeId == Registry.BIOME.getRawId(Registry.BIOME.get(new Identifier(secondaryBiome.getParent())))) : true;
|
||||
isUnsimilar = isUnsimilar && (mainBiome.hasParent() ? !(secondaryBiomeId == Registry.BIOME.getRawId(Registry.BIOME.get(new Identifier(mainBiome.getParent())))) : true);
|
||||
|
||||
return isUnsimilar;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param north raw id of the biome to the north
|
||||
* @param east raw id of the biome to the east
|
||||
* @param south raw id of the biome to the south
|
||||
* @param west raw id of the biome to the west
|
||||
* @return whether a biome in any direction is an ocean around the central biome
|
||||
*/
|
||||
public static boolean neighborsOcean(int north, int east, int south, int west) {
|
||||
return isOceanBiome(north) || isOceanBiome(east) || isOceanBiome(south) || isOceanBiome(west);
|
||||
}
|
||||
|
||||
private static boolean isOceanBiome(int id) {
|
||||
Biome biome = Registry.BIOME.get(id);
|
||||
return biome != null && biome.getCategory() == Biome.Category.OCEAN;
|
||||
}
|
||||
|
||||
public static int searchForBiome(double reqWeightSum, int vanillaArrayWeight, List<ContinentalBiomeEntry> moddedBiomes) {
|
||||
reqWeightSum -= vanillaArrayWeight;
|
||||
int low = 0;
|
||||
int high = moddedBiomes.size() - 1;
|
||||
while (low < high) {
|
||||
int mid = (high + low) >>> 1;
|
||||
if (reqWeightSum < moddedBiomes.get(mid).getUpperWeightBound()) {
|
||||
high = mid;
|
||||
} else {
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
return low;
|
||||
}
|
||||
|
||||
/**
|
||||
* Potentially transforms a biome into its variants based on the provided randomness source.
|
||||
*
|
||||
* @param random The randomness source
|
||||
* @param existing The base biome
|
||||
* @param climate The climate in which the biome resides, or null to indicate an unknown climate
|
||||
* @return The potentially transformed biome
|
||||
*/
|
||||
public static int transformBiome(LayerRandomnessSource random, Biome existing, OverworldClimate climate) {
|
||||
Map<Biome, VariantTransformer> overworldVariantTransformers = InternalBiomeData.getOverworldVariantTransformers();
|
||||
VariantTransformer transformer = overworldVariantTransformers.get(existing);
|
||||
|
||||
if (transformer != null) {
|
||||
return Registry.BIOME.getRawId(transformer.transformBiome(existing, random, climate));
|
||||
}
|
||||
|
||||
return Registry.BIOME.getRawId(existing);
|
||||
}
|
||||
|
||||
public static void injectBiomesIntoClimate(LayerRandomnessSource random, int[] vanillaArray, OverworldClimate climate, IntConsumer result) {
|
||||
WeightedBiomePicker picker = InternalBiomeData.getOverworldModdedContinentalBiomePickers().get(climate);
|
||||
|
||||
if (picker == null || picker.getCurrentWeightTotal() <= 0.0) {
|
||||
// Return early, there are no modded biomes.
|
||||
// Since we don't pass any values to the IntConsumer, this falls through to vanilla logic.
|
||||
// Thus, this prevents Fabric from changing vanilla biome selection behavior without biome mods in this case.
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int vanillaArrayWeight = vanillaArray.length;
|
||||
double reqWeightSum = (double) random.nextInt(Integer.MAX_VALUE) * (vanillaArray.length + picker.getCurrentWeightTotal()) / Integer.MAX_VALUE;
|
||||
|
||||
if (reqWeightSum < vanillaArray.length) {
|
||||
// Vanilla biome; look it up from the vanilla array and transform accordingly.
|
||||
|
||||
result.accept(transformBiome(random, Registry.BIOME.get(vanillaArray[(int) reqWeightSum]), climate));
|
||||
} else {
|
||||
// Modded biome; use a binary search, and then transform accordingly.
|
||||
|
||||
ContinentalBiomeEntry found = picker.search(reqWeightSum - vanillaArrayWeight);
|
||||
|
||||
result.accept(transformBiome(random, found.getBiome(), climate));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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.biomes;
|
||||
|
||||
import net.fabricmc.fabric.api.biomes.v1.OverworldClimate;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.biome.layer.LayerRandomnessSource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Deals with picking variants for you
|
||||
*/
|
||||
final class VariantTransformer {
|
||||
private final SubTransformer defaultTransformer = new SubTransformer();
|
||||
private final Map<OverworldClimate, SubTransformer> transformers = new HashMap<>();
|
||||
|
||||
/**
|
||||
* @param variant the variant that the replaced biome is replaced with
|
||||
* @param chance the chance of replacement of the biome into the variant
|
||||
* @param climates the climates that the variant can replace the base biome in, empty/null indicates all climates
|
||||
*/
|
||||
void addBiome(Biome variant, double chance, OverworldClimate[] climates) {
|
||||
if (climates == null || climates.length == 0) {
|
||||
defaultTransformer.addBiome(variant, chance);
|
||||
climates = OverworldClimate.values();
|
||||
}
|
||||
|
||||
for (OverworldClimate climate : climates) {
|
||||
transformers.computeIfAbsent(climate, c -> new SubTransformer()).addBiome(variant, chance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a biome into a variant randomly depending on its chance
|
||||
*
|
||||
* @param replaced biome to transform
|
||||
* @param random the {@link LayerRandomnessSource} from the layer
|
||||
* @return the transformed biome
|
||||
*/
|
||||
Biome transformBiome(Biome replaced, LayerRandomnessSource random, OverworldClimate climate) {
|
||||
if (climate == null) {
|
||||
return defaultTransformer.transformBiome(replaced, random);
|
||||
}
|
||||
|
||||
SubTransformer transformer = transformers.get(climate);
|
||||
|
||||
if (transformer != null) {
|
||||
return transformer.transformBiome(replaced, random);
|
||||
} else {
|
||||
return replaced;
|
||||
}
|
||||
}
|
||||
|
||||
static final class SubTransformer {
|
||||
private final List<BiomeVariant> variants = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* @param variant the variant that the replaced biome is replaced with
|
||||
* @param chance the chance of replacement of the biome into the variant
|
||||
*/
|
||||
private void addBiome(Biome variant, double chance) {
|
||||
variants.add(new BiomeVariant(variant, chance));
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a biome into a variant randomly depending on its chance
|
||||
*
|
||||
* @param replaced biome to transform
|
||||
* @param random the {@link LayerRandomnessSource} from the layer
|
||||
* @return the transformed biome
|
||||
*/
|
||||
private Biome transformBiome(Biome replaced, LayerRandomnessSource random) {
|
||||
for (BiomeVariant variant : variants) {
|
||||
if (random.nextInt(Integer.MAX_VALUE) < variant.getChance() * Integer.MAX_VALUE) {
|
||||
return variant.getVariant();
|
||||
}
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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.biomes;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.biome.layer.LayerRandomnessSource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Picks biomes with arbitrary double weights using a binary search.
|
||||
*/
|
||||
public final class WeightedBiomePicker {
|
||||
private double currentTotal;
|
||||
private List<ContinentalBiomeEntry> entries;
|
||||
|
||||
WeightedBiomePicker() {
|
||||
currentTotal = 0;
|
||||
entries = new ArrayList<>();
|
||||
}
|
||||
|
||||
void addBiome(final Biome biome, final double weight) {
|
||||
currentTotal += weight;
|
||||
|
||||
entries.add(new ContinentalBiomeEntry(biome, weight, currentTotal));
|
||||
}
|
||||
|
||||
double getCurrentWeightTotal() {
|
||||
return currentTotal;
|
||||
}
|
||||
|
||||
public Biome pickRandom(LayerRandomnessSource random) {
|
||||
double target = (double) random.nextInt(Integer.MAX_VALUE) * getCurrentWeightTotal() / Integer.MAX_VALUE;
|
||||
|
||||
return search(target).getBiome();
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches with the specified target value
|
||||
*
|
||||
* @param target The target value, must satisfy the constraint 0 <= target <= currentTotal
|
||||
* @return The result of the search
|
||||
*/
|
||||
ContinentalBiomeEntry search(final double target) {
|
||||
// Sanity checks, fail fast if stuff is going wrong.
|
||||
Preconditions.checkArgument(target <= currentTotal, "The provided target value for biome selection must be less than or equal to the weight total");
|
||||
Preconditions.checkArgument(target >= 0, "The provided target value for biome selection cannot be negative");
|
||||
|
||||
int low = 0;
|
||||
int high = entries.size() - 1;
|
||||
|
||||
while (low < high) {
|
||||
int mid = (high + low) >>> 1;
|
||||
if (target < entries.get(mid).getUpperWeightBound()) {
|
||||
high = mid;
|
||||
} else {
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return entries.get(low);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.mixin.biomes;
|
||||
|
||||
import net.fabricmc.fabric.api.biomes.v1.OverworldBiomes;
|
||||
import net.fabricmc.fabric.impl.biomes.InternalBiomeData;
|
||||
import net.fabricmc.fabric.impl.biomes.InternalBiomeUtils;
|
||||
import net.minecraft.util.registry.Registry;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.biome.layer.AddEdgeBiomesLayer;
|
||||
import net.minecraft.world.biome.layer.LayerRandomnessSource;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Adds edges and shores specified in {@link OverworldBiomes#addEdgeBiome(Biome, Biome, double)} and {@link OverworldBiomes#addShoreBiome(Biome, Biome, double)} to the edges layer
|
||||
*/
|
||||
@Mixin(AddEdgeBiomesLayer.class)
|
||||
public class MixinAddEdgeBiomesLayer {
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "sample", cancellable = true)
|
||||
private void sample(LayerRandomnessSource rand, int north, int east, int south, int west, int center, CallbackInfoReturnable<Integer> info) {
|
||||
Biome centerBiome = Registry.BIOME.get(center);
|
||||
if (InternalBiomeData.getOverworldShores().containsKey(centerBiome) && InternalBiomeUtils.neighborsOcean(north, east, south, west)) {
|
||||
info.setReturnValue(Registry.BIOME.getRawId(InternalBiomeData.getOverworldShores().get(centerBiome).pickRandom(rand)));
|
||||
} else if (InternalBiomeData.getOverworldEdges().containsKey(centerBiome) && InternalBiomeUtils.isEdge(north, east, south, west, center)) {
|
||||
info.setReturnValue(Registry.BIOME.getRawId(InternalBiomeData.getOverworldEdges().get(centerBiome).pickRandom(rand)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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.biomes;
|
||||
|
||||
import net.fabricmc.fabric.api.biomes.v1.OverworldBiomes;
|
||||
import net.fabricmc.fabric.impl.biomes.InternalBiomeData;
|
||||
import net.fabricmc.fabric.impl.biomes.WeightedBiomePicker;
|
||||
import net.minecraft.util.registry.Registry;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.biome.layer.AddHillsLayer;
|
||||
import net.minecraft.world.biome.layer.BiomeLayers;
|
||||
import net.minecraft.world.biome.layer.LayerRandomnessSource;
|
||||
import net.minecraft.world.biome.layer.LayerSampler;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Injects hills biomes specified from {@link OverworldBiomes#addHillsBiome(Biome, Biome, double)}into the default hills layer
|
||||
*/
|
||||
@Mixin(AddHillsLayer.class)
|
||||
public class MixinAddHillsLayer {
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "sample", cancellable = true)
|
||||
private void sample(LayerRandomnessSource rand, LayerSampler biomeSampler, LayerSampler noiseSampler, int chunkX, int chunkZ, CallbackInfoReturnable<Integer> info) {
|
||||
if (InternalBiomeData.getOverworldHills().isEmpty()) {
|
||||
// No use doing anything if there are no hills registered. Fall through to vanilla logic.
|
||||
return;
|
||||
}
|
||||
|
||||
final int biomeId = biomeSampler.sample(chunkX, chunkZ);
|
||||
int noiseSample = noiseSampler.sample(chunkX, chunkZ);
|
||||
int processedNoiseSample = (noiseSample - 2) % 29;
|
||||
final Biome biome = Registry.BIOME.get(biomeId);
|
||||
|
||||
WeightedBiomePicker hillPicker = InternalBiomeData.getOverworldHills().get(biome);
|
||||
|
||||
if (hillPicker == null) {
|
||||
// No hills for this biome, fall through to vanilla logic.
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (rand.nextInt(3) == 0 || processedNoiseSample == 0) {
|
||||
int biomeReturn = Registry.BIOME.getRawId(hillPicker.pickRandom(rand));
|
||||
Biome parent;
|
||||
|
||||
if (processedNoiseSample == 0 && biomeReturn != biomeId) {
|
||||
parent = Biome.getParentBiome(Registry.BIOME.get(biomeReturn));
|
||||
biomeReturn = parent == null ? biomeId : Registry.BIOME.getRawId(parent);
|
||||
}
|
||||
|
||||
if (biomeReturn != biomeId) {
|
||||
int similarity = 0;
|
||||
if (BiomeLayers.areSimilar(biomeSampler.sample(chunkX, chunkZ - 1), biomeId)) {
|
||||
++similarity;
|
||||
}
|
||||
if (BiomeLayers.areSimilar(biomeSampler.sample(chunkX + 1, chunkZ), biomeId)) {
|
||||
++similarity;
|
||||
}
|
||||
if (BiomeLayers.areSimilar(biomeSampler.sample(chunkX - 1, chunkZ), biomeId)) {
|
||||
++similarity;
|
||||
}
|
||||
if (BiomeLayers.areSimilar(biomeSampler.sample(chunkX, chunkZ + 1), biomeId)) {
|
||||
++similarity;
|
||||
}
|
||||
if (similarity >= 3) {
|
||||
info.setReturnValue(biomeReturn);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel vanilla logic.
|
||||
info.setReturnValue(biomeId);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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.biomes;
|
||||
|
||||
import net.fabricmc.fabric.api.biomes.v1.OverworldBiomes;
|
||||
import net.fabricmc.fabric.impl.biomes.InternalBiomeData;
|
||||
import net.minecraft.util.registry.Registry;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.biome.layer.AddRiversLayer;
|
||||
import net.minecraft.world.biome.layer.LayerRandomnessSource;
|
||||
import net.minecraft.world.biome.layer.LayerSampler;
|
||||
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.CallbackInfoReturnable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Sets river biomes specified with {@link OverworldBiomes#setRiverBiome(Biome, Biome)}
|
||||
*/
|
||||
@Mixin(AddRiversLayer.class)
|
||||
public class MixinAddRiversLayer {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private static int RIVER_ID;
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "sample", cancellable = true)
|
||||
private void sample(LayerRandomnessSource rand, LayerSampler landSampler, LayerSampler riverSampler, int x, int z, CallbackInfoReturnable<Integer> info) {
|
||||
int landBiomeId = landSampler.sample(x, z);
|
||||
Biome landBiome = Registry.BIOME.get(landBiomeId);
|
||||
|
||||
int riverBiomeId = riverSampler.sample(x, z);
|
||||
Map<Biome, Biome> overworldRivers = InternalBiomeData.getOverworldRivers();
|
||||
if (overworldRivers.containsKey(landBiome) && riverBiomeId == RIVER_ID) {
|
||||
Biome riverBiome = overworldRivers.get(landBiome);
|
||||
info.setReturnValue(riverBiome == null ? landBiomeId : Registry.BIOME.getRawId(riverBiome));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.biomes;
|
||||
|
||||
import net.fabricmc.fabric.impl.biomes.InternalBiomeData;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.biome.source.BiomeSource;
|
||||
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.CallbackInfoReturnable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Adds spawn biomes to the base {@link BiomeSource} class.
|
||||
*/
|
||||
@Mixin(BiomeSource.class)
|
||||
public class MixinBiomeSource {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private static List<Biome> SPAWN_BIOMES;
|
||||
|
||||
@Inject(at = @At("RETURN"), cancellable = true, method = "getSpawnBiomes")
|
||||
private void getSpawnBiomes(CallbackInfoReturnable<List<Biome>> info) {
|
||||
Set<Biome> biomes = new LinkedHashSet<>(info.getReturnValue());
|
||||
if (biomes.addAll(InternalBiomeData.getSpawnBiomes())) {
|
||||
info.setReturnValue(new ArrayList<>(biomes));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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.biomes;
|
||||
|
||||
import net.fabricmc.fabric.api.biomes.v1.OverworldClimate;
|
||||
import net.fabricmc.fabric.impl.biomes.InternalBiomeUtils;
|
||||
import net.minecraft.util.registry.Registry;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.biome.layer.LayerRandomnessSource;
|
||||
import net.minecraft.world.biome.layer.SetBaseBiomesLayer;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
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.CallbackInfoReturnable;
|
||||
|
||||
/**
|
||||
* Injects biomes into the arrays of biomes in the {@link SetBaseBiomesLayer}
|
||||
*/
|
||||
@Mixin(SetBaseBiomesLayer.class)
|
||||
public class MixinSetBaseBiomesLayer {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
@Mutable
|
||||
private static int[] SNOWY_BIOMES;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
@Mutable
|
||||
private static int[] COOL_BIOMES;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
@Mutable
|
||||
private static int[] TEMPERATE_BIOMES;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
@Mutable
|
||||
private static int[] DRY_BIOMES;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private static int WOODED_BADLANDS_PLATEAU_ID;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private static int BADLANDS_PLATEAU_ID;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private static int JUNGLE_ID;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private static int GIANT_TREE_TAIGA_ID;
|
||||
|
||||
@Inject(at = @At(value = "FIELD", target = "Lnet/minecraft/world/biome/layer/SetBaseBiomesLayer;chosenGroup1:[I"), method = "sample", cancellable = true)
|
||||
private void injectDryBiomes(LayerRandomnessSource random, int value, CallbackInfoReturnable<Integer> info) {
|
||||
InternalBiomeUtils.injectBiomesIntoClimate(random, DRY_BIOMES, OverworldClimate.DRY, info::setReturnValue);
|
||||
}
|
||||
|
||||
@Inject(at = @At(value = "FIELD", target = "Lnet/minecraft/world/biome/layer/SetBaseBiomesLayer;TEMPERATE_BIOMES:[I"), method = "sample", cancellable = true)
|
||||
private void injectTemperateBiomes(LayerRandomnessSource random, int value, CallbackInfoReturnable<Integer> info) {
|
||||
InternalBiomeUtils.injectBiomesIntoClimate(random, TEMPERATE_BIOMES, OverworldClimate.TEMPERATE, info::setReturnValue);
|
||||
}
|
||||
|
||||
@Inject(at = @At(value = "FIELD", target = "Lnet/minecraft/world/biome/layer/SetBaseBiomesLayer;SNOWY_BIOMES:[I"), method = "sample", cancellable = true)
|
||||
private void injectSnowyBiomes(LayerRandomnessSource random, int value, CallbackInfoReturnable<Integer> info) {
|
||||
InternalBiomeUtils.injectBiomesIntoClimate(random, SNOWY_BIOMES, OverworldClimate.SNOWY, info::setReturnValue);
|
||||
}
|
||||
|
||||
@Inject(at = @At(value = "FIELD", target = "Lnet/minecraft/world/biome/layer/SetBaseBiomesLayer;COOL_BIOMES:[I"), method = "sample", cancellable = true)
|
||||
private void injectCoolBiomes(LayerRandomnessSource random, int value, CallbackInfoReturnable<Integer> info) {
|
||||
InternalBiomeUtils.injectBiomesIntoClimate(random, COOL_BIOMES, OverworldClimate.COOL, info::setReturnValue);
|
||||
}
|
||||
|
||||
@Inject(at = @At("RETURN"), method = "sample", cancellable = true)
|
||||
private void transformVariants(LayerRandomnessSource random, int value, CallbackInfoReturnable<Integer> info) {
|
||||
int biomeId = info.getReturnValueI();
|
||||
Biome biome = Registry.BIOME.get(biomeId);
|
||||
|
||||
// Determine what special case this is...
|
||||
OverworldClimate climate;
|
||||
|
||||
if (biomeId == BADLANDS_PLATEAU_ID || biomeId == WOODED_BADLANDS_PLATEAU_ID) {
|
||||
climate = OverworldClimate.DRY;
|
||||
} else if (biomeId == JUNGLE_ID) {
|
||||
climate = OverworldClimate.TEMPERATE;
|
||||
} else if (biomeId == GIANT_TREE_TAIGA_ID) {
|
||||
climate = OverworldClimate.TEMPERATE;
|
||||
} else {
|
||||
climate = null;
|
||||
}
|
||||
|
||||
info.setReturnValue(InternalBiomeUtils.transformBiome(random, biome, climate));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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.biomes;
|
||||
|
||||
import net.fabricmc.fabric.impl.biomes.InternalBiomeData;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.biome.source.VanillaLayeredBiomeSource;
|
||||
import net.minecraft.world.gen.feature.StructureFeature;
|
||||
import org.spongepowered.asm.mixin.*;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Adds the biomes in world gen to the array for the vanilla layered biome source.
|
||||
* This helps with {@link VanillaLayeredBiomeSource#hasStructureFeature(StructureFeature)} returning correctly for modded biomes as well as in {@link VanillaLayeredBiomeSource#getTopMaterials()}}
|
||||
*/
|
||||
@Mixin(VanillaLayeredBiomeSource.class)
|
||||
public class MixinVanillaLayeredBiomeSource {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
@Mutable
|
||||
private Biome[] biomes;
|
||||
|
||||
@Unique
|
||||
private int injectionCount;
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "hasStructureFeature")
|
||||
private void hasStructureFeature(CallbackInfoReturnable<Boolean> info) {
|
||||
updateInjections();
|
||||
}
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "getTopMaterials")
|
||||
private void getTopMaterials(CallbackInfoReturnable<Set<BlockState>> info) {
|
||||
updateInjections();
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void updateInjections() {
|
||||
List<Biome> injectedBiomes = InternalBiomeData.getOverworldInjectedBiomes();
|
||||
int currentSize = injectedBiomes.size();
|
||||
if (this.injectionCount < currentSize) {
|
||||
List<Biome> toInject = injectedBiomes.subList(injectionCount, currentSize - 1);
|
||||
|
||||
Biome[] oldBiomes = this.biomes;
|
||||
this.biomes = new Biome[oldBiomes.length + toInject.size()];
|
||||
System.arraycopy(oldBiomes, 0, this.biomes, 0, oldBiomes.length);
|
||||
|
||||
int index = oldBiomes.length;
|
||||
for (Biome injected : toInject) {
|
||||
biomes[index++] = injected;
|
||||
}
|
||||
|
||||
injectionCount += toInject.size();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"required": true,
|
||||
"package": "net.fabricmc.fabric.mixin.biomes",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"mixins": [
|
||||
"MixinAddEdgeBiomesLayer",
|
||||
"MixinAddHillsLayer",
|
||||
"MixinAddRiversLayer",
|
||||
"MixinBiomeSource",
|
||||
"MixinSetBaseBiomesLayer",
|
||||
"MixinVanillaLayeredBiomeSource"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
18
fabric-biomes-v1/src/main/resources/fabric.mod.json
Normal file
18
fabric-biomes-v1/src/main/resources/fabric.mod.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "fabric-biomes-v1",
|
||||
"version": "${version}",
|
||||
|
||||
"name": "fabric-biomes-v1",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
"environment": "*",
|
||||
|
||||
"depends": {
|
||||
"fabricloader": ">=0.4.0"
|
||||
},
|
||||
|
||||
"mixins": [
|
||||
"fabric-biomes-v1.mixins.json"
|
||||
]
|
||||
}
|
|
@ -14,6 +14,7 @@ rootProject.name = "fabric-api"
|
|||
|
||||
include 'fabric-api-base'
|
||||
|
||||
include 'fabric-biomes-v1'
|
||||
include 'fabric-commands-v0'
|
||||
include 'fabric-containers-v0'
|
||||
include 'fabric-content-registries-v0'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue