package robombs.game;
import java.util.*;
import robombs.clientserver.*;
import robombs.game.model.*;
import robombs.game.util.*;
/**
* This is this test game's server component. It's usually started from within a running client but can run as
* a stand-alone server as well.
*/
public class BlueThunderServer implements DataTransferListener, Runnable, ClientLoginListener, ClientLogoutListener {
private final static Object SYNC=new Object();
private boolean exit = false;
private SimpleServer serverImpl=null;
private ExtendedDataContainer serverState=null;
private ServerObjectManager serverObjMan=null;
private ServerEventManager eventMan=null;
private int port=0;
private Map<Integer, List<PlayerInfo>> client2Info=new HashMap<Integer, List<PlayerInfo>>();
private Map<Integer, NetState> client2State=new HashMap<Integer, NetState>();
private int lastLevelChksum=-9999;
private boolean playing=false;
private MapList mapList=null;
private boolean running=false;
@SuppressWarnings("unused")
private List<MapInfo> selectedMaps=null;
/**
* Starts a new server using the default tcp-port.
*/
public BlueThunderServer() {
this(SimpleServer.DEFAULT_PORT);
}
/**
* Starts a new server using a specified tcp-port.
* @param port int the port
*/
public BlueThunderServer(int port) {
this.port=port;
mapList=new MapList();
NetLogger.log("Server: Magic number is "+mapList.getCheckSum());
}
/**
* Run the server as stand-alone.
* @param args String[]
* @throws Exception
*/
public static void main(String[] args) throws Exception {
BlueThunderServer str = new BlueThunderServer();
str.run();
}
public void setTimeOut(int t) {
serverImpl.setTimeOut(t);
}
public void reset() {
serverObjMan.reset();
}
public void setSelectedMaps(List<MapInfo> lst) {
selectedMaps=new ArrayList<MapInfo>(lst);
}
public int getClientCount() {
return client2Info.size();
//return serverImpl.getClientCount();
}
public void setGameState(boolean gameRunning) {
playing=gameRunning;
}
public List<PlayerInfo> getPlayers() {
List<PlayerInfo> res=new ArrayList<PlayerInfo>();
for (List<PlayerInfo> lst:client2Info.values()) {
res.addAll(lst);
}
Collections.sort(res);
return res;
}
public boolean getGameState() {
return playing;
}
public int getStateCount(int state) {
int cnt=0;
for (NetState ns:client2State.values()) {
if (ns.getState()==state) {
cnt++;
}
}
return cnt;
}
public List<PlayerInfo> getWithoutState(int state) {
for (Integer i:client2State.keySet()) {
NetState ns=client2State.get(i);
if (ns.getState()!=state) {
return getPlayerInfo(i);
}
}
return null;
}
public void logout(int cid) {
serverImpl.logout(cid);
}
public DataContainer loggedOut(ClientInfo ci) {
InfoDataContainer dc=new InfoDataContainer();
Integer key=Integer.valueOf(ci.getID());
List<PlayerInfo> pis= client2Info.get(key);
if (pis!=null) {
for (PlayerInfo pi:pis) {
if (pi!=null) {
InfoLine il=new InfoLine(InfoLine.PLAYER_REMOVED, 0, String.valueOf(ci.getID()), pi.getName());
dc.add(il);
il=new InfoLine(InfoLine.SYSTEM_OUT, 0, "msg", "Player '"+pi.getName()+"' has left the game!");
dc.add(il);
TeamAssigner.removeTeamAssigment(ci.getID(), pi.getObjectID());
} else {
// This may happen, if the game is already running. Do we have to do something here?
}
}
}
serverObjMan.remove(ci);
client2Info.remove(key);
client2State.remove(key);
addScores(dc);
return dc;
}
public void setStateForAll(int state) {
for (NetState ns:client2State.values()) {
ns.setState(state);
}
}
public void setState(int id, int state) {
NetState ns=client2State.get(Integer.valueOf(id));
if (ns!=null) {
ns.setState(state);
} else {
throw new RuntimeException("Can't set the state for an unknown client: "+id+"!");
}
}
public int getPort() {
return serverImpl.getPort();
}
public DataContainer loggedIn(ClientInfo ci, DataContainer dc) {
if (!getGameState()) {
String playerName="player";
int chkSum=-1;
int botFlag=0;
int objID=0;
if (dc!=null) {
String tmp=dc.getNextString();
if (tmp!=null && tmp.length()>0) {
playerName = tmp;
}
chkSum=dc.getNextInt();
botFlag=dc.getNextInt();
objID=dc.getNextInt();
}
if (chkSum!=mapList.getCheckSum()) {
// Levels differ!
EventDataContainer edc=new EventDataContainer();
Event e=new Event(Event.LOGIN_REJECTED, -98, -98, ci.getID());
e.setSourceClientID(ci.getID());
edc.add(e);
Integer key=Integer.valueOf(ci.getID());
NetState ns=new NetState();
ns.setState(NetState.STATE_NOT_CONNECTED);
client2State.put(key, ns);
NetLogger.log("Client "+ci.getID()+" rejected. Levels are different ("+mapList.getCheckSum()+"!="+chkSum+")!");
return edc;
} else {
Integer key=Integer.valueOf(ci.getID());
NetState ns=new NetState();
ns.setState(NetState.STATE_CONNECTED);
client2State.put(key, ns);
if (botFlag==0) {
addPlayer(playerName, ci.getID(), objID, false);
}
String txt="'"+playerName+"' has logged in!";
NetLogger.log(txt);
InfoDataContainer ic=new InfoDataContainer();
InfoLine il=new InfoLine(InfoLine.SYSTEM_OUT, 0, "msg", txt);
ic.add(il);
return ic;
}
} else {
EventDataContainer edc=new EventDataContainer();
Event e=new Event(Event.LOGIN_REJECTED, -99, -99, ci.getID());
e.setSourceClientID(ci.getID());
edc.add(e);
Integer key=Integer.valueOf(ci.getID());
NetState ns=new NetState();
ns.setState(NetState.STATE_NOT_CONNECTED);
client2State.put(key, ns);
NetLogger.log("Client "+ci.getID()+" rejected. The game is already running!");
return edc;
}
}
public void sendScores() {
InfoDataContainer ic=new InfoDataContainer();
addScores(ic);
broadcast(ic);
}
public void removePlayer(PlayerInfo pi) {
List<PlayerInfo> lst=client2Info.get(pi.getClientID());
for (Iterator<PlayerInfo> itty=lst.iterator(); itty.hasNext();) {
PlayerInfo pit=itty.next();
if (pi.getObjectID()==pit.getObjectID()) {
itty.remove();
break;
}
}
String txt="'"+pi.getName()+"' has left the game!";
NetLogger.log(txt);
InfoDataContainer ic=new InfoDataContainer();
InfoLine il=new InfoLine(InfoLine.SYSTEM_OUT, 0, "msg", txt);
ic.add(il);
addScores(ic);
broadcast(ic);
}
public synchronized void addPlayer(String name, int cid, int oid, boolean isBot) {
PlayerInfo pi=new PlayerInfo(name, cid, oid);
addToPlayerInfoList(Integer.valueOf(cid), pi);
pi.setBot(isBot);
if (isBot) {
pi.ready(true);
}
String txt="'"+name+"' has joined the game!";
NetLogger.log(txt);
InfoDataContainer ic=new InfoDataContainer();
InfoLine il=new InfoLine(InfoLine.PLAYER_ADDED, 0, "name", name);
ic.add(il);
il=new InfoLine(InfoLine.SYSTEM_OUT, 0, "msg", txt);
ic.add(il);
addScores(ic);
broadcast(ic);
}
public boolean checkLevel(DataContainer dc) {
int chk=dc.getNextInt();
// very simple way to prove that the level is the same on all clients.
// the first one with a different level will be rejected!
if (lastLevelChksum!=-9999 && chk!=lastLevelChksum) {
return false;
}
lastLevelChksum=chk;
return true;
}
public void run() {
try {
initServer();
runServer();
} catch(Exception e) {
throw new RuntimeException(e);
}
}
/**
* Stops the server.
*/
public void stop() {
exit=true;
running=false;
serverImpl.shutDown();
}
public void dataReceivedEnd() {}
/**
* Gets the players' info to a client's ID.
* @param clientID int the ID
* @return PlayerInfo the player info
*/
public synchronized List<PlayerInfo> getPlayerInfo(int clientID) {
return client2Info.get(Integer.valueOf(clientID));
}
public PlayerInfo getPlayerInfo(int clientID, int objectID) {
List<PlayerInfo> pis=getPlayerInfo(clientID);
if (pis!=null) {
for (PlayerInfo pi:pis) {
if (pi.getObjectID()==objectID) {
return pi;
}
}
}
NetLogger.log("Server: Unable to find PlayerInfo for "+clientID+"/"+objectID+"!?");
return null;
}
/**
* Broadcasts data to all clients.
* @param dc DataContainer the container with the data to broadcast
*/
public void broadcast(DataContainer dc) {
serverImpl.broadcast(dc);
}
public void broadcastToOthers(DataContainer dc, int clientID) {
serverImpl.broadcastToOthers(dc, clientID);
}
public void sendToSingleClient(DataContainer dc, int clientID) {
serverImpl.sendToSingleClient(dc, clientID);
}
/**
* Removes an object from the server
* @param loID int
* @param clientID int
* @param ci ClientInfo
*/
public void remove(int loID, int clientID, ClientInfo ci) {
synchronized(SYNC) {
LocalObject lo=serverObjMan.getLocalObjectToIDs(loID, clientID);
serverObjMan.removeObject(lo, ci);
}
}
public DataContainer[] dataReceived(DataContainer c, int type) {
try {
if (c != null) {
synchronized(SYNC) {
ExtendedDataContainer ec = new ExtendedDataContainer(c);
if (type==MessageTypes.OBJ_TRANSFER) {
while (ec.hasData()) {
serverObjMan.setOrUpdate(ec);
}
if (!Globals.activeServer) {
return new DataContainer[] {(DataContainer) serverState.clone()};
} else {
return new DataContainer[] {createServerState(ec.getClientInfo())};
}
}
if (type==MessageTypes.EVENT) {
EventDataContainer edc=new EventDataContainer(ec);
List<DataContainer> resp=new ArrayList<DataContainer>();
ClientInfo ci=edc.getClientInfo();
while (edc.hasData()) {
DataContainer[] res=eventMan.manageEvent(edc.getEvent(), serverObjMan, this, ci);
if (res!=null) {
resp.addAll(Arrays.asList(res));
}
}
if (resp.size()==0) {
return null;
}
DataContainer[] res=new DataContainer[resp.size()];
for (int i=0; i<res.length; i++) {
res[i]=(DataContainer) resp.get(i);
}
return res;
}
}
}
} catch(Exception e) {
throw new RuntimeException(e);
}
return null;
}
/**
* Adds the scores to a highscore table (a GUI-element).
* @param idc InfoDataContainer the info container containing the scores
*/
public void addScores(InfoDataContainer idc) {
HighscoreTable hi = new HighscoreTable();
for (List<PlayerInfo> lst:client2Info.values()) {
for (Iterator<PlayerInfo> itty = lst.iterator(); itty.hasNext(); ) {
PlayerInfo pi = itty.next();
hi.addLine(pi);
}
}
if (idc!=null) {
hi.addToContainer(idc);
}
}
public boolean isRunning() {
return running && serverImpl.isRunning();
}
private synchronized void addToPlayerInfoList(Integer cid, PlayerInfo pi) {
List<PlayerInfo> lst=client2Info.get(cid);
if (lst==null) {
lst=new ArrayList<PlayerInfo>();
client2Info.put(cid, lst);
}
lst.add(pi);
}
/**
* Initialize the server
*/
private void initServer() {
TeamAssigner.clear();
serverState=new ExtendedDataContainer();
String name=System.getProperty("user.name");
if (name==null) {
name="My server";
} else {
if (name.endsWith("s")) {
name+="'";
} else {
name+="'s";
}
name+=" server";
}
serverImpl=new SimpleServer(port,SimpleServer.UDP_DEFAULT_PORT,true, name);
serverImpl.addListener(this);
serverObjMan=new ServerObjectManager();
serverImpl.addLogoutListener(this);
serverImpl.addLoginListener(this);
eventMan=new ServerEventManager();
running=true;
}
/**
* Creates a new "server state". This means that all the data living on the server gets collected
* and stored in a structure ready to be transfered to the clients. This method is intended for
* fixed time frame usage.
*/
private void updateServerState() {
synchronized(SYNC) {
serverState=new ExtendedDataContainer();
// Transfer all client data
serverObjMan.fill(serverState, null);
}
}
/**
* Similar to above, but it excludes the client's own objects. This is intended for creating the
* data on demand.
* @param ci
* @return
*/
private ExtendedDataContainer createServerState(ClientInfo ci) {
//synchronized(SYNC) {
ExtendedDataContainer serverState=new ExtendedDataContainer();
// Transfer all client data
serverObjMan.fill(serverState, ci);
return serverState;
//}
}
/**
* The server's main loop.
* @throws Exception
*/
private void runServer() throws Exception {
if (!Globals.activeServer) {
// For an active Server (i.e. one that creates the data for each client on request not
// in fixed intervals, this method does nothing but returns
Ticker ticker = new Ticker(18);
while (!exit) {
int ticks = ticker.getTicks();
if (ticks > 0) {
updateServerState();
} else {
Thread.sleep(5);
}
}
stop();
}
}
}