/*
* Copyright 2014 MovingBlocks
*
* 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 org.terasology.world.biomes;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.module.ModuleEnvironment;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class BiomeManager implements BiomeRegistry {
private static final Logger logger = LoggerFactory.getLogger(BiomeManager.class);
// Bi Di map between biome short id and biome
private final BiMap<Short, Biome> biomeShortIdMap = HashBiMap.create();
// Map from biome id to biome
private final BiMap<String, Biome> biomeIdMap = HashBiMap.create();
/**
* Creates a new Biome Manager that auto discovers all biomes in the given environment.
*/
public BiomeManager(ModuleEnvironment environment) {
this(environment, Collections.<String, Short>emptyMap());
}
/**
* Create a BiomeManager from known state such as a world save, that already contains
* a mapping between Biome URIs and their short ids.
*
* @param knownBiomeIdMap A mapping between Biome URIs (combination of module id + biome id) and
* their short ids that are applicable to this world save.
*/
public BiomeManager(ModuleEnvironment moduleEnvironment, Map<String, Short> knownBiomeIdMap) {
for (Class<?> biomeRegistrator : moduleEnvironment.getSubtypesOf(BiomeRegistrator.class)) {
BiomeRegistrator registrator;
try {
registrator = (BiomeRegistrator) biomeRegistrator.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
logger.error("Cannot call biome registrator {} because it cannot be instantiated.", biomeRegistrator, e);
continue;
}
registrator.registerBiomes(this);
}
BiMap<Short, Biome> currentIdMap = HashBiMap.create(biomeShortIdMap); // Make a copy before we start modifying it
biomeShortIdMap.clear();
// Always register the unknown biome first, so it gets id 0, which is the default for all chunks
registerBiome(UnknownBiome.INSTANCE);
for (Map.Entry<String, Short> entry : knownBiomeIdMap.entrySet()) {
if (entry.getKey().equals(getUnknownBiome().getId())) {
continue; // The unknown biome is handled internally
}
Biome biome = biomeIdMap.get(entry.getKey());
if (biome == null) {
throw new IllegalStateException("Save game references biome " + entry.getKey() + " which is no " +
"longer available.");
}
if (biomeShortIdMap.put(entry.getValue(), biome) != null) {
throw new IllegalStateException("Biome short id " + entry.getValue() + " is present multiple times " +
"in the save game (latest is mapped to " + biome.getId() + ".");
}
logger.info("Restored biome {} with short id {} from save game.", entry.getKey(), entry.getValue());
currentIdMap.values().remove(biome);
}
// Handle all new biomes that weren't present in the save game
for (Biome biome : currentIdMap.values()) {
short freeBiomeId = getFreeBiomeId();
biomeShortIdMap.put(freeBiomeId, biome);
logger.info("Registered new biome {} with id {} that wasn't present in the save game.", biome.getId(),
freeBiomeId);
}
}
public String getBiomeId(Biome biome) {
return biomeIdMap.inverse().get(biome);
}
@Override
public void registerBiome(Biome biome) {
String fullId = biome.getId();
if (biomeShortIdMap.containsValue(biome)) {
throw new IllegalArgumentException("The biome " + fullId + " is already registered.");
}
if (biomeIdMap.containsKey(fullId)) {
throw new IllegalArgumentException("A biome with id " + fullId + " is already registered!");
}
short biomeShortId = getFreeBiomeId();
logger.info("Registering biome {} with short id {}.", biome, biomeShortId);
biomeShortIdMap.put(biomeShortId, biome);
biomeIdMap.put(fullId, biome);
}
@Override
public Biome getBiomeById(String id) {
return biomeIdMap.get(id);
}
@Override
public List<Biome> getBiomes() {
return ImmutableList.copyOf(biomeIdMap.values());
}
/**
* Returns a biome with the given id and of the given biome type, only if the biome actually is of the given type.
*
* @param id
* @param biomeClass
* @param <T>
* @return
*/
@Override
public <T extends Biome> T getBiomeById(String id, Class<T> biomeClass) {
Biome biome = getBiomeById(id);
if (biome != null) {
if (biomeClass.isAssignableFrom(biome.getClass())) {
return biomeClass.cast(biome);
}
}
return null;
}
@Override
public <T extends Biome> List<T> getBiomes(Class<T> biomeClass) {
ImmutableList.Builder<T> builder = ImmutableList.builder();
biomeIdMap.values().stream().filter(biome -> biomeClass.isAssignableFrom(biome.getClass())).forEach(biome ->
builder.add(biomeClass.cast(biome)));
return builder.build();
}
public Biome getBiomeByShortId(short biomeShortId) {
Biome result = biomeShortIdMap.get(biomeShortId);
if (result == null) {
result = getUnknownBiome();
}
return result;
}
public short getBiomeShortId(Biome biome) {
Short result = biomeShortIdMap.inverse().get(biome);
if (result == null) {
throw new IllegalArgumentException("No short id for biome " + biome + " exists.");
}
return result;
}
private short getFreeBiomeId() {
short id = 0;
while (id < Short.MAX_VALUE) {
if (!biomeShortIdMap.containsKey(id)) {
return id;
}
id++;
}
throw new IllegalStateException("The maximum number of biomes has been reached: " + biomeShortIdMap.size());
}
/**
* @return A biome that can be used to avoid a null pointer in cases where the real biome is unknown, i.e.
* when the chunk has not yet been loaded.
* <br><br>
* TODO: Decide how a caller can determine that he got the unknown biome and not the real one.
*/
public static Biome getUnknownBiome() {
return UnknownBiome.INSTANCE;
}
}