blob: 3c628f304130874206c43c53196120748b99124d [file] [log] [blame] [raw]
package net.glowstone;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.PluginCommandYamlParser;
import org.bukkit.command.SimpleCommandMap;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.World.Environment;
import org.bukkit.command.CommandException;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.Player;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.Recipe;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.SimplePluginManager;
import org.bukkit.plugin.SimpleServicesManager;
import org.bukkit.plugin.java.JavaPluginLoader;
import net.glowstone.io.McRegionChunkIoService;
import net.glowstone.net.MinecraftPipelineFactory;
import net.glowstone.net.Session;
import net.glowstone.net.SessionRegistry;
import net.glowstone.scheduler.GlowScheduler;
import net.glowstone.util.PlayerListFile;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
/**
* The core class of the Glowstone server.
* @author Graham Edgecombe
*/
public final class GlowServer implements Server {
/**
* The logger for this class.
*/
public static final Logger logger = Logger.getLogger(GlowServer.class.getName());
/**
* The configuration the server uses.
*/
private static final Properties properties = new Properties();
/**
* Creates a new server on TCP port 25565 and starts listening for
* connections.
* @param args The command-line arguments.
*/
public static void main(String[] args) {
try {
File props = new File("server.properties");
if (props.exists()) {
properties.load(new FileInputStream(props));
} else {
properties.setProperty("server-port", "25565");
properties.save(new FileOutputStream(props), "Glowstone server properties");
}
int port = Integer.valueOf(properties.getProperty("server-port", "25565"));
GlowServer server = new GlowServer();
server.bind(new InetSocketAddress(port));
server.start();
} catch (Throwable t) {
logger.log(Level.SEVERE, "Error during server startup.", t);
}
}
/**
* The {@link ServerBootstrap} used to initialize Netty.
*/
private final ServerBootstrap bootstrap = new ServerBootstrap();
/**
* A group containing all of the channels.
*/
private final ChannelGroup group = new DefaultChannelGroup();
/**
* The network executor service - Netty dispatches events to this thread
* pool.
*/
private final ExecutorService executor = Executors.newCachedThreadPool();
/**
* A list of all the active {@link Session}s.
*/
private final SessionRegistry sessions = new SessionRegistry();
/**
* The list of OPs on the server.
*/
private final PlayerListFile opsList = new PlayerListFile("ops.txt");
/**
* The plugin manager of this server.
*/
private final SimplePluginManager pluginManager = new SimplePluginManager(this);
/**
* The services manager of this server.
*/
private final SimpleServicesManager servicesManager = new SimpleServicesManager();
/**
* The command map of this server.
*/
private final SimpleCommandMap commandMap = new SimpleCommandMap(this);
/**
* The task scheduler used by this server.
*/
private final GlowScheduler scheduler = new GlowScheduler(this);
/**
* The world this server is managing.
*/
private final ArrayList<GlowWorld> worlds = new ArrayList<GlowWorld>();
/**
* Creates a new server.
*/
public GlowServer() {
logger.info("Starting Glowstone...");
init();
}
/**
* Initializes the channel and pipeline factories.
*/
private void init() {
Bukkit.setServer(this);
createWorld(properties.getProperty("world-name", "world"), Environment.NORMAL);
ChannelFactory factory = new NioServerSocketChannelFactory(executor, executor);
bootstrap.setFactory(factory);
ChannelPipelineFactory pipelineFactory = new MinecraftPipelineFactory(this);
bootstrap.setPipelineFactory(pipelineFactory);
}
/**
* Binds this server to the specified address.
* @param address The addresss.
*/
public void bind(SocketAddress address) {
logger.log(Level.INFO, "Binding to address: {0}...", address);
group.add(bootstrap.bind(address));
}
/**
* Starts this server.
*/
public void start() {
reload();
logger.info("Ready for connections.");
}
/**
* Reloads the server, refreshing settings and plugin information
*/
public void reload() {
try {
properties.load(new FileInputStream(new File("server.properties")));
opsList.load();
File folder = new File(properties.getProperty("plugin-folder", "plugins"));
folder.mkdirs();
// clear and reregister our commands
commandMap.clearCommands();
commandMap.register("glowstone", new net.glowstone.command.OpCommand(this));
commandMap.register("glowstone", new net.glowstone.command.DeopCommand(this));
commandMap.register("glowstone", new net.glowstone.command.ListCommand(this));
// clear plugins and prepare to load
pluginManager.clearPlugins();
pluginManager.registerInterface(JavaPluginLoader.class);
Plugin[] plugins = pluginManager.loadPlugins(folder);
// call onLoad methods
for (Plugin plugin : plugins) {
try {
plugin.onLoad();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error loading {0}: {1}", new Object[]{plugin.getDescription().getName(), ex.getMessage()});
ex.printStackTrace();
}
}
// register plugin commands
for (Plugin plugin : plugins) {
List<Command> commands = PluginCommandYamlParser.parse(plugin);
commandMap.registerAll(plugin.getDescription().getName(), commands);
}
// enable plugins
for (Plugin plugin : plugins) {
try {
pluginManager.enablePlugin(plugin);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error enabling {0}: {1}", new Object[]{plugin.getDescription().getName(), ex.getMessage()});
ex.printStackTrace();
}
}
}
catch (Exception ex) {
logger.log(Level.SEVERE, "Uncaught error while reloading: {0}", ex.getMessage());
ex.printStackTrace();
}
}
/**
* Gets the channel group.
* @return The {@link ChannelGroup}.
*/
public ChannelGroup getChannelGroup() {
return group;
}
/**
* Gets the session registry.
* @return The {@link SessionRegistry}.
*/
public SessionRegistry getSessionRegistry() {
return sessions;
}
/**
* Returns the list of OPs on this server.
*/
public PlayerListFile getOpsList() {
return opsList;
}
/**
* Gets the world by the given name.
* @param name The name of the world to look up.
* @return The {@link GlowWorld} this server manages.
*/
public GlowWorld getWorld(String name) {
for (GlowWorld world : worlds) {
if (world.getName().equalsIgnoreCase(name))
return world;
}
return null;
}
/**
* Gets the list of worlds currently loaded.
* @return An ArrayList containing all loaded worlds.
*/
public List<World> getWorlds() {
ArrayList<World> result = new ArrayList<World>();
for (GlowWorld world : worlds)
result.add(world);
return result;
}
/**
* Gets the name of this server implementation
*
* @return "Glowstone"
*/
public String getName() {
return "Glowstone";
}
/**
* Gets the version string of this server implementation.
*
* @return version of this server implementation
*/
public String getVersion() {
return "git-Glowstone-unknown";
}
/**
* Gets a list of all currently logged in players
*
* @return An array of Players that are currently online
*/
public Player[] getOnlinePlayers() {
ArrayList<Player> result = new ArrayList<Player>();
for (World world : getWorlds()) {
for (Player player : world.getPlayers())
result.add(player);
}
return result.toArray(new Player[] {});
}
/**
* Get the maximum amount of players which can login to this server
*
* @return The amount of players this server allows
*/
public int getMaxPlayers() {
return Integer.valueOf(properties.getProperty("max-players", "0"));
}
/**
* Gets the port the server listens on.
* @return The port number the server is listening on.
*/
public int getPort() {
return Integer.valueOf(properties.getProperty("server-port", "25565"));
}
/**
* Get the IP that this server is bound to or empty string if not specified
*
* @return The IP string that this server is bound to, otherwise empty string
*/
public String getIp() {
return "";
}
/**
* Get the name of this server
*
* @return The name of this server
*/
public String getServerName() {
return "Glowstone Server";
}
/**
* Get an ID of this server. The ID is a simple generally alphanumeric
* ID that can be used for uniquely identifying this server.
*
* @return The ID of this server
*/
public String getServerId() {
return Integer.toHexString(getServerName().hashCode());
}
/**
* Broadcast a message to all players.
*
* @param message the message
* @return the number of players
*/
public int broadcastMessage(String message) {
for (Player player : getOnlinePlayers()) {
player.sendMessage(message);
}
return getOnlinePlayers().length;
}
/**
* Gets the name of the update folder. The update folder is used to safely update
* plugins at the right moment on a plugin load.
*
* @return The name of the update folder
*/
public String getUpdateFolder() {
return properties.getProperty("update-folder", "update");
}
/**
* Gets a player object by the given username
*
* This method may not return objects for offline players
*
* @param name Name to look up
* @return Player if it was found, otherwise null
*/
public Player getPlayer(String name) {
for (Player player : getOnlinePlayers()) {
if (player.getName().equalsIgnoreCase(name))
return player;
}
return null;
}
/**
* Attempts to match any players with the given name, and returns a list
* of all possibly matches
*
* This list is not sorted in any particular order. If an exact match is found,
* the returned list will only contain a single result.
*
* @param name Name to match
* @return List of all possible players
*/
public List<Player> matchPlayer(String name) {
ArrayList<Player> result = new ArrayList<Player>();
for (Player player : getOnlinePlayers()) {
if (player.getName().startsWith(name)) {
result.add(player);
}
}
return result;
}
/**
* Gets the PluginManager for interfacing with plugins
*
* @return PluginManager for this GlowServer instance
*/
public SimplePluginManager getPluginManager() {
return pluginManager;
}
/**
* Gets the Scheduler for managing scheduled events
*
* @return Scheduler for this GlowServer instance
*/
public GlowScheduler getScheduler() {
return scheduler;
}
/**
* Gets a services manager
*
* @return Services manager
*/
public SimpleServicesManager getServicesManager() {
return servicesManager;
}
/**
* Gets the default ChunkGenerator for the given environment.
* @return The ChunkGenerator.
*/
private ChunkGenerator getGenerator(Environment environment) {
if (environment == Environment.NETHER) {
return new net.glowstone.generator.FlatNetherGenerator();
} else if (environment == Environment.SKYLANDS) {
// TODO: add skylands generator
return new net.glowstone.generator.FlatgrassGenerator();
} else {
return new net.glowstone.generator.FlatForestGenerator();
}
}
/**
* Creates or loads a world with the given name.
* If the world is already loaded, it will just return the equivalent of
* getWorld(name)
*
* @param name Name of the world to load
* @param environment Environment type of the world
* @return Newly created or loaded World
*/
public GlowWorld createWorld(String name, Environment environment) {
return createWorld(name, environment, new Random().nextLong(), getGenerator(environment));
}
/**
* Creates or loads a world with the given name.
* If the world is already loaded, it will just return the equivalent of
* getWorld(name)
*
* @param name Name of the world to load
* @param environment Environment type of the world
* @param seed Seed value to create the world with
* @return Newly created or loaded World
*/
public GlowWorld createWorld(String name, Environment environment, long seed) {
return createWorld(name, environment, seed, getGenerator(environment));
}
/**
* Creates or loads a world with the given name.
* If the world is already loaded, it will just return the equivalent of
* getWorld(name)
*
* @param name Name of the world to load
* @param environment Environment type of the world
* @param generator ChunkGenerator to use in the construction of the new world
* @return Newly created or loaded World
*/
public GlowWorld createWorld(String name, Environment environment, ChunkGenerator generator) {
return createWorld(name, environment, new Random().nextLong(), generator);
}
/**
* Creates or loads a world with the given name.
* If the world is already loaded, it will just return the equivalent of
* getWorld(name)
*
* @param name Name of the world to load
* @param environment Environment type of the world
* @param seed Seed value to create the world with
* @param generator ChunkGenerator to use in the construction of the new world
* @return Newly created or loaded World
*/
public GlowWorld createWorld(String name, Environment environment, long seed, ChunkGenerator generator) {
if (getWorld(name) != null) return getWorld(name);
GlowWorld world = new GlowWorld(name, environment, seed, new McRegionChunkIoService(new File(name)), generator);
if (world != null) worlds.add(world);
return world;
}
/**
* Unloads a world with the given name.
*
* @param name Name of the world to unload
* @param save Whether to save the chunks before unloading.
* @return Whether the action was Successful
*/
public boolean unloadWorld(String name, boolean save) {
if (getWorld(name) == null) return false;
return unloadWorld(getWorld(name), save);
}
/**
* Unloads the given world.
*
* @param world The world to unload
* @param save Whether to save the chunks before unloading.
* @return Whether the action was Successful
*/
public boolean unloadWorld(World world, boolean save) {
if (save) {
world.save();
}
if (!(world instanceof GlowWorld)) {
return false;
}
if (worlds.contains((GlowWorld) world)) {
worlds.remove((GlowWorld) world);
return true;
}
return false;
}
/**
* Returns the primary logger associated with this server instance
*
* @return Logger associated with this server
*/
public Logger getLogger() {
return logger;
}
/**
* Gets a {@link PluginCommand} with the given name or alias
*
* @param name Name of the command to retrieve
* @return PluginCommand if found, otherwise null
*/
public PluginCommand getPluginCommand(String name) {
Command command = commandMap.getCommand(name);
if (command instanceof PluginCommand) {
return (PluginCommand) command;
} else {
return null;
}
}
/**
* Writes loaded players to disk
*/
public void savePlayers() {
for (Player player : getOnlinePlayers())
player.saveData();
}
/**
* Dispatches a command on the server, and executes it if found.
*
* @param cmdLine command + arguments. Example: "test abc 123"
* @return targetFound returns false if no target is found.
* @throws CommandException Thrown when the executor for the given command fails with an unhandled exception
*/
public boolean dispatchCommand(CommandSender sender, String commandLine) {
try {
String[] args = commandLine.split(" +");
String commandName = args[0];
String[] newargs = new String[args.length - 1];
for (int i = 1; i < args.length; ++i) {
newargs[i - 1] = args[i];
}
Command command = commandMap.getCommand(commandName);
if (command == null)
return false;
return command.execute(sender, commandName, newargs);
}
catch (CommandException ex) {
throw ex;
}
catch (Exception ex) {
throw new CommandException("Unhandled exception executing command", ex);
}
}
/**
* Populates a given {@link ServerConfig} with values attributes to this server
*
* @param config ServerConfig to populate
*/
public void configureDbConfig(com.avaje.ebean.config.ServerConfig config) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Adds a recipe to the crafting manager.
* @param recipe The recipe to add.
* @return True to indicate that the recipe was added.
*/
public boolean addRecipe(Recipe recipe) {
throw new UnsupportedOperationException("Not supported yet.");
}
}