package complexion.server; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import com.esotericsoftware.kryonet.Connection; import com.esotericsoftware.minlog.Log; import complexion.common.Config; import complexion.common.Console; import complexion.common.Directions; import complexion.common.Utils; /** * Server class containing all global definitions for a specific server instance. */ public class Server { // Fixed-size array of the map z-levels // Probably should use lists instead, since we might want to resize map size // at runtime. private Zlevel[] map; /// The current iteration of the world ticker. private int tick = 0; /// Height of the map in z-levels private static int height = 1; /// Permanently passing around server instances is very bothersome. /// /// Since in one application, we will have only one server, use a /// global instance instead. public static Server current; /** Start the server program. */ public static void main(String[] args) { current = new Server(); // See https://code.google.com/p/minlog/#Log_level //Log.set(Log.LEVEL_DEBUG); // Generate all the Zlevels for the map and store them in this.map current.map = new Zlevel[height]; for(int z = 0; z<height; z++) { current.map[z] = new Zlevel(z); } for(int x=0; x<10; x++) for(int y=0;y<10;y++) { // Add a sample object Tile test_tile = new Tile(current,x,y,0); test_tile.setSprite("floors.dmi"); test_tile.setSpriteState("floor"); test_tile.setDirection(Directions.SOUTH); test_tile.setLayer(0); } Movable test_mover = new Movable(); test_mover.setSprite("mask.dmi"); test_mover.setSpriteState("muzzle"); test_mover.setLayer(10); test_mover.Move(current.getTile(1, 1, 0)); // Start server networking try { ServerListener master = new ServerListener(current,1024); } catch (IOException e) { e.printStackTrace(); return; } // Create a window so that the application can be closed // This is for debugging only Console console = new Console(); console.run(); // Run the world in a loop while(true) { current.nextTick(); if(current.getTick() % 50 < 25) { test_mover.setSpriteState("owl"); test_mover.Move(current.getTile(2, 1, 0)); } else { //test_mover.setSpriteState("muzzle"); test_mover.Move(current.getTile(3, 1, 0)); } Utils.sleep(Config.tickLag); } } /** * Get the tile at the specified map position(NOT pixel coordinates) * * Returns null if position exceeds map boundaries. */ public Tile getTile(int pos_x, int pos_y, int pos_z) { // Make sure the position exists if(pos_z > map.length || pos_z < 0) { return null; } return map[pos_z].getTile(pos_x, pos_y); } /** Overwrite the tile at the specified tile position with the given tile. * @param x,y,z Position on the map where the tile will be inserted. * @param tile Tile to be inserted into the map. */ public void setTile(int x, int y, int z, Tile tile) { // Make sure the position exists if(z > map.length || z < 0) { return; } map[z].setTile(x, y, tile); } /** * Handle a login request from a client. This will later utilize some kind * of account system, which may work similarly to the BYOND one. * * @return true if login info valid, false otherwise */ public boolean handleLoginRequest(String account_name, String password) { // accept ALL the login requests return true; } /** * @return The current iteration of the world ticker */ public int getTick() { return this.tick; } /** * Run the next tick iteration. * * This function is responsible for gathering data about all changes to objects * and synchronizing these changes with the clients. */ public void nextTick() { this.tick++; // First give all clients a chance to process custom logic for(Map.Entry<Connection, Client> entry : clientsByIP.entrySet()) { Client client = entry.getValue(); // Check if the client has a connection; If not, it's not ready. if(client.connection == null) continue; client.Tick(); } // Then update all clients for(Map.Entry<Connection, Client> entry : clientsByIP.entrySet()) { Client client = entry.getValue(); // Check if the client has a connection; If not, it's not ready. if(client.connection == null) continue; client.synchronizeAtoms(); } // Then clear atom updates for all clients for(Map.Entry<Connection, Client> entry : clientsByIP.entrySet()) { Client client = entry.getValue(); // Check if the client has a connection; If not, it's not ready. if(client.connection == null) continue; // This sets atom.outdated = 0 for all atoms in the client's view client.clearAtomsInView(); } } /** * Get all chunks that overlap the given view-range. * @return A list that contains all chunks which overlap the range defined by the parameters. */ public List<Chunk> getOverlappingChunks(int start_x, int end_x, int start_y, int end_y, int start_z, int end_z) { List<Chunk> rval = new ArrayList<Chunk>(); // convert the tile positions on the x and y axis to chunk indices int chunk_start_x = start_x / Chunk.width; int chunk_end_x = end_x / Chunk.width; int chunk_start_y = start_y / Chunk.height; int chunk_end_y = end_y / Chunk.height; // Make sure everything is within bounds if(start_z < 0) start_z = 0; if(end_z > map.length) end_z = map.length; // Now populate the return list by iterating over all the // relevant chunk indices. for(int z = start_z; z<=end_z; z++) { Zlevel zl = this.map[z]; for(int x = chunk_start_x; x<=chunk_end_x; x++) for(int y = chunk_start_y; y<=chunk_end_y; y++) { // Try to extract the chunk and add it if it exists Chunk chunk = zl.getChunk(x, y); if(chunk != null) rval.add(chunk); } } return rval; } // TODO: Create a getTileRange function which will get a list of tiles // within a specific range /// Maps TCP connections to clients connected via that address /// Accessed by networking threads, so it has to be concurrent. ConcurrentMap<Connection,Client> clientsByIP = new ConcurrentHashMap<Connection,Client>(); }