package electrosphere.server.datacell; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Semaphore; import org.joml.Vector3d; import org.joml.Vector3i; import electrosphere.engine.Globals; import electrosphere.engine.threads.LabeledThread.ThreadLabel; import electrosphere.entity.Entity; import electrosphere.entity.EntityUtils; import electrosphere.entity.ServerEntityUtils; import electrosphere.entity.state.server.ServerPlayerViewDirTree; import electrosphere.game.server.world.ServerWorldData; import electrosphere.logger.LoggerInterface; import electrosphere.net.parser.net.message.EntityMessage; import electrosphere.net.parser.net.message.TerrainMessage; import electrosphere.net.server.player.Player; import electrosphere.net.server.protocol.TerrainProtocol; import electrosphere.server.content.ServerContentManager; import electrosphere.server.datacell.interfaces.DataCellManager; import electrosphere.server.datacell.interfaces.VoxelCellManager; import electrosphere.server.datacell.physics.PhysicsDataCell; import electrosphere.server.fluid.manager.ServerFluidChunk; import electrosphere.server.fluid.manager.ServerFluidManager; import electrosphere.server.terrain.manager.ServerTerrainManager; import electrosphere.server.terrain.models.TerrainModel; import io.github.studiorailgun.HashUtils; import electrosphere.server.terrain.manager.ServerTerrainChunk; /** * Implementation of DataCellManager that lays out cells in a logical grid (array). Useful for eg 3d terrain gridded world. */ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager { /** * The minimum grid size allowed */ public static final int MIN_GRID_SIZE = 1; /** * The max grid size allowed */ public static final int MAX_GRID_SIZE = TerrainModel.MAX_MACRO_DATA_SIZE * TerrainModel.DEFAULT_MACRO_DATA_SCALE * ServerTerrainChunk.CHUNK_DIMENSION; /** * The number of frames without players that must pass before a server data cell is unloaded */ static final int UNLOAD_FRAME_THRESHOLD = 100; /** * Tracks whether this manager has been flagged to unload cells or not */ boolean unloadCells = true; /** * These are going to be the natural ground grid of data cells, but we're going to have more than this */ Map groundDataCells = new HashMap(); /** * Map of server cell to its world position */ Map cellPositionMap = new HashMap(); /** * Map of server data cell to the number of frames said cell has had no players */ Map cellPlayerlessFrameMap = new HashMap(); /** * Loaded cells */ Semaphore loadedCellsLock = new Semaphore(1); /** * Parent realm */ Realm parent; /** * The world data of the parent */ ServerWorldData serverWorldData; /** * Manager for terrain for this particular cell manager */ ServerTerrainManager serverTerrainManager; /** * Manager for fluids for this particular cell manager */ ServerFluidManager serverFluidManager; /** * Lock for terrain editing */ Semaphore terrainEditLock = new Semaphore(1); /** * Manager for getting entities to fill in a cell */ ServerContentManager serverContentManager; /** * Used for cleaning server data cells no longer in use from the realm */ Set toCleanQueue = new HashSet(); /** * Map of world position key -> physics cell */ Map posPhysicsMap = new HashMap(); /** * Constructor * @param parent The gridded data cell manager's parent realm */ public GriddedDataCellManager( Realm parent ) { this.parent = parent; this.serverWorldData = this.parent.getServerWorldData(); this.serverTerrainManager = serverWorldData.getServerTerrainManager(); this.serverFluidManager = serverWorldData.getServerFluidManager(); this.serverContentManager = this.parent.getServerContentManager(); //Assert the gridded data cell manager was given good data if( this.parent == null || this.serverWorldData == null || this.serverTerrainManager == null || this.serverFluidManager == null || this.serverContentManager == null ){ throw new IllegalStateException("Tried to create a GriddedDataCellManager with invalid parameters " + this.parent + " " + this.serverWorldData + " " + this.serverTerrainManager + " " + this.serverFluidManager + " " + this.serverContentManager + " " ); } } /** * Adds a player to the realm that this manager controls. Should do this intelligently based on the player's location * @param player The player */ public void addPlayerToRealm(Player player){ Globals.realmManager.setPlayerRealm(player, parent); int playerSimulationRadius = player.getSimulationRadius(); Vector3i worldPos = player.getWorldPos(); for(int x = worldPos.x - playerSimulationRadius; x < worldPos.x + playerSimulationRadius + 1; x++){ for(int y = worldPos.y - playerSimulationRadius; y < worldPos.y + playerSimulationRadius + 1; y++){ for(int z = worldPos.z - playerSimulationRadius; z < worldPos.z + playerSimulationRadius + 1; z++){ if(this.canCreateCell(x, y, z) && this.shouldContainPlayer(new Vector3i(x,y,z), worldPos, playerSimulationRadius)){ Vector3i targetPos = new Vector3i(x,y,z); LoggerInterface.loggerEngine.DEBUG("GriddedDataCellManager: Add player to " + x + " " + y + " " + z); loadedCellsLock.acquireUninterruptibly(); if(groundDataCells.get(getServerDataCellKey(targetPos)) != null){ groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player); } else { LoggerInterface.loggerEngine.DEBUG("Creating new cell @ " + x + " " + y + " " + z); //create data cell this.createServerDataCell(targetPos); //add to loaded cells cellPlayerlessFrameMap.put(groundDataCells.get(getServerDataCellKey(targetPos)),0); //add player groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player); } loadedCellsLock.release(); } } } } } /** * Moves a player to a new position * @param player The player * @param newPosition The new position */ public void movePlayer(Player player, Vector3i newPosition){ int playerSimulationRadius = player.getSimulationRadius(); player.setWorldPos(newPosition); for(ServerDataCell cell : this.groundDataCells.values()){ Vector3i worldPos = this.getCellWorldPosition(cell); if(cell.containsPlayer(player) && !this.shouldContainPlayer(worldPos, newPosition, playerSimulationRadius)){ cell.removePlayer(player); this.broadcastDestructionToPlayer(player, cell); if(cell.getScene().containsEntity(player.getPlayerEntity())){ throw new Error( "Unregistering player from cell that contains player's entity!\n " + player + "\n " + worldPos + "\n " + player.getPlayerEntity() + "\n " + EntityUtils.getPosition(player.getPlayerEntity()) ); } } } for(int x = newPosition.x - playerSimulationRadius; x < newPosition.x + playerSimulationRadius + 1; x++){ for(int y = newPosition.y - playerSimulationRadius; y < newPosition.y + playerSimulationRadius + 1; y++){ for(int z = newPosition.x - playerSimulationRadius; z < newPosition.z + playerSimulationRadius + 1; z++){ if(this.canCreateCell(x, y, z) && this.shouldContainPlayer(new Vector3i(x, y, z), newPosition, playerSimulationRadius)){ Vector3i targetPos = new Vector3i(x,y,z); if(groundDataCells.get(getServerDataCellKey(targetPos)) != null){ loadedCellsLock.acquireUninterruptibly(); groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player); loadedCellsLock.release(); } else { loadedCellsLock.acquireUninterruptibly(); //create data cell createServerDataCell(targetPos); //add to loaded cells cellPlayerlessFrameMap.put(groundDataCells.get(getServerDataCellKey(targetPos)),0); //add player groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player); loadedCellsLock.release(); } } } } } } /** * Checks if a player should be contained in a cell * @param cellWorldPos The world position of the cell * @param playerPos The world position of the player * @param simRadius The simulation radius of the player * @return true if the player should be contained in the cell, false otherwise */ private boolean shouldContainPlayer(Vector3i cellWorldPos, Vector3i playerPos, int simRadius){ return cellWorldPos.distance(playerPos) < simRadius; } /** * Checks if a cell can be created at the position * @param cellPos The position to check * @return true if a cell can be created at that position, false otherwise */ private boolean canCreateCell(Vector3i cellPos){ return this.canCreateCell(cellPos.x, cellPos.y, cellPos.z); } /** * Checks if a cell can be created at the position * @return true if a cell can be created at that position, false otherwise */ private boolean canCreateCell(int x, int y, int z){ return x >= 0 && x < this.serverWorldData.getWorldSizeDiscrete() && y >= 0 && y < this.serverWorldData.getWorldSizeDiscrete() && z >= 0 && z < this.serverWorldData.getWorldSizeDiscrete() ; } /** * Broadcasts messages to player to destroy all entities in a given cell * @param player The player * @param cell The cell */ private void broadcastDestructionToPlayer(Player player, ServerDataCell cell){ for(Entity entity : cell.getScene().getEntityList()){ player.addMessage(EntityMessage.constructDestroyMessage(entity.getId())); } } /** * Creates physics entities when new data cell being created */ private void createTerrainPhysicsEntities(Vector3i worldPos){ Long key = this.getServerDataCellKey(worldPos); if(posPhysicsMap.containsKey(key)){ PhysicsDataCell cell = posPhysicsMap.get(key); cell.retireCell(); cell.generatePhysics(); } else { PhysicsDataCell cell = PhysicsDataCell.createPhysicsCell(parent, worldPos); cell.generatePhysics(); posPhysicsMap.put(key, cell); } } /** * For every player, looks at their entity and determines what data cell they should be considered inside of * @return True if the player changed cell, false otherwise */ public boolean updatePlayerPositions(){ boolean playerChangedChunk = false; for(Player player : Globals.playerManager.getPlayers()){ Entity playerEntity = player.getPlayerEntity(); if(playerEntity != null && !parent.getLoadingDataCell().containsPlayer(player)){ Vector3d position = EntityUtils.getPosition(playerEntity); int currentWorldX = parent.getServerWorldData().convertRealToChunkSpace(position.x); int currentWorldY = parent.getServerWorldData().convertRealToChunkSpace(position.y); int currentWorldZ = parent.getServerWorldData().convertRealToChunkSpace(position.z); Vector3i newPosition = new Vector3i(currentWorldX,currentWorldY,currentWorldZ); player.setWorldPos(newPosition); int playerSimulationRadius = player.getSimulationRadius(); //remove from cells that are out of range for(ServerDataCell cell : this.groundDataCells.values()){ Vector3i cellWorldPos = this.getCellWorldPosition(cell); if(cell.containsPlayer(player) && !this.shouldContainPlayer(cellWorldPos, newPosition, playerSimulationRadius)){ if(cell.getScene().containsEntity(player.getPlayerEntity())){ throw new Error("Trying to remove player from a cell that contains its entity!"); } cell.removePlayer(player); this.broadcastDestructionToPlayer(player, cell); } } //Add to cells that are in range for(int x = newPosition.x - playerSimulationRadius + 1; x < newPosition.x + playerSimulationRadius; x++){ for(int y = newPosition.y - playerSimulationRadius + 1; y < newPosition.y + playerSimulationRadius; y++){ for(int z = newPosition.x - playerSimulationRadius + 1; z < newPosition.z + playerSimulationRadius; z++){ if(this.canCreateCell(x,y,z) && this.shouldContainPlayer(new Vector3i(x,y,z), newPosition, playerSimulationRadius)){ Vector3i targetPos = new Vector3i(x,y,z); if(groundDataCells.get(this.getServerDataCellKey(targetPos)) != null){ loadedCellsLock.acquireUninterruptibly(); ServerDataCell cell = groundDataCells.get(this.getServerDataCellKey(targetPos)); if(!cell.containsPlayer(player)){ cell.addPlayer(player); } loadedCellsLock.release(); } else { loadedCellsLock.acquireUninterruptibly(); //create data cell this.createServerDataCell(targetPos); //add to loaded cells cellPlayerlessFrameMap.put(groundDataCells.get(this.getServerDataCellKey(targetPos)),0); //add player groundDataCells.get(this.getServerDataCellKey(targetPos)).addPlayer(player); loadedCellsLock.release(); } } } } } } } return playerChangedChunk; } /** * Unloads all chunks that haven't had players in them for a set amount of time */ public void unloadPlayerlessChunks(){ if(this.unloadCells){ //TODO: improve to make have less performance impact loadedCellsLock.acquireUninterruptibly(); for(ServerDataCell cell : this.groundDataCells.values()){ if(cell.isReady() && cell.getPlayers().size() < 1){ int frameCount = cellPlayerlessFrameMap.get(cell) + 1; cellPlayerlessFrameMap.put(cell,frameCount); if(frameCount > UNLOAD_FRAME_THRESHOLD){ toCleanQueue.add(cell); } } else { if(cellPlayerlessFrameMap.get(cell) > 0){ cellPlayerlessFrameMap.put(cell, 0); } } } for(ServerDataCell cell : toCleanQueue){ boolean containsPlayerEntity = false; //clear all entities in cell for(Entity entity : cell.getScene().getEntityList()){ if(ServerPlayerViewDirTree.hasTree(entity)){ containsPlayerEntity = true; break; // int playerId = CreatureUtils.getControllerPlayerId(entity); // Player player = Globals.playerManager.getPlayerFromId(playerId); // throw new Error( // "Trying to unload a player's entity! " + // "entity: " + entity + "\n" + // "entity pos (real): " + EntityUtils.getPosition(entity) + "\n" + // "entity pos (world): " + serverWorldData.convertRealToWorldSpace(EntityUtils.getPosition(entity)) + "\n" + // "chunk pos (world): " + worldPos + "\n" + // "player pos (world): " + player.getWorldPos() + "\n" + // "Number of players in cell: " + cell.getPlayers().size() // ); } // ServerEntityUtils.destroyEntity(entity); } if(containsPlayerEntity){ continue; } parent.deregisterCell(cell); Vector3i worldPos = getCellWorldPosition(cell); Long key = getServerDataCellKey(worldPos); groundDataCells.remove(key); //offload all entities in cell to chunk file serverContentManager.saveContentToDisk(key, cell.getScene().getEntityList()); for(Entity entity : cell.getScene().getEntityList()){ ServerEntityUtils.destroyEntity(entity); } //save terrain to disk serverTerrainManager.savePositionToDisk(worldPos); } loadedCellsLock.release(); toCleanQueue.clear(); } } /** * Evicts all loaded chunks. */ public void evictAll(){ //TODO: improve to make have less performance impact loadedCellsLock.acquireUninterruptibly(); for(ServerDataCell cell : this.groundDataCells.values()){ int frameCount = cellPlayerlessFrameMap.get(cell) + 1; cellPlayerlessFrameMap.put(cell,frameCount); toCleanQueue.add(cell); } for(ServerDataCell cell : toCleanQueue){ parent.deregisterCell(cell); Vector3i worldPos = getCellWorldPosition(cell); Long key = getServerDataCellKey(worldPos); groundDataCells.remove(key); //offload all entities in cell to chunk file serverContentManager.saveContentToDisk(key, cell.getScene().getEntityList()); //clear all entities in cell for(Entity entity : cell.getScene().getEntityList()){ ServerEntityUtils.destroyEntity(entity); } } loadedCellsLock.release(); this.serverTerrainManager.evictAll(); toCleanQueue.clear(); } /** * Get data cell at a given real point in this realm * @param point The real point * @return Either the data cell if found, or null if not found */ public ServerDataCell getDataCellAtPoint(Vector3d point){ ServerDataCell rVal = null; int worldX = parent.getServerWorldData().convertRealToChunkSpace(point.x); int worldY = parent.getServerWorldData().convertRealToChunkSpace(point.y); int worldZ = parent.getServerWorldData().convertRealToChunkSpace(point.z); Vector3i worldPos = new Vector3i(worldX,worldY,worldZ); if( //in bounds of array worldX >= 0 && worldX < this.serverWorldData.getWorldSizeDiscrete() && worldY >= 0 && worldY < this.serverWorldData.getWorldSizeDiscrete() && worldZ >= 0 && worldZ < this.serverWorldData.getWorldSizeDiscrete() && //isn't null groundDataCells.get(getServerDataCellKey(worldPos)) != null ){ LoggerInterface.loggerEngine.DEBUG("Get server data cell key: " + getServerDataCellKey(worldPos)); rVal = groundDataCells.get(getServerDataCellKey(worldPos)); } else { LoggerInterface.loggerEngine.DEBUG("Failed to get server data cell at: " + worldPos); } return rVal; } /** * Tries to create a data cell at a given real point * @param point The real point * @return The data cell if created, null otherwise */ public ServerDataCell tryCreateCellAtPoint(Vector3d point){ int worldX = parent.getServerWorldData().convertRealToChunkSpace(point.x); int worldY = parent.getServerWorldData().convertRealToChunkSpace(point.y); int worldZ = parent.getServerWorldData().convertRealToChunkSpace(point.z); Vector3i worldPos = new Vector3i(worldX,worldY,worldZ); return tryCreateCellAtPoint(worldPos); } /** * Tries to create a data cell at a given discrete point * @param point The discrete point * @return The data cell if created, null otherwise */ public ServerDataCell tryCreateCellAtPoint(Vector3i worldPos){ if(this.canCreateCell(worldPos) && groundDataCells.get(this.getServerDataCellKey(worldPos)) == null){ loadedCellsLock.acquireUninterruptibly(); //create data cell this.createServerDataCell(worldPos); //add to loaded cells cellPlayerlessFrameMap.put(groundDataCells.get(this.getServerDataCellKey(worldPos)),0); loadedCellsLock.release(); } else if(groundDataCells.get(this.getServerDataCellKey(worldPos)) == null) { LoggerInterface.loggerEngine.WARNING( "Trying to create data cell outside world bounds!\n" + worldPos + "\n" + this.serverWorldData.getWorldSizeDiscrete() ); } return groundDataCells.get(this.getServerDataCellKey(worldPos)); } /** * Gets a data cell at a given world position * @param position The world position * @return The data cell if found, null otherwise */ public ServerDataCell getCellAtWorldPosition(Vector3i position){ if(this.canCreateCell(position) && groundDataCells.get(this.getServerDataCellKey(position)) != null){ return groundDataCells.get(this.getServerDataCellKey(position)); } return null; } /** * Calls the simulate function on all loaded cells */ public void simulate(){ Globals.profiler.beginCpuSample("GriddedDataCellManager.simulate"); loadedCellsLock.acquireUninterruptibly(); for(ServerDataCell cell : this.groundDataCells.values()){ if(Globals.microSimulation != null && Globals.microSimulation.isReady()){ Globals.microSimulation.simulate(cell); } //simulate fluid Vector3i cellPos = this.getCellWorldPosition(cell); boolean update = this.serverFluidManager.simulate(cellPos.x,cellPos.y,cellPos.z); if(update){ rebroadcastFluidChunk(cellPos); } } loadedCellsLock.release(); this.unloadPlayerlessChunks(); this.updatePlayerPositions(); Globals.profiler.endCpuSample(); } /** * Gets the server terrain manager for this realm if it exists * @return The server terrain manager if it exists, null otherwise */ public ServerTerrainManager getServerTerrainManager(){ return serverTerrainManager; } /** * Gets the server fluid manager for this realm if it exists * @return The server fluid manager if it exists, null otherwise */ public ServerFluidManager getServerFluidManager(){ return serverFluidManager; } /** * Runs code to generate physics entities and register cell in a dedicated thread. * Because cell hasn't been registered yet, no simulation is performed until the physics is created. * @param worldPos */ private void runPhysicsGenerationThread(Vector3i worldPos){ Thread thread = new Thread(new Runnable(){ @Override public void run() { //create physics entities createTerrainPhysicsEntities(worldPos); //set ready if(groundDataCells.get(getServerDataCellKey(worldPos)) != null){ groundDataCells.get(getServerDataCellKey(worldPos)).setReady(true); } else { LoggerInterface.loggerEngine.WARNING("Finished generating physics for server cell, but cell is null!"); } } }); groundDataCells.get(getServerDataCellKey(worldPos)).setReady(false); Globals.threadManager.start(ThreadLabel.ASSET_LOADING, thread); } /** * Gets the key in the groundDataCells map for the data cell at the provided world pos * @param worldPos The position in world coordinates of the server data cell * @return The server data cell if it exists, otherwise null */ private Long getServerDataCellKey(Vector3i worldPos){ return (long)HashUtils.cantorHash(worldPos.x, worldPos.y, worldPos.z); } /** * Registers a server data cell with the internal datastructure for tracking them * @param key The key to register the cell at * @param cell The cell itself */ private ServerDataCell createServerDataCell(Vector3i worldPos){ ServerDataCell rVal = parent.createNewCell(); Long cellKey = this.getServerDataCellKey(worldPos); groundDataCells.put(cellKey,rVal); LoggerInterface.loggerEngine.DEBUG("Create server data cell with key " + cellKey); cellPositionMap.put(rVal,new Vector3i(worldPos)); serverContentManager.generateContentForDataCell(parent, worldPos, rVal, cellKey); //generates physics for the cell in a dedicated thread then finally registers this.runPhysicsGenerationThread(worldPos); return rVal; } @Override /** * Gets the weight of a single voxel at a position * @param worldPosition The position in world coordinates of the chunk to grab data from * @param voxelPosition The position in voxel coordinates (local/relative to the chunk) to get voxel values from * @return The weight of the described voxel */ public float getVoxelWeightAtLocalPosition(Vector3i worldPosition, Vector3i voxelPosition) { return serverTerrainManager.getChunk(worldPosition.x, worldPosition.y, worldPosition.z).getWeights()[voxelPosition.x][voxelPosition.y][voxelPosition.z]; } @Override /** * Gets the type of a single voxel at a position * @param worldPosition The position in world coordinates of the chunk to grab data from * @param voxelPosition The position in voxel coordinates (local/relative to the chunk) to get voxel values from * @return The type of the described voxel */ public int getVoxelTypeAtLocalPosition(Vector3i worldPosition, Vector3i voxelPosition) { return serverTerrainManager.getChunk(worldPosition.x, worldPosition.y, worldPosition.z).getValues()[voxelPosition.x][voxelPosition.y][voxelPosition.z]; } @Override /** * Gets the chunk data at a given world position * @param worldPosition The position in world coordinates * @return The ServerTerrainChunk of data at that position, or null if it is out of bounds or otherwise doesn't exist */ public ServerTerrainChunk getChunkAtPosition(Vector3i worldPosition) { return serverTerrainManager.getChunk(worldPosition.x, worldPosition.y, worldPosition.z); } @Override /** * Edits a single voxel * @param worldPosition The world position of the chunk to edit * @param voxelPosition The voxel position of the voxel to edit * @param weight The weight to set the voxel to * @param type The type to set the voxel to */ public void editChunk(Vector3i worldPosition, Vector3i voxelPosition, float weight, int type) { terrainEditLock.acquireUninterruptibly(); //update terrain serverTerrainManager.deformTerrainAtLocationToValue(worldPosition, voxelPosition, weight, type); List worldPositionsToUpdate = new LinkedList(); worldPositionsToUpdate.add(worldPosition); if(voxelPosition.x < 1){ worldPositionsToUpdate.add(new Vector3i(worldPosition).sub(1,0,0)); if(voxelPosition.y < 1){ worldPositionsToUpdate.add(new Vector3i(worldPosition).sub(1,1,0)); if(voxelPosition.z < 1){ worldPositionsToUpdate.add(new Vector3i(worldPosition).sub(1,1,1)); } } else { if(voxelPosition.z < 1){ worldPositionsToUpdate.add(new Vector3i(worldPosition).sub(1,0,1)); } } } else { if(voxelPosition.y < 1){ worldPositionsToUpdate.add(new Vector3i(worldPosition).sub(0,1,0)); if(voxelPosition.z < 1){ worldPositionsToUpdate.add(new Vector3i(worldPosition).sub(0,1,1)); } } else { if(voxelPosition.z < 1){ worldPositionsToUpdate.add(new Vector3i(worldPosition).sub(0,0,1)); } } } //update all loaded cells for(Vector3i toUpdate : worldPositionsToUpdate){ ServerDataCell cell = groundDataCells.get(getServerDataCellKey(toUpdate)); if(cell != null){ this.createTerrainPhysicsEntities(toUpdate); cell.broadcastNetworkMessage(TerrainMessage.constructUpdateVoxelMessage( worldPosition.x, worldPosition.y, worldPosition.z, voxelPosition.x, voxelPosition.y, voxelPosition.z, weight, type)); } } terrainEditLock.release(); } /** * Gets the world position of a given data cell * @param cell The data cell * @return The world position */ public Vector3i getCellWorldPosition(ServerDataCell cell){ return cellPositionMap.get(cell); } @Override /** * Gets the fluid chunk at a given position */ public ServerFluidChunk getFluidChunkAtPosition(Vector3i worldPosition) { return serverFluidManager.getChunk(worldPosition.x, worldPosition.y, worldPosition.z); } /** * Loads all cells */ public void loadAllCells(){ this.unloadCells = false; for(int x = 0; x < this.serverWorldData.getWorldSizeDiscrete(); x++){ for(int y = 0; y < this.serverWorldData.getWorldSizeDiscrete(); y++){ for(int z = 0; z < this.serverWorldData.getWorldSizeDiscrete(); z++){ this.tryCreateCellAtPoint(new Vector3i(x,y,z)); } } } } /** * Rebroadcasts the fluid cell at a given position * @param worldPosition the world position */ private void rebroadcastFluidChunk(Vector3i worldPosition){ Globals.profiler.beginAggregateCpuSample("GriddedDataCellManager.rebroadcastFluidChunk"); ServerDataCell cell = getCellAtWorldPosition(worldPosition); ServerFluidChunk chunk = getFluidChunkAtPosition(worldPosition); cell.broadcastNetworkMessage( TerrainMessage.constructupdateFluidDataMessage(worldPosition.x, worldPosition.y, worldPosition.z, TerrainProtocol.constructFluidByteBuffer(chunk).array()) ); Globals.profiler.endCpuSample(); } @Override public void save(String saveName) { for(ServerDataCell cell : this.groundDataCells.values()){ Long key = this.getServerDataCellKey(this.getCellWorldPosition(cell)); //offload all entities in cell to chunk file serverContentManager.saveContentToDisk(key, cell.getScene().getEntityList()); } } @Override public Vector3d guaranteePositionIsInBounds(Vector3d positionToTest) { Vector3d returnPos = new Vector3d(positionToTest); if(positionToTest.x < 0){ returnPos.x = 0; } if(positionToTest.x >= parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete())){ returnPos.x = parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete()) - 1; } if(positionToTest.y < 0){ returnPos.y = 0; } if(positionToTest.y >= parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete())){ returnPos.y = parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete()) - 1; } if(positionToTest.z < 0){ returnPos.z = 0; } if(positionToTest.z >= parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete())){ returnPos.z = parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete()) - 1; } return returnPos; } /** * Gets the set of loaded cells * @return The set of loaded cells */ public Collection getLoadedCells(){ return Collections.unmodifiableCollection(this.groundDataCells.values()); } }