/*
* Minecraft Forge
* Copyright (c) 2016.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.common;
import java.util.*;
import javax.annotation.Nonnull;
import net.minecraft.init.Biomes;
import net.minecraft.init.Blocks;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.biome.*;
import static net.minecraftforge.common.BiomeDictionary.Type.*;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.registry.ForgeRegistries;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
public class BiomeDictionary
{
public static final class Type
{
private static final Map<String, Type> byName = new HashMap<String, Type>();
/*Temperature-based tags. Specifying neither implies a biome is temperate*/
public static final Type HOT = new Type("HOT");
public static final Type COLD = new Type("COLD");
/*Tags specifying the amount of vegetation a biome has. Specifying neither implies a biome to have moderate amounts*/
public static final Type SPARSE = new Type("SPARSE");
public static final Type DENSE = new Type("DENSE");
/*Tags specifying how moist a biome is. Specifying neither implies the biome as having moderate humidity*/
public static final Type WET = new Type("WET");
public static final Type DRY = new Type("DRY");
/*Tree-based tags, SAVANNA refers to dry, desert-like trees (Such as Acacia), CONIFEROUS refers to snowy trees (Such as Spruce) and JUNGLE refers to jungle trees.
* Specifying no tag implies a biome has temperate trees (Such as Oak)*/
public static final Type SAVANNA = new Type("SAVANNA");
public static final Type CONIFEROUS = new Type("CONIFEROUS");
public static final Type JUNGLE = new Type("JUNGLE");
/*Tags specifying the nature of a biome*/
public static final Type SPOOKY = new Type("SPOOKY");
public static final Type DEAD = new Type("DEAD");
public static final Type LUSH = new Type("LUSH");
public static final Type NETHER = new Type("NETHER");
public static final Type END = new Type("END");
public static final Type MUSHROOM = new Type("MUSHROOM");
public static final Type MAGICAL = new Type("MAGICAL");
public static final Type RARE = new Type("RARE");
public static final Type OCEAN = new Type("OCEAN");
public static final Type RIVER = new Type("RIVER");
/**
* A general tag for all water-based biomes. Shown as present if OCEAN or RIVER are.
**/
public static final Type WATER = new Type("WATER", OCEAN, RIVER);
/*Generic types which a biome can be*/
public static final Type MESA = new Type("MESA");
public static final Type FOREST = new Type("FOREST");
public static final Type PLAINS = new Type("PLAINS");
public static final Type MOUNTAIN = new Type("MOUNTAIN");
public static final Type HILLS = new Type("HILLS");
public static final Type SWAMP = new Type("SWAMP");
public static final Type SANDY = new Type("SANDY");
public static final Type SNOWY = new Type("SNOWY");
public static final Type WASTELAND = new Type("WASTELAND");
public static final Type BEACH = new Type("BEACH");
public static final Type VOID = new Type("VOID");
private final String name;
private final List<Type> subTypes;
private final Set<Biome> biomes = new HashSet<Biome>();
private final Set<Biome> biomesUn = Collections.unmodifiableSet(biomes);
private Type(String name, Type... subTypes)
{
this.name = name;
this.subTypes = ImmutableList.copyOf(subTypes);
byName.put(name, this);
}
private boolean hasSubTypes()
{
return !subTypes.isEmpty();
}
/**
* Gets the name for this type.
*/
public String getName()
{
return name;
}
public String toString()
{
return name;
}
/**
* Retrieves a Type instance by name,
* if one does not exist already it creates one.
* This can be used as intermediate measure for modders to
* add their own Biome types.
* <p>
* There are <i>no</i> naming conventions besides:
* <ul><li><b>Must</b> be all upper case (enforced by name.toUpper())</li>
* <li><b>No</b> Special characters. {Unenforced, just don't be a pain, if it becomes a issue I WILL
* make this RTE with no worry about backwards compatibility}</li></ul>
* <p>
* Note: For performance sake, the return value of this function SHOULD be cached.
* Two calls with the same name SHOULD return the same value.
*
* @param name The name of this Type
* @return An instance of Type for this name.
*/
public static Type getType(String name, Type... subTypes)
{
name = name.toUpperCase();
Type t = byName.get(name);
if (t == null)
{
t = new Type(name, subTypes);
}
return t;
}
}
private static final Map<ResourceLocation, BiomeInfo> biomeInfoMap = new HashMap<ResourceLocation, BiomeInfo>();
private static class BiomeInfo
{
private final Set<Type> types = new HashSet<Type>();
private final Set<Type> typesUn = Collections.unmodifiableSet(this.types);
}
static
{
registerVanillaBiomes();
}
/**
* Adds the given types to the biome.
*
*/
public static void addTypes(Biome biome, Type... types)
{
Preconditions.checkArgument(ForgeRegistries.BIOMES.containsValue(biome), "Cannot add types to unregistered biome %s", biome);
Collection<Type> supertypes = listSupertypes(types);
for (Type type : supertypes)
{
type.biomes.add(biome);
}
BiomeInfo biomeInfo = getBiomeInfo(biome);
Collections.addAll(biomeInfo.types, types);
biomeInfo.types.addAll(supertypes);
}
/**
* Gets the set of biomes that have the given type.
*
*/
@Nonnull
public static Set<Biome> getBiomes(Type type)
{
return type.biomesUn;
}
/**
* Gets the set of types that have been added to the given biome.
*
*/
@Nonnull
public static Set<Type> getTypes(Biome biome)
{
ensureHasTypes(biome);
return getBiomeInfo(biome).typesUn;
}
/**
* Checks if the two given biomes have types in common.
*
* @return returns true if a common type is found, false otherwise
*/
public static boolean areSimilar(Biome biomeA, Biome biomeB)
{
for (Type type : getTypes(biomeA))
{
if (getTypes(biomeB).contains(type))
{
return true;
}
}
return false;
}
/**
* Checks if the given type has been added to the given biome.
*
*/
public static boolean hasType(Biome biome, Type type)
{
return getTypes(biome).contains(type);
}
/**
* Checks if any type has been added to the given biome.
*
*/
public static boolean hasAnyType(Biome biome)
{
return !getBiomeInfo(biome).types.isEmpty();
}
/**
* Automatically adds appropriate types to a given biome based on certain heuristics.
* If a biome's types are requested and no types have been added to the biome so far, the biome's types
* will be determined and added using this method.
*
*/
public static void makeBestGuess(Biome biome)
{
if (biome.theBiomeDecorator.treesPerChunk >= 3)
{
if (biome.isHighHumidity() && biome.getTemperature() >= 0.9F)
{
BiomeDictionary.addTypes(biome, JUNGLE);
}
else if (!biome.isHighHumidity())
{
BiomeDictionary.addTypes(biome, FOREST);
if (biome.getTemperature() <= 0.2f)
{
BiomeDictionary.addTypes(biome, CONIFEROUS);
}
}
}
else if (biome.getHeightVariation() <= 0.3F && biome.getHeightVariation() >= 0.0F)
{
if (!biome.isHighHumidity() || biome.getBaseHeight() >= 0.0F)
{
BiomeDictionary.addTypes(biome, PLAINS);
}
}
if (biome.getRainfall() > 0.85f)
{
BiomeDictionary.addTypes(biome, WET);
}
if (biome.getRainfall() < 0.15f)
{
BiomeDictionary.addTypes(biome, DRY);
}
if (biome.getTemperature() > 0.85f)
{
BiomeDictionary.addTypes(biome, HOT);
}
if (biome.getTemperature() < 0.15f)
{
BiomeDictionary.addTypes(biome, COLD);
}
if (biome.theBiomeDecorator.treesPerChunk > 0 && biome.theBiomeDecorator.treesPerChunk < 3)
{
BiomeDictionary.addTypes(biome, SPARSE);
}
else if (biome.theBiomeDecorator.treesPerChunk >= 10)
{
BiomeDictionary.addTypes(biome, DENSE);
}
if (biome.isHighHumidity() && biome.getBaseHeight() < 0.0F && (biome.getHeightVariation() <= 0.3F && biome.getHeightVariation() >= 0.0F))
{
BiomeDictionary.addTypes(biome, SWAMP);
}
if (biome.getBaseHeight() <= -0.5F)
{
if (biome.getHeightVariation() == 0.0F)
{
BiomeDictionary.addTypes(biome, RIVER);
}
else
{
BiomeDictionary.addTypes(biome, OCEAN);
}
}
if (biome.getHeightVariation() >= 0.4F && biome.getHeightVariation() < 1.5F)
{
BiomeDictionary.addTypes(biome, HILLS);
}
if (biome.getHeightVariation() >= 1.5F)
{
BiomeDictionary.addTypes(biome, MOUNTAIN);
}
if (biome.getEnableSnow())
{
BiomeDictionary.addTypes(biome, SNOWY);
}
if (biome.topBlock != Blocks.SAND && biome.getTemperature() >= 1.0f && biome.getRainfall() < 0.2f)
{
BiomeDictionary.addTypes(biome, SAVANNA);
}
if (biome.topBlock == Blocks.SAND)
{
BiomeDictionary.addTypes(biome, SANDY);
}
else if (biome.topBlock == Blocks.MYCELIUM)
{
BiomeDictionary.addTypes(biome, MUSHROOM);
}
if (biome.fillerBlock == Blocks.HARDENED_CLAY)
{
BiomeDictionary.addTypes(biome, MESA);
}
}
//Internal implementation
private static BiomeInfo getBiomeInfo(Biome biome)
{
BiomeInfo info = biomeInfoMap.get(biome.getRegistryName());
if (info == null)
{
info = new BiomeInfo();
biomeInfoMap.put(biome.getRegistryName(), info);
}
return info;
}
/**
* Ensure that at least one type has been added to the given biome.
*/
static void ensureHasTypes(Biome biome)
{
if (!hasAnyType(biome))
{
makeBestGuess(biome);
FMLLog.warning("No types have been added to Biome %s, types have been assigned on a best-effort guess: %s", biome.getRegistryName(), getTypes(biome));
}
}
private static Collection<Type> listSupertypes(Type... types)
{
Set<Type> supertypes = new HashSet<Type>();
Deque<Type> next = new ArrayDeque<Type>();
Collections.addAll(next, types);
while (!next.isEmpty())
{
Type type = next.remove();
for (Type sType : Type.byName.values())
{
if (type.subTypes.contains(type) && supertypes.add(sType))
next.add(sType);
}
}
return supertypes;
}
private static void registerVanillaBiomes()
{
addTypes(Biomes.OCEAN, OCEAN );
addTypes(Biomes.PLAINS, PLAINS );
addTypes(Biomes.DESERT, HOT, DRY, SANDY );
addTypes(Biomes.EXTREME_HILLS, MOUNTAIN, HILLS );
addTypes(Biomes.FOREST, FOREST );
addTypes(Biomes.TAIGA, COLD, CONIFEROUS, FOREST );
addTypes(Biomes.SWAMPLAND, WET, SWAMP );
addTypes(Biomes.RIVER, RIVER );
addTypes(Biomes.HELL, HOT, DRY, NETHER );
addTypes(Biomes.SKY, COLD, DRY, END );
addTypes(Biomes.FROZEN_OCEAN, COLD, OCEAN, SNOWY );
addTypes(Biomes.FROZEN_RIVER, COLD, RIVER, SNOWY );
addTypes(Biomes.ICE_PLAINS, COLD, SNOWY, WASTELAND );
addTypes(Biomes.ICE_MOUNTAINS, COLD, SNOWY, MOUNTAIN );
addTypes(Biomes.MUSHROOM_ISLAND, MUSHROOM, RARE );
addTypes(Biomes.MUSHROOM_ISLAND_SHORE, MUSHROOM, BEACH, RARE );
addTypes(Biomes.BEACH, BEACH );
addTypes(Biomes.DESERT_HILLS, HOT, DRY, SANDY, HILLS );
addTypes(Biomes.FOREST_HILLS, FOREST, HILLS );
addTypes(Biomes.TAIGA_HILLS, COLD, CONIFEROUS, FOREST, HILLS );
addTypes(Biomes.EXTREME_HILLS_EDGE, MOUNTAIN );
addTypes(Biomes.JUNGLE, HOT, WET, DENSE, JUNGLE );
addTypes(Biomes.JUNGLE_HILLS, HOT, WET, DENSE, JUNGLE, HILLS );
addTypes(Biomes.JUNGLE_EDGE, HOT, WET, JUNGLE, FOREST, RARE );
addTypes(Biomes.DEEP_OCEAN, OCEAN );
addTypes(Biomes.STONE_BEACH, BEACH );
addTypes(Biomes.COLD_BEACH, COLD, BEACH, SNOWY );
addTypes(Biomes.BIRCH_FOREST, FOREST );
addTypes(Biomes.BIRCH_FOREST_HILLS, FOREST, HILLS );
addTypes(Biomes.ROOFED_FOREST, SPOOKY, DENSE, FOREST );
addTypes(Biomes.COLD_TAIGA, COLD, CONIFEROUS, FOREST, SNOWY );
addTypes(Biomes.COLD_TAIGA_HILLS, COLD, CONIFEROUS, FOREST, SNOWY, HILLS );
addTypes(Biomes.REDWOOD_TAIGA, COLD, CONIFEROUS, FOREST );
addTypes(Biomes.REDWOOD_TAIGA_HILLS, COLD, CONIFEROUS, FOREST, HILLS );
addTypes(Biomes.EXTREME_HILLS_WITH_TREES, MOUNTAIN, FOREST, SPARSE );
addTypes(Biomes.SAVANNA, HOT, SAVANNA, PLAINS, SPARSE );
addTypes(Biomes.SAVANNA_PLATEAU, HOT, SAVANNA, PLAINS, SPARSE, RARE );
addTypes(Biomes.MESA, MESA, SANDY );
addTypes(Biomes.MESA_ROCK, MESA, SPARSE, SANDY );
addTypes(Biomes.MESA_CLEAR_ROCK, MESA, SANDY );
addTypes(Biomes.VOID, VOID );
addTypes(Biomes.MUTATED_PLAINS, PLAINS, RARE );
addTypes(Biomes.MUTATED_DESERT, HOT, DRY, SANDY, RARE );
addTypes(Biomes.MUTATED_EXTREME_HILLS, MOUNTAIN, SPARSE, RARE );
addTypes(Biomes.MUTATED_FOREST, FOREST, HILLS, RARE );
addTypes(Biomes.MUTATED_TAIGA, COLD, CONIFEROUS, FOREST, MOUNTAIN, RARE );
addTypes(Biomes.MUTATED_SWAMPLAND, WET, SWAMP, HILLS, RARE );
addTypes(Biomes.MUTATED_ICE_FLATS, COLD, SNOWY, HILLS, RARE );
addTypes(Biomes.MUTATED_JUNGLE, HOT, WET, DENSE, JUNGLE, MOUNTAIN, RARE);
addTypes(Biomes.MUTATED_JUNGLE_EDGE, HOT, SPARSE, JUNGLE, HILLS, RARE );
addTypes(Biomes.MUTATED_BIRCH_FOREST, FOREST, DENSE, HILLS, RARE );
addTypes(Biomes.MUTATED_BIRCH_FOREST_HILLS, FOREST, DENSE, MOUNTAIN, RARE );
addTypes(Biomes.MUTATED_ROOFED_FOREST, SPOOKY, DENSE, FOREST, MOUNTAIN, RARE );
addTypes(Biomes.MUTATED_TAIGA_COLD, COLD, CONIFEROUS, FOREST, SNOWY, MOUNTAIN, RARE);
addTypes(Biomes.MUTATED_REDWOOD_TAIGA, DENSE, FOREST, RARE );
addTypes(Biomes.MUTATED_REDWOOD_TAIGA_HILLS, DENSE, FOREST, HILLS, RARE );
addTypes(Biomes.MUTATED_EXTREME_HILLS_WITH_TREES, MOUNTAIN, SPARSE, RARE );
addTypes(Biomes.MUTATED_SAVANNA, HOT, DRY, SPARSE, SAVANNA, MOUNTAIN, RARE);
addTypes(Biomes.MUTATED_SAVANNA_ROCK, HOT, DRY, SPARSE, SAVANNA, HILLS, RARE);
addTypes(Biomes.MUTATED_MESA, HOT, DRY, SPARSE, SAVANNA, MOUNTAIN, RARE);
addTypes(Biomes.MUTATED_MESA_ROCK, HOT, DRY, SPARSE, HILLS, RARE );
addTypes(Biomes.MUTATED_MESA_CLEAR_ROCK, HOT, DRY, SPARSE, SAVANNA, MOUNTAIN, RARE);
}
}