package server;
import java.io.IOException;
import java.io.InputStream;
import java.net.BindException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.TreeMap;
import javax.crypto.Cipher;
import server.login.LoginHandler;
import com.aelfengard.i3.ErrorCallback;
import com.aelfengard.i3.I3Client;
import com.aelfengard.i3.I3EventListener;
import com.aelfengard.i3.LPCMixed;
import com.aelfengard.i3.MudInfo;
import com.aelfengard.i3.packet.EmoteToPacket;
import com.aelfengard.i3.packet.ErrorPacket;
import com.aelfengard.i3.packet.TellPacket;
import common.ui.GameClient;
public class GameServer {
private static final SecurityManager SECURITY_MANAGER = System.getSecurityManager();
private static final Runnable[][] EMPTY_RUNNABLE_ARRAY = {};
private static TreeMap<Long,Runnable> SCHEDULED_TASKS = new TreeMap<Long,Runnable>();
private static List<Object> EXEC_QUEUE = new ArrayList<Object>();
private static Selector selector;
private static String gameMode;
private static String myHostname;
private static int myPort;
private static String rcSubdir;
public static final I3Client i3client = new I3Client();
public static final Random RANDOM = new Random();
public static void main(String... args) {
if (args.length == 1 && "-mkdevdb".equals(args[0])) {
PersistenceManager.MAKING_DEV_DB = true;
PersistenceManager.doLoad();
PersistenceManager.doInlineSave("aelfengard-dev.db");
System.exit(0);
}
else if (args.length < 3 || args.length > 4) {
usage();
}
System.setIn(new InputStream() {
@Override
public int read() throws IOException {
return -1; }
});
gameMode = System.getProperty("aelfengard.gameMode");
myHostname = args[0];
myPort = Integer.parseInt(args[1]);
rcSubdir = args[2];
boolean launchClient = false;
if (args.length > 3) {
if (args[3].equals("-gui")) {
launchClient = true;
}
else {
usage();
}
}
try {
System.setOut(new TimeStampPrintStream(System.out));
System.setErr(new TimeStampPrintStream(System.err));
System.out.println("Aelfengard server starting up...");
run(launchClient);
} catch (Throwable t) {
t.printStackTrace();
} finally {
System.exit(-1);
}
}
private static void run(boolean launchClient) throws Exception {
selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
try {
serverChannel.socket().bind(new InetSocketAddress(myPort));
}
catch (BindException ex) {
if (launchClient) {
GameClient.setExtraModeInfo("AUX");
GameClient.main("localhost", String.valueOf(myPort));
}
throw ex;
}
if (launchClient) {
GameClient.setExtraModeInfo("MAIN");
new Thread() {
@Override
public void run() {
GameClient.main("localhost", String.valueOf(myPort));
}
}.start();
}
PersistenceManager.doLoad();
createBasicObjects();
schedule(30000, new Runnable() {
public void run() {
PersistenceManager.doThreadedSave(null);
schedule(30000, this); }
});
if (gameMode == null) {
i3client.setMudName("Aelfengard");
i3client.setOpenStatus(MudInfo.OPEN_STATUS_MUDLIB_DEVELOPMENT + " - Go to http://couchpotato.net/gamerc/aelfengard.jnlp to connect");
i3client.setPlayerPort(23);
}
else {
i3client.setMudName("Aelfengard-" + gameMode);
i3client.setOpenStatus(MudInfo.OPEN_STATUS_RESTRICTED_ACCESS);
}
i3client.setAdminEmail("green@couchpotato.net");
i3client.setMudLib("Custom");
i3client.setBaseMudLib("Custom");
i3client.setDriver("Custom");
i3client.setMudType("Graphics & Text");
i3client.addEventListener(new MyI3EventListener());
if (System.getProperty("aelfengard.i3") != null) {
i3client.autoconnect();
}
while (true) {
long timeout = 0; while (true) {
if (SCHEDULED_TASKS.isEmpty()) {
break;
}
Long firstKey = SCHEDULED_TASKS.firstKey();
long time = firstKey;
long now = System.currentTimeMillis();
if (time > now) {
timeout = time - now;
break;
}
Runnable r = SCHEDULED_TASKS.remove(firstKey);
r.run();
}
int count = selector.selectNow();
if (count == 0) {
Object[][] todo = EMPTY_RUNNABLE_ARRAY;
synchronized (GameServer.class) {
if (!EXEC_QUEUE.isEmpty()) {
todo = new Object[EXEC_QUEUE.size()][];
EXEC_QUEUE.toArray(todo);
EXEC_QUEUE = new ArrayList<Object>();
}
}
if (todo != EMPTY_RUNNABLE_ARRAY) {
for (int i = 0; i < todo.length; i++) {
Object ret = null;
Exception ex = null;
if (todo[i][0] instanceof XRunnable) {
ret = ((XRunnable) todo[i][0]).run();
} else {
((Runnable) todo[i][0]).run();
}
synchronized (todo[i]) {
todo[i][0] = null; todo[i][1] = ret;
todo[i][2] = ex;
todo[i].notifyAll();
}
}
continue;
}
count = selector.select(timeout);
if (count == 0) {
continue;
}
}
for (final SelectionKey key : selector.selectedKeys()) {
if (key.isWritable()) {
Writable writable = (Writable) key.attachment();
writable.doWrite();
} else if (key.isReadable()) {
Readable readable = (Readable) key.attachment();
readable.doRead();
} else if (key.isAcceptable()) {
SocketChannel clientChannel = serverChannel.accept();
InetAddress addr = ((InetSocketAddress) clientChannel
.socket().getRemoteSocketAddress()).getAddress();
String ip = addr.getHostAddress();
System.out.println("Client connected from " + ip);
new LoginHandler(clientChannel).start();
}
}
selector.selectedKeys().clear();
}
}
public static void invokeLater(Runnable tmp) {
Object[] r = new Object[] { tmp, null, null };
synchronized (GameServer.class) {
EXEC_QUEUE.add(r);
}
selector.wakeup();
}
public static Object invokeAndWait(XRunnable tmp) throws Exception {
Object[] r = new Object[] { tmp, null, null };
synchronized (GameServer.class) {
EXEC_QUEUE.add(r);
}
selector.wakeup();
synchronized (r) {
while (r[0] != null) {
r.wait();
}
if (r[2] != null) {
throw (Exception) r[2];
}
return r[1];
}
}
public static void schedule(long offset, Runnable r) {
long time = System.currentTimeMillis() + offset;
Long otime;
while (true) {
otime = new Long(time);
if (SCHEDULED_TASKS.get(otime) == null) {
break; }
time++;
}
SCHEDULED_TASKS.put(otime, r);
}
public static void loggedIn(SocketChannel channel, Player player, Cipher encipher, Cipher decipher) {
try {
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, 0);
player.initClientState(key, encipher, decipher);
} catch (IOException ex) {
throw new FatalError(ex);
}
}
private static void createBasicObjects() {
Area mainArea = Area.findByName("main");
if (mainArea == null) {
mainArea = new Area("main");
Room defaultRoom = mainArea.getDefaultRoom();
defaultRoom.setName("the trash mound");
defaultRoom.setDescription(
"The acrid stench of the air around the enourmous pile " +
"of garbage is enough to pull all the insect life away " +
"from other parts of town. A small swarm of flies " +
"gathers around each of the many sources of rotted food.");
}
}
public static void sendCoderMessage(String msg) {
msg = "@10F@10D[@10FCODERS@10D] @10F" + msg;
for (Player player : Player.getOnlinePlayers()) {
if (player.getAdminFlags().contains(AdminFlag.CODER)) {
player.sendText(true, msg);
}
}
}
public static int sendAdminsMessage(Player player, String premsg, String postmsg) {
if (premsg == null) {
premsg = "";
}
if (postmsg == null) {
postmsg = "";
}
int count = 0;
Collection<Player> admins = new HashSet<Player>(Player.getOnlineAdmins());
for (Player target : admins) {
String nick;
if (player == null) {
nick = "";
}
else if (player == target) { nick = "You";
}
else {
nick = player.getName(target, false);
nick = Utils.capitalize(nick);
}
String text = "@10F@10D[@10Fadmins@10D] @10F" + premsg + nick + postmsg;
target.sendText(true, text);
if (!target.isAFK()) {
count++;
}
}
return count;
}
public static String getGameMode() {
return gameMode;
}
private static void usage() {
System.err.println("Usage: java -jar aelfengard.jar <my_hostname> <listen_port> <rc_subdir> [-gui]");
System.err.println(" or: java -jar aelfengard.jar -mkdevdb");
System.exit(-1);
}
public static String getMyHostname() {
return myHostname;
}
public static String getRcSubdir() {
return rcSubdir;
}
private static class MyI3EventListener implements I3EventListener {
public void whoReply(final LPCMixed targetUsername, final LPCMixed originatorMudName, final List<LPCMixed> whoInfo) {
invokeLater(new Runnable() {
public void run() {
Player player = Player.findByUsername(targetUsername.asString());
if (player != null) {
player.showWhoInfo(originatorMudName, whoInfo);
}
}
});
}
public void whoFailed(final LPCMixed targetUsername, final LPCMixed targetMudName, final LPCMixed errorMessage) {
invokeLater(new Runnable() {
public void run() {
Player player = Player.findByUsername(targetUsername.asString());
if (player != null) {
player.sendText(true, "I3 WHO listing of \"@10F" + targetMudName.asString() + "@ZZZ\" failed: " + Utils.prettyMessage(errorMessage.asString()));
}
}
});
}
public List<LPCMixed> whoRequest() {
final List<LPCMixed> list = new LinkedList<LPCMixed>();
try {
invokeAndWait(new XRunnable() {
public Object run() throws Exception {
int admins = 0;
int count = Player.getOnlinePlayers().size();
for (Player p : Player.getOnlineAdmins()) {
count--;
if (p.isAFK()) {
continue; }
List<LPCMixed> entry = new LinkedList<LPCMixed>();
entry.add(new LPCMixed(Utils.capitalize(p.getUsername())));
entry.add(new LPCMixed(-1));
entry.add(new LPCMixed(""));
list.add(new LPCMixed(entry));
admins++;
}
return null;
}
});
}
catch (Exception ex) {
ex.printStackTrace();
System.exit(-1);
}
return list;
}
public void tell(final TellPacket packet, final ErrorCallback callback) {
invokeLater(new Runnable() {
public void run() {
Player player = Player.findByUsername(packet.getTargetUsername().asString());
if (player == null || !player.hasI3()) {
callback.returnError("Unknown player");
}
else if (!player.isOnline()) {
callback.returnError("Player is offline");
}
else {
player.i3Tell(packet.getOrigVisName(), packet.getOriginatorMudName(), packet.getMessage());
}
}
});
}
public void tellFailed(final LPCMixed username, final LPCMixed targetMudName, final LPCMixed targetUsername, final LPCMixed errorMessage) {
invokeLater(new Runnable() {
public void run() {
Player player = Player.findByUsername(username.asString());
if (player != null && player.hasI3()) {
player.sendText(true, "I3 TELL to @10F" + targetUsername + "@@10F" + Utils.quoteIfSpace(targetMudName.asString()) + "@ZZZ failed: " + Utils.prettyMessage(errorMessage.asString()));
}
}
});
}
public void i3Error(final ErrorPacket packet) {
invokeLater(new Runnable() {
public void run() {
String username = packet.getTargetUsername().asString();
if (username == null) {
System.err.println("Got unhandled, untargetted error packet: " + packet);
return;
}
Player player = Player.findByUsername(username);
if (player != null && player.hasI3()) {
player.sendText(true, "I3 ERROR: [" + packet.getErrorCode() + "] " + packet.getErrorMessage());
}
}
});
}
public void emoteTo(final EmoteToPacket packet, final ErrorCallback callback) {
invokeLater(new Runnable() {
public void run() {
Player player = Player.findByUsername(packet.getTargetUsername().asString());
if (player == null || !player.hasI3()) {
callback.returnError("Unknown player");
}
else if (!player.isOnline()) {
callback.returnError("Player is offline");
}
else {
player.i3EmoteTo(packet.getOrigVisName(), packet.getOriginatorMudName(), packet.getMessage());
}
}
});
}
}
public static void internalOnly() {
if (SECURITY_MANAGER != null) {
SECURITY_MANAGER.checkPermission(GamePermission.SINGLETON);
}
}
}