- 
                Notifications
    
You must be signed in to change notification settings  - Fork 3
 
Using ArtClientLib
Home > Using ArtClientLib
This simple application connects to an Artemis server as a science console and prints out information on every packet it receives:
import java.io.IOException;
import net.dhleong.acl.enums.Console;
import net.dhleong.acl.iface.ArtemisNetworkInterface;
import net.dhleong.acl.iface.ConnectionSuccessEvent;
import net.dhleong.acl.iface.DisconnectEvent;
import net.dhleong.acl.iface.Listener;
import net.dhleong.acl.iface.ThreadedArtemisNetworkInterface;
import net.dhleong.acl.protocol.ArtemisPacket;
import net.dhleong.acl.protocol.core.setup.ReadyPacket;
import net.dhleong.acl.protocol.core.setup.SetConsolePacket;
public class ClientDemo {
	public static void main(String[] args) {
		if (args.length == 0) {
			System.out.println("Usage: ClientDemo {host} [port]");
			return;
		}
		String host = args[0];
		int port = args.length > 1 ? Integer.parseInt(args[1]) : 2010;
		try {
			new ClientDemo(host, port);
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}
	private ArtemisNetworkInterface server;
	public ClientDemo(String host, int port) throws IOException {
		server = new ThreadedArtemisNetworkInterface(host, port);
		server.addListener(this);
		server.start();
	}
	@Listener
	public void onConnectSuccess(ConnectionSuccessEvent event) {
		server.send(new SetConsolePacket(Console.SCIENCE, true));
		server.send(new ReadyPacket());
	}
	@Listener
	public void onPacket(ArtemisPacket pkt) {
		System.out.println(pkt);
	}
	@Listener
	public void onDisconnect(DisconnectEvent event) {
		System.out.println("Disconnected: " + event.getCause());
	}
}This object is responsible for managing the connection to the Artemis server. You must provide the constructor with the host/IP address and port to which it should connect. (By default, Artemis servers listen for connections on port 2010, but this can be changed in the Artemis .ini file.) On construction, it will attempt to connect, throwing an IOException if it fails.
Next, you must add one or more event listeners to the ThreadedArtemisNetworkInterface object via the addListener() method. Event listeners are objects which ArtClientLib will notify when certain events occur. An event listener can be any Object which has one or more methods marked with the @Listener annotation. A listener method must be public, return void, and have exactly one argument of type ConnectionEvent, ArtemisPacket, or any subtype of either. Listener methods are invoked by ArtClientLib when the corresponding event occurs. For example, if you create a listener method whose argument type is CommsIncomingPacket, that method will be invoked every time the COMMs console receives a text message.
One important event to listen for is the ConnectionSuccessEvent. You shouldn't attempt to send any packets to the server before you receive this event. This may also a good time to send a SetShipPacket and a SetConsolePacket.
It's also likely that you'll want to know when the connection to the server is lost; listening for DisconnectEvent will handle that.
Once your listeners are registered, invoke start() on the ThreadedArtemisNetworkInterface object. This spins up two Threads, one to receive and parse incoming packets, and one to send packets. Once you invoke start(), your listeners will start receiving events.
As your listeners receive events, you will inevitably want to send packets back to the Artemis server.
To send packets to the Artemis server, construct an instance of the appropriate subclass of ArtemisPacket, then pass it into the ThreadedArtemisNetworkInterface.send() method. For example, to fire a torpedo, construct a FireTubePacket and send() it.
To disconnect from the Artemis server, invoke the ThreadedArtemisNetworkInterface.stop() method. Make sure you do this so that the send and receive threads will be terminated; otherwise, your application won't stop.
The application below starts up a proxy Artemis server which accepts a connection from a single client, connects to the Artemis server on their behalf, and prints out all the traffic that passes between them.
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import net.dhleong.acl.enums.ConnectionType;
import net.dhleong.acl.iface.ArtemisNetworkInterface;
import net.dhleong.acl.iface.DisconnectEvent;
import net.dhleong.acl.iface.Listener;
import net.dhleong.acl.iface.ThreadedArtemisNetworkInterface;
import net.dhleong.acl.protocol.RawPacket;
public class ArtemisProxy implements Runnable {
	public static void main(String[] args) {
		String serverAddr = args[0];
		int port = args.length > 1 ? Integer.parseInt(args[1]) : 2010;
		new Thread(new ArtemisProxy(port, serverAddr)).start();
	}
	private int port;
	private String serverAddr;
	private int serverPort = 2010;
	private PacketPersistingProxyDebugger debugger;
	public ArtemisProxy(int port, String serverAddr) {
		this.port = port;
		int colonPos = serverAddr.indexOf(':');
		if (colonPos == -1) {
			this.serverAddr = serverAddr;
		} else {
			this.serverAddr = serverAddr.substring(0, colonPos);
			serverPort = Integer.parseInt(serverAddr.substring(colonPos + 1));
		}
	}
	@Override
	public void run() {
		ServerSocket listener = null;
		try {
			listener = new ServerSocket(this.port, 0);
			listener.setSoTimeout(0);
			System.out.println("Listening for connections on port " + this.port + "...");
			Socket skt = listener.accept();
			System.out.println("Received connection from " + skt.getRemoteSocketAddress());
			ThreadedArtemisNetworkInterface client = new ThreadedArtemisNetworkInterface(skt, ConnectionType.CLIENT);
			client.setParsePackets(false);
			System.out.println("Connecting to server at " + serverAddr + ":" + serverPort + "...");
			ThreadedArtemisNetworkInterface server = new ThreadedArtemisNetworkInterface(serverAddr, serverPort);
			server.setParsePackets(false);
			new ProxyListener(server, client);
			System.out.println("Connection established.");
		} catch (IOException ex) {
			ex.printStackTrace();
		} finally {
			if (listener != null && !listener.isClosed()) {
				try {
					listener.close();
				} catch (IOException ex) {
					ex.printStackTrace();
				}
			}
		}
	}
	public class ProxyListener {
		private ArtemisNetworkInterface server;
		private ArtemisNetworkInterface client;
		private ProxyListener(ArtemisNetworkInterface server, ArtemisNetworkInterface client) {
			this.server = server;
			this.client = client;
			server.addListener(this);
			client.addListener(this);
			server.start();
			client.start();
		}
		@Listener
		public void onDisconnect(DisconnectEvent event) {
			server.stop();
			client.stop();
			System.out.println("Disconnect: " + event);
			if (event.getException() != null) {
				event.getException().printStackTrace();
			}
		}
		@Listener
		public void onPacket(RawPacket pkt) {
			ConnectionType type = pkt.getConnectionType();
			ArtemisNetworkInterface dest = type == ConnectionType.SERVER ? client : server;
			dest.send(pkt);
			System.out.println(type + "> " + pkt);
		}
	}
}Open a ServerSocket on the desired port, then call accept() on it to listen for a connecting client. The accept() method will block until a client connects or it times out, and return a Socket object when the client connects. You can set the timeout by calling ServerSocket.setSoTimeout(); passing in 0 will cause it to wait indefinitely for a connection.
ThreadedArtemisNetworkInterface has a constructor that accepts a Socket and a ConnectionType (CLIENT in this case). The resulting object will be responsible for managing the connection to the client.
This is done exactly the same way as you would for creating an Artemis client, as documented above. You now have two ThreadedArtemisNetworkInterface objects: one for the client and one for the server.
Add your listeners to both the client and server objects. In addition to performing any additional operations your proxy may want to do, it's important to ensure that every packet that gets received is passed through to the other connection. There are two ways to do this. One way is to simply listen for ArtemisPacket; this will notify your listener about every packet received, which you can then pass to the other connection.
If your proxy is only interested in certain packets, you can create listeners for just those packets, plus one listener for RawPacket. A RawPacket listener will be invoked whenever ArtClientLib receives a packet that it doesn't know how to parse, or that no other listeners are interested in receiving. This is more efficient, because ArtClientLib won't have to expend effort parsing these packets. Make sure all your listeners send received packets to the other connection; otherwise, your proxy will be "eating" packets and the other side won't get them.
The example above isn't interested in doing anything with the packets other than printing out the raw bytes it receives, so it invokes setParsePackets(false) on both connections. This causes ArtClientLib to not attempt to parse any of the packets it receives.
Listen for the DisconnectEvent from both sides. When you receive it from one side, invoke ThreadedArtemisNetworkInterface.stop() on the other connection (or both connections, if that's easier; calling stop() on an already closed connection has no effect).
The most frequently received packets from the server are ones which provide updates on the status of objects in the game world. These updates typically contain only that information which has changed, not the complete state of the object. The SystemManager class aggregates the received updates in order to provide an up-to-the-moment view of the game world. To use it, simply construct a new SystemManager and add it as a listener to your ThreadedArtemisNetworkInterface; it has several listener methods which collect the relevant packets to build the game world. You can then use the various get*() methods to retrieve game state from the SystemManager.
The VesselData class allows you to access the information about the vessels and factions in the game.
By default, ArtClientLib assumes that you have a stock Artemis install. However, the user may have a mod installed that makes changes to the vesselData.xml file. To allow your application to correctly reflect a mod's changes, you can use the VesselData.load() static method, passing in the path to the Artemis install. This should be done before you attempt to read any information from VesselData or connect to other machines.
Invoke the VesselData.get() static method to get the VesselData instance. This is a singleton that exposes the information in the vesselData.xml file. From there, you can invoke getFaction(int) or getVessel(int) to retrieve the Faction or Vessel with the corresponding ID. You should make sure that your code deals gracefully with the possibility that no Faction or Vessel with the given ID exists (in which case, the get*() methods will return null). For example, if the user's vesselData.xml file doesn't match the server's, this can occur.