package electrosphere.server.terrain.manager; import electrosphere.game.server.world.ServerWorldData; import electrosphere.server.terrain.diskmap.ChunkDiskMap; import electrosphere.server.terrain.generation.TestGenerationChunkGenerator; 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 chunkCache; List 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(); this.chunkCacheContents = new CopyOnWriteArrayList(); 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]); } if(floatView.position() > 0){ 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); } /** * Generates a test terrain model * @param chunkGen The chunk generator */ public void genTestData(TestGenerationChunkGenerator chunkGen){ this.model = TerrainModel.generateTestModel(); chunkGen.setModel(model); } 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; } /** * Gets the chunk generator of the terrain manager * @return The chunk generator */ public ChunkGenerator getChunkGenerator(){ return chunkGenerator; } }