285 lines
9.6 KiB
Java
285 lines
9.6 KiB
Java
package electrosphere.server.terrain.manager;
|
|
|
|
import electrosphere.game.server.world.ServerWorldData;
|
|
import electrosphere.server.terrain.diskmap.ChunkDiskMap;
|
|
import electrosphere.server.terrain.generation.continentphase.TerrainGenerator;
|
|
import electrosphere.server.terrain.generation.interfaces.ChunkGenerator;
|
|
import electrosphere.server.terrain.models.TerrainModel;
|
|
import electrosphere.server.terrain.models.TerrainModification;
|
|
import electrosphere.util.FileUtils;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.FloatBuffer;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
|
|
import org.joml.Vector3i;
|
|
|
|
/**
|
|
* Provides an interface for the server to query information about terrain
|
|
*/
|
|
public class ServerTerrainManager {
|
|
|
|
/**
|
|
* Full world discrete size
|
|
*/
|
|
public static final int WORLD_SIZE_DISCRETE = 1000;
|
|
|
|
/**
|
|
* Vertical interp ratio
|
|
*/
|
|
public static final int VERTICAL_INTERPOLATION_RATIO = 50;
|
|
|
|
//the interpolation width of a server terrain manager is hard coded at the moment to make sure it's divisible by the sub chunk calculations
|
|
public static final int SERVER_TERRAIN_MANAGER_INTERPOLATION_RATIO = 128;
|
|
|
|
/**
|
|
* The dampening ratio for ground noise
|
|
*/
|
|
public static final float SERVER_TERRAIN_MANAGER_DAMPENER = 1.0f;
|
|
|
|
/**
|
|
* The parent world data
|
|
*/
|
|
ServerWorldData parent;
|
|
|
|
//the seed for terrain generation
|
|
long seed;
|
|
|
|
//The model of the terrain this manager is managing
|
|
TerrainModel model;
|
|
|
|
|
|
//In memory cache of chunk data
|
|
//Basic idea is we associate string that contains chunk x&y&z with elevation
|
|
//While we incur a penalty with converting ints -> string, think this will
|
|
//offset regenerating the array every time we want a new one
|
|
int cacheSize = 500;
|
|
Map<String, ServerTerrainChunk> chunkCache;
|
|
List<String> chunkCacheContents;
|
|
|
|
//The map of chunk position <-> file on disk containing chunk data
|
|
ChunkDiskMap chunkDiskMap = null;
|
|
|
|
//The generation algorithm for this terrain manager
|
|
ChunkGenerator chunkGenerator;
|
|
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public ServerTerrainManager(
|
|
ServerWorldData parent,
|
|
long seed,
|
|
ChunkGenerator chunkGenerator
|
|
){
|
|
this.parent = parent;
|
|
this.chunkCache = new ConcurrentHashMap<String, ServerTerrainChunk>();
|
|
this.chunkCacheContents = new CopyOnWriteArrayList<String>();
|
|
this.seed = seed;
|
|
this.chunkGenerator = chunkGenerator;
|
|
}
|
|
|
|
ServerTerrainManager(){
|
|
|
|
}
|
|
|
|
/**
|
|
* Generates a terrain model for the manager
|
|
*/
|
|
public void generate(){
|
|
TerrainGenerator terrainGen = new TerrainGenerator();
|
|
terrainGen.setInterpolationRatio(parent.getWorldSizeDiscrete()/200);
|
|
terrainGen.setVerticalInterpolationRatio(parent.getWorldSizeDiscrete());
|
|
terrainGen.setRandomSeed(seed);
|
|
model = terrainGen.generateModel();
|
|
this.chunkGenerator.setModel(model);
|
|
model.setInterpolationRandomDampener(SERVER_TERRAIN_MANAGER_DAMPENER);
|
|
this.chunkDiskMap = new ChunkDiskMap();
|
|
}
|
|
|
|
/**
|
|
* Saves the terrain model backing this manager to a save file
|
|
* @param saveName The name of the save
|
|
*/
|
|
public void save(String saveName){
|
|
if(model != null){
|
|
ByteBuffer buffer = ByteBuffer.allocate(model.getElevation().length * model.getElevation()[0].length * 4);
|
|
FloatBuffer floatView = buffer.asFloatBuffer();
|
|
for(int x = 0; x < model.getElevation().length; x++){
|
|
floatView.put(model.getElevation()[x]);
|
|
}
|
|
floatView.flip();
|
|
FileUtils.serializeObjectToSavePath(saveName, "./terrain.json", model);
|
|
FileUtils.saveBinaryToSavePath(saveName, "./terrain.dat", buffer.array());
|
|
}
|
|
//for each chunk, save via disk map
|
|
for(String chunkKey : chunkCacheContents){
|
|
ServerTerrainChunk chunk = chunkCache.get(chunkKey);
|
|
chunkDiskMap.saveToDisk(chunk);
|
|
}
|
|
//save disk map itself
|
|
if(chunkDiskMap != null){
|
|
chunkDiskMap.save();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads a terrain manager from a save file
|
|
* @param saveName The name of the save
|
|
*/
|
|
public void load(String saveName){
|
|
//load terrain model
|
|
if(FileUtils.getSaveFile(saveName, "./terrain.json").exists()){
|
|
model = FileUtils.loadObjectFromSavePath(saveName, "./terrain.json", TerrainModel.class);
|
|
chunkGenerator.setModel(model);
|
|
byte[] data = FileUtils.loadBinaryFromSavePath(saveName, "./terrain.dat");
|
|
ByteBuffer buffer = ByteBuffer.wrap(data);
|
|
FloatBuffer floatView = buffer.asFloatBuffer();
|
|
float[][] elevation = new float[parent.getWorldSizeDiscrete()][parent.getWorldSizeDiscrete()];
|
|
for(int x = 0; x < parent.getWorldSizeDiscrete(); x++){
|
|
for(int y = 0; y < parent.getWorldSizeDiscrete(); y++){
|
|
elevation[x][y] = floatView.get();
|
|
}
|
|
}
|
|
model.setElevationArray(elevation);
|
|
}
|
|
//load chunk disk map
|
|
chunkDiskMap = new ChunkDiskMap();
|
|
chunkDiskMap.init(saveName);
|
|
}
|
|
|
|
public float[][] getTerrainAtChunk(int x, int y){
|
|
return model.getElevationForChunk(x, y);
|
|
}
|
|
|
|
public double getHeightAtPosition(double x, double y, double z){
|
|
return y;
|
|
}
|
|
|
|
public float getDiscreteValue(int x, int y){
|
|
if(model != null){
|
|
return model.getElevation()[x][y];
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public int getDynamicInterpolationRatio(){
|
|
//THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING
|
|
if(model != null){
|
|
return model.getDynamicInterpolationRatio();
|
|
} else {
|
|
//THIS FIRES IF THERE IS AN ARENA WORLD RUNNING
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public float getRandomDampener(){
|
|
//THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING
|
|
if(model != null){
|
|
return model.getRandomDampener();
|
|
} else {
|
|
//THIS FIRES IF THERE IS AN ARENA WORLD RUNNING
|
|
return 0.0f;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the terrain model backing this terrain manager
|
|
* @return The terrain model
|
|
*/
|
|
public TerrainModel getModel() {
|
|
return model;
|
|
}
|
|
|
|
/**
|
|
* Gets the key for a given world position
|
|
* @param worldX The x component
|
|
* @param worldY The y component
|
|
* @param worldZ The z component
|
|
* @return The key
|
|
*/
|
|
public String getKey(int worldX, int worldY, int worldZ){
|
|
return worldX + "_" + worldY + "_" + worldZ;
|
|
}
|
|
|
|
/**
|
|
* Gets a server terrain chunk
|
|
* @param worldX The world x position
|
|
* @param worldY The world y position
|
|
* @param worldZ The world z position
|
|
* @return The ServerTerrainChunk
|
|
*/
|
|
public ServerTerrainChunk getChunk(int worldX, int worldY, int worldZ){
|
|
//THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING
|
|
String key = getKey(worldX,worldY,worldZ);
|
|
ServerTerrainChunk returnedChunk = null;
|
|
if(chunkCache.containsKey(key)){
|
|
chunkCacheContents.remove(key);
|
|
chunkCacheContents.add(0, key);
|
|
returnedChunk = chunkCache.get(key);
|
|
return returnedChunk;
|
|
} else {
|
|
if(chunkCacheContents.size() >= cacheSize){
|
|
String oldChunk = chunkCacheContents.remove(chunkCacheContents.size() - 1);
|
|
chunkCache.remove(oldChunk);
|
|
}
|
|
//pull from disk if it exists
|
|
if(chunkDiskMap != null){
|
|
if(chunkDiskMap.containsTerrainAtPosition(worldX, worldY, worldZ)){
|
|
returnedChunk = chunkDiskMap.getTerrainChunk(worldX, worldY, worldZ);
|
|
}
|
|
}
|
|
//generate if it does not exist
|
|
if(returnedChunk == null){
|
|
returnedChunk = chunkGenerator.generateChunk(worldX, worldY, worldZ);
|
|
}
|
|
chunkCache.put(key, returnedChunk);
|
|
chunkCacheContents.add(key);
|
|
return returnedChunk;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Saves a given position's chunk to disk.
|
|
* Uses the current global save name
|
|
* @param position The position to save
|
|
*/
|
|
public void savePositionToDisk(Vector3i position){
|
|
if(chunkDiskMap != null){
|
|
chunkDiskMap.saveToDisk(getChunk(position.x, position.y, position.z));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Applies a deform to terrain at a given location
|
|
* @param worldPos The world coordinates of the chunk to modify
|
|
* @param voxelPos The voxel coordinates of the voxel to modify
|
|
* @param weight The weight to set it to
|
|
* @param value The value to set it to
|
|
*/
|
|
public void deformTerrainAtLocationToValue(Vector3i worldPos, Vector3i voxelPos, float weight, int value){
|
|
TerrainModification modification = new TerrainModification(worldPos,voxelPos,weight,value);
|
|
//could be null if, for instance, arena mode
|
|
if(model != null){
|
|
model.addModification(modification);
|
|
}
|
|
String key = getKey(worldPos.x,worldPos.y,worldPos.z);
|
|
if(chunkCache.containsKey(key)){
|
|
ServerTerrainChunk chunk = chunkCache.get(key);
|
|
chunk.addModification(modification);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the parent world data of this manager
|
|
* @param serverWorldData The parent world data
|
|
*/
|
|
public void setParent(ServerWorldData serverWorldData){
|
|
this.parent = serverWorldData;
|
|
}
|
|
|
|
}
|