package edu.ups.gamedev.examples.two;
import java.awt.Color;
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.JColorChooser;
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.input.KeyInput;
import com.jme.input.action.InputActionEvent;
import com.jme.input.action.KeyInputAction;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import edu.ups.gamedev.net.message.ColoredSynchronizeCreateMessage;
import edu.ups.gamedev.player.PlayerSphere;
/**
* A simplified version of the previous examples. By simplified, we simply mean that
* the reflection has been removed and it's all been collapsed into a single class
* which can be started in either server or client mode. This is closer to the
* architecture our final game will have, so it is important to show that this is,
* in fact, possible.
*
* This example creates a sphere representing the local player that can be moved
* with the UHJK keys. Spheres from other clients will show up as the other clients
* connect.
*
* @author Walker Lindley
* @version $Revision: 1.3 $, $Date: 2008/01/29 06:46:05 $
*
*/
public class SimplifiedExample extends SimpleGame implements SyncObjectManager {
PlayerSphere localSphere; //the PlayerSphere of the local player
final static float MOVEINC = .25f; //amount to move a PlayerSphere by on each step
private static boolean runServer = false; //run a server if true, run a client if false
private static ColorRGBA color;
/**
* 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 SimplifiedExample app = new SimplifiedExample(); //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;
}
}
/**
* Allows the user to select which color their sphere will be.
*/
private static void getColorFromUser() {
Color resultColor = JColorChooser.showDialog(null, "Choose the color of your sphere", Color.red);
if(resultColor == null) {
color = ColorRGBA.red.clone();
}else {
color = new ColorRGBA(resultColor.getRed(), resultColor.getGreen(), resultColor.getBlue(), resultColor.getAlpha());
}
}
/**
* 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() {
localSphere = new PlayerSphere("localSphere", color.clone()); //the sphere for our player, we'll make it red
rootNode.attachChild(localSphere); //add our sphere 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 sphere, like WASD, but on the other side of the keyboard
input.addAction(new MoveForward(), "MoveForward", KeyInput.KEY_J, true);
input.addAction(new MoveBack(), "MoveBack", KeyInput.KEY_U, true);
input.addAction(new MoveRight(), "MoveRight", KeyInput.KEY_K, true);
input.addAction(new MoveLeft(), "MoveLeft", KeyInput.KEY_H, 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 sphere 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 sphere and make sure ot include its color
try {
syncManager.register(localSphere, new ColoredSynchronizeCreateMessage(localSphere.getColor()), 50);
} catch (IOException e) {
System.err.println("Could not register the local PlayerSphere:");
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 sphere or
* spheres.
*
*/
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 sphere and make sure ot include its color
try {
syncManager.register(localSphere, new ColoredSynchronizeCreateMessage(localSphere.getColor()), 50);
} catch (IOException e) {
System.err.println("Could not register local PlayerSphere:");
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 buildRemotePlayer(m.getColor());
}
return buildRemotePlayer(ColorRGBA.red.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) {
PlayerSphere s = (PlayerSphere)obj;
System.out.println("Removing player " + s.getName());
return s.removeFromParent();
}
/**
* Generates a new {@link edu.ups.gamedev.player.PlayerSphere PlayerSphere} for
* remote players and gives it default values. Note that all remote spheres will
* be green to help the player distinguish their sphere from the others.
*
* @param color the color of the <code>PlayerSphere</code> to be created
* @return the <code>PlayerSphere</code> that has been created to represent
* a remote player
*/
private PlayerSphere buildRemotePlayer(ColorRGBA color) {
PlayerSphere s = new PlayerSphere("remoteSphere", color); //create the sphere
rootNode.attachChild(s); //attach it to the scene graph
//make sure the material gets applied correctly by forcing a few updates
rootNode.updateGeometricState(0, true);
s.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
rootNode.updateRenderState();
return s;
}
//The events that occur when a key is pressed trigger actions.
//We can create custom actions by extending KeyInput action.
class MoveForward extends KeyInputAction {
public void performAction(InputActionEvent evt) {
//we get the location of the sphere, add a small vector to it, and set the resulting vector as the new position
localSphere.setLocalTranslation(localSphere.getLocalTranslation().add(new Vector3f(0, 0, MOVEINC)));
}
}
class MoveBack extends KeyInputAction {
public void performAction(InputActionEvent evt) {
localSphere.setLocalTranslation(localSphere.getLocalTranslation().add(new Vector3f(0, 0, -MOVEINC)));
}
}
class MoveRight extends KeyInputAction {
public void performAction(InputActionEvent evt) {
localSphere.setLocalTranslation(localSphere.getLocalTranslation().add(new Vector3f(MOVEINC, 0, 0)));
}
}
class MoveLeft extends KeyInputAction {
public void performAction(InputActionEvent evt) {
localSphere.setLocalTranslation(localSphere.getLocalTranslation().add(new Vector3f(-MOVEINC, 0, 0)));
}
}
}