package com.loohp.limbo; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import com.loohp.limbo.File.ServerProperties; import com.loohp.limbo.Location.Location; import com.loohp.limbo.Player.Player; import com.loohp.limbo.Server.ServerConnection; import com.loohp.limbo.Server.Packets.Packet; import com.loohp.limbo.Server.Packets.PacketIn; import com.loohp.limbo.Server.Packets.PacketOut; import com.loohp.limbo.Utils.ImageUtils; import com.loohp.limbo.Utils.NetworkUtils; import com.loohp.limbo.World.Schematic; import com.loohp.limbo.World.World; import net.querz.nbt.io.NBTUtil; import net.querz.nbt.tag.CompoundTag; public class Limbo { private static Limbo instance; public static void main(String args[]) throws IOException, ParseException, NumberFormatException, ClassNotFoundException { new Limbo(); } public static Limbo getInstance() { return instance; } //=========================== private ServerConnection server; private Console console; private List worlds = new ArrayList<>(); private Map playersByName = new HashMap<>(); private Map playersByUUID = new HashMap<>(); private ServerProperties properties; public AtomicInteger entityCount = new AtomicInteger(); @SuppressWarnings("unchecked") public Limbo() throws IOException, ParseException, NumberFormatException, ClassNotFoundException { instance = this; console = new Console(System.in, System.out); String spName = "server.properties"; File sp = new File(spName); if (!sp.exists()) { try (InputStream in = getClass().getClassLoader().getResourceAsStream(spName)) { Files.copy(in, sp.toPath()); } catch (IOException e) { e.printStackTrace(); } } properties = new ServerProperties(sp); if (!properties.isBungeecord()) { console.sendMessage("If you are using bungeecord, consider turning that on in the settings!"); } else { console.sendMessage("Starting Limbo server in bungeecord mode!"); } String mappingName = "mapping.json"; File mappingFile = new File(mappingName); if (!mappingFile.exists()) { try (InputStream in = getClass().getClassLoader().getResourceAsStream(mappingName)) { Files.copy(in, mappingFile.toPath()); } catch (IOException e) { e.printStackTrace(); } } console.sendMessage("Loading packet id mappings from mapping.json ..."); JSONObject json = (JSONObject) new JSONParser().parse(new FileReader(mappingFile)); String classPrefix = Packet.class.getName().substring(0, Packet.class.getName().lastIndexOf(".") + 1); int mappingsCount = 0; Map> HandshakeIn = new HashMap<>(); for (Object key : ((JSONObject) json.get("HandshakeIn")).keySet()) { int packetId = Integer.decode((String) key); HandshakeIn.put(packetId, (Class) Class.forName(classPrefix + (String) ((JSONObject) json.get("HandshakeIn")).get(key))); } Packet.setHandshakeIn(HandshakeIn); mappingsCount += HandshakeIn.size(); Map> StatusIn = new HashMap<>(); for (Object key : ((JSONObject) json.get("StatusIn")).keySet()) { int packetId = Integer.decode((String) key); StatusIn.put(packetId, (Class) Class.forName(classPrefix + (String) ((JSONObject) json.get("StatusIn")).get(key))); } Packet.setStatusIn(StatusIn); mappingsCount += StatusIn.size(); Map, Integer> StatusOut = new HashMap<>(); for (Object key : ((JSONObject) json.get("StatusOut")).keySet()) { Class packetClass = (Class) Class.forName(classPrefix + (String) key); StatusOut.put(packetClass, Integer.decode((String) ((JSONObject) json.get("StatusOut")).get(key))); } Packet.setStatusOut(StatusOut); mappingsCount += StatusOut.size(); Map> LoginIn = new HashMap<>(); for (Object key : ((JSONObject) json.get("LoginIn")).keySet()) { int packetId = Integer.decode((String) key); LoginIn.put(packetId, (Class) Class.forName(classPrefix + (String) ((JSONObject) json.get("LoginIn")).get(key))); } Packet.setLoginIn(LoginIn); mappingsCount += LoginIn.size(); Map, Integer> LoginOut = new HashMap<>(); for (Object key : ((JSONObject) json.get("LoginOut")).keySet()) { Class packetClass = (Class) Class.forName(classPrefix + (String) key); LoginOut.put(packetClass, Integer.decode((String) ((JSONObject) json.get("LoginOut")).get(key))); } Packet.setLoginOut(LoginOut); mappingsCount += LoginOut.size(); Map> PlayIn = new HashMap<>(); for (Object key : ((JSONObject) json.get("PlayIn")).keySet()) { int packetId = Integer.decode((String) key); PlayIn.put(packetId, (Class) Class.forName(classPrefix + (String) ((JSONObject) json.get("PlayIn")).get(key))); } Packet.setPlayIn(PlayIn); mappingsCount += PlayIn.size(); Map, Integer> PlayOut = new HashMap<>(); for (Object key : ((JSONObject) json.get("PlayOut")).keySet()) { Class packetClass = (Class) Class.forName(classPrefix + (String) key); PlayOut.put(packetClass, Integer.decode((String) ((JSONObject) json.get("PlayOut")).get(key))); } Packet.setPlayOut(PlayOut); mappingsCount += PlayOut.size(); console.sendMessage("Loaded all " + mappingsCount + " packet id mappings!"); worlds.add(loadDefaultWorld()); Location spawn = properties.getWorldSpawn(); properties.setWorldSpawn(new Location(getWorld(properties.getLevelName().getKey()), spawn.getX(), spawn.getY(), spawn.getZ(), spawn.getYaw(), spawn.getPitch())); if (!NetworkUtils.available(properties.getServerPort())) { console.sendMessage(""); console.sendMessage("*****FAILED TO BIND PORT [" + properties.getServerPort() + "]*****"); console.sendMessage("*****PORT ALREADY IN USE*****"); console.sendMessage("*****PERHAPS ANOTHER INSTANCE OF THE SERVER IS ALREADY RUNNING?*****"); console.sendMessage(""); System.exit(2); } server = new ServerConnection(properties.getServerIp(), properties.getServerPort()); console.run(); } private World loadDefaultWorld() throws IOException { console.sendMessage("Loading world " + properties.getLevelName() + " with the schematic file " + properties.getSchemFileName() + " ..."); File schem = new File(properties.getSchemFileName()); if (!schem.exists()) { console.sendMessage("Schemetic file " + properties.getSchemFileName() + " for world " + properties.getLevelName() + " not found!"); console.sendMessage("Server will exit!"); System.exit(1); return null; } World world = Schematic.toWorld(properties.getLevelName().getKey(), (CompoundTag) NBTUtil.read(schem).getTag()); console.sendMessage("Loaded world " + properties.getLevelName() + "!"); return world; } public ServerProperties getServerProperties() { return properties; } public ServerConnection getServerConnection() { return server; } public Console getConsole() { return console; } public Set getPlayers() { return new HashSet<>(playersByUUID.values()); } public Player getPlayer(String name) { return playersByName.get(name); } public Player getPlayer(UUID uuid) { return playersByUUID.get(uuid); } public void addPlayer(Player player) { playersByName.put(player.getName(), player); playersByUUID.put(player.getUUID(), player); } public void removePlayer(Player player) { playersByName.remove(player.getName()); playersByUUID.remove(player.getUUID()); } public List getWorlds() { return new ArrayList<>(worlds); } public World getWorld(String name) { for (World world : worlds) { if (world.getName().equalsIgnoreCase(name)) { return world; } } return null; } public String getServerListResponseJson() throws IOException { String base = ServerProperties.JSON_BASE_RESPONSE; base = base.replace("%VERSION%", properties.getVersionString()); base = base.replace("%PROTOCOL%", String.valueOf(properties.getProtocol())); base = base.replace("%MOTD%", properties.getMotdJson()); base = base.replace("%MAXPLAYERS%", String.valueOf(properties.getMaxPlayers())); base = base.replace("%ONLINECLIENTS%", String.valueOf(getPlayers().size())); if (properties.getFavicon().isPresent()) { String icon = "\"favicon\":\"data:image/png;base64," + ImageUtils.imgToBase64String(properties.getFavicon().get(), "png") + "\","; base = base.replace("%FAVICON%", icon); } else { base = base.replace("%FAVICON%", ""); } return base; } public void stopServer() { Limbo.getInstance().getConsole().sendMessage("Stopping Server..."); for (Player player : getPlayers()) { player.disconnect("Server closed"); } while (!getPlayers().isEmpty()) { try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } System.exit(0); } }