package edu.ups.gamedev.examples.three;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import javax.swing.JOptionPane;
import com.captiveimagination.jgn.JGN;
import com.captiveimagination.jgn.clientserver.JGNClient;
import com.captiveimagination.jgn.clientserver.JGNServer;
import com.captiveimagination.jgn.event.MessageListener;
import com.captiveimagination.jgn.message.Message;
import com.captiveimagination.jgn.synchronization.SyncObjectManager;
import com.captiveimagination.jgn.synchronization.SynchronizationManager;
import com.captiveimagination.jgn.synchronization.message.SynchronizeCreateMessage;
import com.captiveimagination.jgn.synchronization.message.SynchronizeRemoveMessage;
import com.captiveimagination.jmenet.JMEGraphicalController;
import com.jme.app.AbstractGame;
import com.jme.app.SimpleGame;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import edu.ups.gamedev.net.message.ColoredSynchronizeCreateMessage;
import edu.ups.gamedev.player.Tank;
/**
* An expansion of the previous example that replaces the spheres with <code>Tank</code>s.
* This example creates a <code>Tank</code> representing the local player that can be
* moved with the UHJK keys. Tanks from other clients will show up as the other clients
* connect.
*
* @author Walker Lindley
* @version $Revision: 1.2 $, $Date: 2008/01/29 06:32:15 $
*
*/
public class ModelExample extends SimpleGame implements SyncObjectManager {
Tank localTank; //the Tank of the local player
private static boolean runServer = false; //run a server if true, run a client if false
private static ColorRGBA color = ColorRGBA.gray.clone();
/**
* Sets up and runs this example. It first asks the user if it should run as a
* client or as a server. Then it displays the jMonkey option panel before
* launching the actual program.
*
* @param args the array containing any command-line arguments
*/
public static void main(String[] args) {
//Ask the user if whether or not to run a server, by default we won't
askRunServer();
//Ask the user what color to use for the sphere
//getColorFromUser();
//get a URL for the image to use instead of jMonkey's default image
URL pic;
try {
pic = new File("resources/tank.jpg").toURI().toURL(); //File's toURL method is deprecated, so we must first convert it to a URI and then to a URL
} catch (MalformedURLException e) {
System.err.println("Couldn't find image:");
e.printStackTrace();
pic = null;
}
//create and run the game
final ModelExample app = new ModelExample(); //create an instance of our class
app.setDialogBehaviour(AbstractGame.ALWAYS_SHOW_PROPS_DIALOG, pic); //make sure the properities dialog shows up and tell it to use our image
app.start();
}
/**
* Queries the user for whether or not to run in server mode.
*/
private static void askRunServer() {
int result = JOptionPane.showConfirmDialog(null, "Should the program run in server mode?", "Run a server?", JOptionPane.YES_NO_OPTION);
if(result == JOptionPane.YES_OPTION) {
runServer = true;
}
}
/**
* Initializes the game by creating the local player, assigning keyboard controls,
* and initializing the network connection. This method is automatically called
* by SimpleGame to give us a chance to setup our custom environment.
*
* @see SimpleGame
*/
@Override
protected void simpleInitGame() {
try {
localTank = new Tank(Tank.LIGHT_TANK, color.clone()); //our player's light tank with the color chosen by the player
} catch (MalformedURLException e) {
System.err.println("Could not create Tank:");
JOptionPane.showMessageDialog(null, "Could not create tank, exiting program...", "Fatal Error", JOptionPane.ERROR_MESSAGE);
e.printStackTrace();
System.exit(1);
}
rootNode.attachChild(localTank); //add our tank to the scenegraph
//setup additional keyboard commands
//note that SimpleGame (which our class extends) already gives us WASD movement and mouse-look controls
//so we'll use UHJK to move the client tank, like WASD, but on the other side of the keyboard
/*input.addAction(localTank.getMoveForward(), "MoveForward", KeyInput.KEY_J, true);
input.addAction(localTank.getMoveBackward(), "MoveBack", KeyInput.KEY_U, true);
input.addAction(localTank.getMoveLeft(), "MoveLeft", KeyInput.KEY_H, true);
input.addAction(localTank.getMoveRight(), "MoveRight", KeyInput.KEY_K, true);
input.addAction(localTank.getRotateLeft(), "RotateLeft", KeyInput.KEY_Y, true);
input.addAction(localTank.getRotateRight(), "RotateRight", KeyInput.KEY_I, true);*/
//initialize the network connection based on which option the user selected
if(runServer) {
runServer();
}else {
runClient();
}
}
/**
* Creates and initializes a server. The game can essentially be run in one of
* two modes, server or client, but not both. This puts the game in server
* mode which still creates a tank for the local player and lets them move
* it. Server mode allows clients to connect and facilitates communication
* between the the clients.
*
*/
private void runServer() {
//create the addresses (really just ports) the server will use
InetSocketAddress serverReliable, serverFast;
try {
serverReliable = new InetSocketAddress(InetAddress.getLocalHost(), 1500);
serverFast = new InetSocketAddress(InetAddress.getLocalHost(), 1501);
} catch (UnknownHostException e) {
System.err.println("Couldn't create local addresses:");
e.printStackTrace();
return;
}
JGNServer server; //this is our primary server and the addresses it should use
try {
server = new JGNServer(serverReliable, serverFast);
} catch (IOException e) {
System.err.println("Couldn't create JGN server");
e.printStackTrace();
return;
}
JMEGraphicalController controller = new JMEGraphicalController(); //in charge of generating and applying sync messages
SynchronizationManager syncManager = new SynchronizationManager(server, controller); //create the server that will send and receive sync messages
syncManager.addSyncObjectManager(this);
JGN.createThread(server).start(); //create a new thread for the server and start it
JGN.createThread(syncManager).start(); //create and start a thread for the synchronization manager
//register our tank and make sure ot include its color
try {
syncManager.register(localTank, new ColoredSynchronizeCreateMessage(localTank.getColor()), 50);
} catch (IOException e) {
System.err.println("Could not register the local Tank:");
e.printStackTrace();
return;
}
}
/**
* Creates and initializes a client. The game can essentially be run in one of
* two modes, server or client, but not both. This puts the game in client
* mode which attempts to connect to a server before displaying the tank or
* tankss.
*
*/
private void runClient() {
//server addresses
InetSocketAddress serverReliable, serverFast;
//client addresses
InetSocketAddress clientReliable, clientFast;
try {
serverReliable = new InetSocketAddress(InetAddress.getLocalHost(), 1500);
serverFast = new InetSocketAddress(InetAddress.getLocalHost(), 1501);
clientReliable = new InetSocketAddress(InetAddress.getLocalHost(), 0); //let Java choose a port for us
clientFast = new InetSocketAddress(InetAddress.getLocalHost(), 0); //let Java choose a port for us
}catch(UnknownHostException e) {
System.err.println("Could not find host:");
e.printStackTrace();
return;
}
//create the client and start it in its own thread
JGNClient client;
try {
client = new JGNClient(clientReliable, clientFast); //create the networking client
} catch (IOException e) {
System.err.println("Could not create JGN client:");
e.printStackTrace();
return;
}
JGN.createThread(client).start(); //create a new thread for the client and start it
JMEGraphicalController controller = new JMEGraphicalController(); //in charge of generating and applying sync messages
//create the sync manager, register ourselves with it, and start its thread
SynchronizationManager syncManager = new SynchronizationManager(client, controller);
syncManager.addSyncObjectManager(this);
JGN.createThread(syncManager).start();
//connect to the server
System.out.println("Connecting...");
try {
client.connectAndWait(serverReliable, serverFast, 5000);
} catch (IOException e) {
//Show an error message that we couldn't connect and print detailed info to standard error
JOptionPane.showMessageDialog(null, "Could not connect to server.", "Connection Error", JOptionPane.ERROR_MESSAGE);
System.err.println("Error while connecting to server:");
e.printStackTrace();
return;
} catch (InterruptedException e) {
//Show an error message that we couldn't connect and print detailed info to standard error
JOptionPane.showMessageDialog(null, "Could not connect to server.", "Connection Error", JOptionPane.ERROR_MESSAGE);
System.err.println("Error while connecting to server:");
e.printStackTrace();
return;
}
System.out.println("Connected!");
//attach a MessageListener so that we can get more detailed information about what the server is doing
client.getServerConnection().getReliableClient().addMessageListener(new MessageListener() {
public void messageCertified(Message message) {
System.out.println("Message Certified: " + message);
}
public void messageFailed(Message message) {
System.out.println("Message Failed: " + message);
}
public void messageReceived(Message message) {
System.out.println("Message Received: " + message);
}
public void messageSent(Message message) {
System.out.println("Message Sent: " + message);
}
});
//register our tank and make sure ot include its color
try {
syncManager.register(localTank, new ColoredSynchronizeCreateMessage(localTank.getColor()), 50);
} catch (IOException e) {
System.err.println("Could not register local Tank:");
e.printStackTrace();
return;
}
}
/**
* Called when a remote host requests an object be created. It creates a generic
* object to represent the remote player. This object is returned to the caller
* who ensures that it is automatically updated. The <code>SyncronizeCreateMessage
* </code> contains information about the object to be created.
*
* @param scm the message requesting that an object be created
* @return the Object created to satisy this request
*/
public Object create(SynchronizeCreateMessage scm) {
System.out.println("Adding player");
if(scm instanceof ColoredSynchronizeCreateMessage) {
ColoredSynchronizeCreateMessage m = (ColoredSynchronizeCreateMessage)scm;
return buildRemoteTank(m.getColor());
}
return buildRemoteTank(ColorRGBA.gray.clone());
}
/**
* Called when a remote host requests an object be removed. It prints a
* notification to standard out and then removes the object from the scene graph.
*
* @param srm the message requesting that an object be removed
* @param obj the actual <code>Object</code> that is going to be removed
* @return <code>true</code> if the <code>Object</code> was removed; <code>
* false</code> otherwise
*/
public boolean remove(SynchronizeRemoveMessage srm, Object obj) {
Tank tank = (Tank)obj;
System.out.println("Removing player " + tank.getName());
return tank.removeFromParent();
}
/**
* Generates a new {@link edu.ups.gamedev.player.Tank Tank} for remote players
* and gives it default values and the color that is passed in.
*
* @param color the color of the <code>Tank</code> to be created
* @return the <code>Tank</code> that has been created to represent
* a remote player
*/
private Tank buildRemoteTank(ColorRGBA color) {
Tank tank;
try {
tank = new Tank("remoteTank", color); //create the tank
} catch (MalformedURLException e) {
System.err.println("Could not create tank for remote player:");
e.printStackTrace();
return null;
}
rootNode.attachChild(tank); //attach it to the scene graph
//make sure the material gets applied correctly by forcing a few updates
rootNode.updateGeometricState(0, true);
tank.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
rootNode.updateRenderState();
return tank;
}
}