/*
PlAr is Platform Arena: a 2D multiplayer shooting game
Copyright (c) 2010, Antonio Ragagnin <spocchio@gmail.com>
All rights reserved.
This file is licensed under the New BSD License.
*/
package plar.ClientServer;
import java.awt.Point;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.*;
import java.net.SocketAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import plar.ClientServer.Signal.Info;
import plar.ClientServer.Signal.LevelInfo;
import plar.ClientServer.Signal.Login;
import plar.core.Common;
import plar.core.Game;
import plar.core.KeyFlag;
import plar.core.Level;
import plar.core.ElementPlayer;
import plar.core.SpriteSet;
/**
*
* 1. Server class receive UDP packets and read the ID and Key flag
* 2. if them are both null, create a new ID,Key pair and send it to the Client
* Create also a new Connect() class to handle packets with this Id,Key pair
*
* data of an UDP packed:
* 1. first byte is the ID
* 2. second byte is the Key
* 3. third byte is the SIGNAL (see Signal.Java)
* 4. the other data could be anything, Connect() read the SIGNAL and decide what kind of data
* is stored in the packet.
*
* Compressions:
* for each SpriteSet for each Element in the Level, each SpriteSet is sent only ONE TIME.
* Repeated SpriteSet are not sent, SpriteSet of an element that is not yet shown is not sent.
*
* when the client request the content of a string an array of byte is sent, the logical data is:
* - for each Element in the screen: (int)x,(int)y,(int)angle,(int)SpriteSetId,(int)forFutureUse
* (forFutureUse it could become the speed in pixel units for local interpolations)
* - these 4 int, that use almost TEN BITS, are compressed in 5 byte
*
* the Client send the user input (stored in KeyFlag) once when repeated
* so the Server, send to Game the last input that the client had sent.
* for example
* - Client says the Left key was pressed
* - Server says to Game that Left Key was pressed until Client don't change it.
* For this reason is NECESSARY that client send an EMPTY KeyFlag when the user
* stop pressing keys
*
* Sprite and Element s:
* each Element in the Level should use a SpriteSet that contains an Array of Sprite.
* there are a LOT of elements with equal SpriteSet (usually statics)
* when the Client request the Screen, Server check if in the screen there are NEW SpriteSet
* that had not been sent yet,( these SpriteSet are stored in ArrayList <SpriteSet> buffer; )
* if yes it send the new SpriteSet and add it to "buffer", the Client should do the same
* and they buffer index should be sincronized.
* FOR EACH SpriteSet is used an UDP with the Signal.SPRITE
*
* For each Element in the Screen the server send the coordinates, the angle and the SpriteSetID stored in buffer.
*
* @author Antonio Ragagnin
*
*/
class Connect {
public Game game;
private ArrayList<SpriteSet> buffer;
public byte Key;
public byte Id;
DatagramSocket server;
SocketAddress addr;
ElementPlayer p;
ArrayList<ShownElement> screen;
ArrayList<Integer> indexToSend;
KeyFlag lastInput;
String username="noname";
public long lastSignal=0;
Connect(Game g, DatagramSocket s, byte k,byte id, SocketAddress a)
throws IOException {
lastSignal=Calendar.getInstance().getTimeInMillis();;
Key = k;
server = s;
game = g;
Id=id;
addr = a;
Common.info(7, "Connect: delay=");
buffer = new ArrayList<SpriteSet>();
screen = new ArrayList<ShownElement>();
indexToSend = new ArrayList<Integer>();
lastInput = new KeyFlag((short) 0);
}
public synchronized void process(byte[] data, byte signal) throws SocketException,
IOException, ClassNotFoundException {
lastSignal=Calendar.getInstance().getTimeInMillis();
// Common.info(1, "client processing data.length:"+data.length);
if (signal == Signal.LOGIN) {
Login login;
login = (Login) Signal.toObject(data);
// Common.info(1, "loginclass:"+login);
String name = login.userName;
username=name;
String type = login.playerName;
if(game.playerList.contains(type))
type = Common.playersPackage + type;
else
type = Common.playersPackage + game.playerList.get(0);
try {
@SuppressWarnings("unchecked")
Class c = Class.forName(type);
p = (ElementPlayer) c.newInstance();
} catch (Exception e) {
Common.info(1, "Connect.process() LOGIN: failed loading " + type);
close();
}
p.username=name;
game.addPlayer(p);
Common.info(1, "Connect.run(): new player: " + p + " name=" + name
+ " type=" + type);
}
if (signal == Signal.WELCOME) {
LevelInfo li = new LevelInfo();
Common.info(1,"received a WELCOME!");
List <String> lp=game.playerList;
List <String> lg=game.gunList;
li.MOTD = "Welcome to PlAr server v"+Common.version+" \n";
li.MOTD += " \n";
li.MOTD += " The game is running the level \""+game.level.description+"\"\n";
li.MOTD += " \n";
li.MOTD += " The are "+game.level.getPlayers().size()+" players online: \n";
li.MOTD += " \n";
li.MOTD += " The are "+lg.size()+" aviable guns in the game:\n";
li.MOTD += Common.joinAL(lg);
li.MOTD += " \n";
li.MOTD += " The are "+lg.size()+" aviable players \n";
li.MOTD += Common.joinAL(lp);
li.MOTD += " \n";
li.MOTD += "Please enter the kind of player (case sensitive) \n";
li.resolution = game.screenResolution;
li.scaleX = game.scale.x;
li.scaleY = game.scale.y;
//li.guns=game.gunList;
server.send(Signal.send(li, (byte) 0, (byte) 0, Signal.LEVELINFO, addr));
Common.info(1,"sent a WELCOME!");
} else if (signal == Signal.SPRITE || signal == Signal.SPRITEANDSCREEN) {
// Common.info(8,"REQUESTED SPRITES!");
screen = game.getScreen(p);
indexToSend = new ArrayList<Integer>();
ArrayList<Integer> indexToBeAdded = new ArrayList<Integer>();
for (int i = 0; i < screen.size(); i++) {
ShownElement e = screen.get(i);
boolean exist = false;
for (int j = 0; (j < buffer.size() && !exist); j++) {
if (e.spriteSet.equals(buffer.get(j))) {
exist = true;
// Common.info("found element in position "+j);
indexToSend.add(j);
}
}
if (!exist) {
buffer.add(e.spriteSet);
indexToBeAdded.add(buffer.size() - 1);
indexToSend.add(buffer.size() - 1);
}
}
for (int i = 0; i < indexToBeAdded.size(); i++) {
Object o = buffer.get(indexToBeAdded.get(i));
server.send(Signal.send(o, (byte) 0, (byte) 0, Signal.SPRITE,
addr));
}
} else if (signal == Signal.DIRECTION) {
byte x = data[0];
byte y = data[1];
float dx = (float) x;
float dy = (float) y;
dx /= 127;
dy /= 127;
Common.info(1, "Connect.run(): someone clicked the mouse: dirx" + dx
+ " diry" + dy + "");
p.directionX = dx;
p.directionY = dy;
} else if (signal == Signal.MESSAGE) {
String c = (String) Signal.toObject(data);
game.sendChat(p, c);
Common.info(1,"<"+p.username+">" +c);
} else if (signal == Signal.INFO) {
if(p==null)
{
sendMessage("OPS! Server received signals in the wrong order. (LOGIN,INFO)");
}else{
Info info = new Info();
info.chat = game.getChat();
info.life = p.energy;
info.score = p.kills;
info.spawns=p.killed;
info.guns=game.getGuns(p);
info.remaning=game.remaning;
//info.chat="";
server.send(Signal
.send(info, (byte) 0, (byte) 0, Signal.INFO, addr));
}
}
if (signal == Signal.SCREEN || signal == Signal.SPRITEANDSCREEN) {
// Common.info(8,"REQUESTED SKREEN");
//int N = 5;
// int N = 6;
int N = 4;
byte[] cData = new byte[screen.size() * N *2];
// cData[0] = (byte) screen.size();
// Common.info(8,"screen size:"+screen.size()+" with"+(screen.size()*6));
for (int i = 0; i < screen.size(); i++) {
ShownElement s = screen.get(i);
int Itheta = (((int) (1000 * s.theta)) % 6282);
float Ptheta = Itheta / 100;
float Ntheta = Ptheta / 6.2831f;
int[] by = { s.position.x, s.position.y, Itheta,
indexToSend.get(i), 0
};
/*
byte[] c = Signal.from5IntTo6Bytes(by);
cData[0 + i * N] = c[0];
cData[1 + i * N] = c[1];
cData[2 + i * N] = c[2];
cData[3 + i * N] = c[3];
cData[4 + i * N] = c[4];
*/
cData[0 + i *2* N] = Signal.From1ShortToFByte((short)s.position.x);
cData[1 + i *2* N ] = Signal.From1ShortToSByte((short)s.position.x);
cData[2 + i *2* N] = Signal.From1ShortToFByte((short)s.position.y);
cData[3 + i *2* N ] = Signal.From1ShortToSByte((short)s.position.y);
cData[4 + i *2* N] = Signal.From1ShortToFByte((short)Itheta);
cData[5 + i *2* N ] = Signal.From1ShortToSByte((short)Itheta);
cData[6 + i *2* N] = Signal.From1ShortToFByte((short)((int)indexToSend.get(i)));
cData[7 + i *2* N ] = Signal.From1ShortToSByte((short)((int)indexToSend.get(i)));
// cData[3 + i * N] = (short)((int)indexToSend.get(i));
/* Common.info(1,"sending: "+s.position.x+":["+cData[0 + i * 2*N]+" "+cData[1 + i *2* N]+"]"
+s.position.y+":["+cData[2 + i *2* N]+" "+cData[3 + i *2* N]+"]"
+0+":["+cData[4 + i *2* N]+" "+cData[5 + i *2* N]+"]"
+((int)indexToSend.get(i))+":["+cData[6 + i *2* N]+" "+cData[7 + i *2* N]+"]"
);
*/
//cData[4 + i * N] = c[4];
// cData[5+i*N]=c[5];
}
server.send(Signal.send(cData, (byte) 0, (byte) 0, Signal.SCREEN,
addr));
} else if (signal == Signal.INPUT) {
KeyFlag strc = null;
try {
short key = (Short) Signal.toObject(data);
strc = new KeyFlag(key);
} catch (Exception e) {
strc = lastInput;
}
lastInput = strc;
game.sendInput(p, strc);
}
if (signal == Signal.STOP) {
close();
}
}
public synchronized void sendMessage(String s)
{
try{
server.send(Signal
.send(s, (byte) 0, (byte) 0, Signal.MESSAGE, addr));
}catch(Exception e)
{
Common.info(1," ERROR sendin message "+s+" to "+Id);
}
}
public synchronized void close() {
game.sendChat( username + " has quit the game!");
if(p!=null) game.level.delElement(p);
}
}
public class Server extends Thread {
public boolean active = true;
public DatagramSocket dateServer;
public Game g;
public Level level;
public HashMap<String, ElementPlayer> playerList;
public HashMap<Byte, Connect> connections;
public long refresh = 10;
public long timeout = 60*1000;
public Point resolution;
public long duration;
public long now;
public long start;
public Server() {
super();
resolution = new Point(700, 700);
connections = new HashMap<Byte, Connect>();
duration=60*1000*(new Long(Common.getConfig().getProperty("time")));
now=Common.now();
start=Common.now();
}
public void close() {
active = false;
for (Byte b : connections.keySet()) {
connections.get(b).close();
}
dateServer.close();
this.interrupt();
}
public boolean listen(int port) {
try {
dateServer = new DatagramSocket(port);
System.out.println("Server listening on port " + port + ".");
this.start();
} catch (Exception e) {
System.out.println("failed listeninng.");
return false;
}
return true;
}
public void newGame(Level l) {
g = new Game();
g.screenResolution = resolution;
g.setLevel(l);
g.startGame();
}
public String getScore()
{
Collection cc = connections.values();
String finale="<html><table><tr><td>Username</td><td>Score</td><td>Deaths</td>";
{
Iterator itr = cc.iterator();
while(itr.hasNext())
{
Connect c = (Connect)itr.next();
if(c!=null){
finale+="<tr><td>["+c.p.username+"]</td><td> "+(c.p.kills)+"</td><td> "+(c.p.killed)+"</td></tr>";
}
}
finale+="</html>";
}
return finale;
}
public void sendBroadcast(String finale){
Collection cc = connections.values();
Iterator itr = cc.iterator();
while(itr.hasNext())
{
Connect c = (Connect)itr.next();
if(c!=null){
c.sendMessage(finale);
}
}
}
public void restartGame()
{
String msg="Game Finished!\n\nHighscores:\n"+getScore();
sendBroadcast(msg);
g.restart();
start=Common.now();
}
public void loadGame() {
class autoupdate extends Thread {
public void run() {
try {
while (active) {
g.run();
Thread.sleep(refresh);
}
} catch (Exception e) {
Common.info(1," --------------------------------------- ");
e.printStackTrace();
}
}
}
class FindTimeout extends Thread {
public void run() {
try {
while (active) {
Thread.sleep(timeout);
syncrun();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void syncrun()
{
synchronized (connections)
{
Collection cc = connections.values();
Iterator itr = cc.iterator();
//iterate through HashMap values iterator
while(itr.hasNext())
{
Connect c = (Connect)itr.next();
if(c!=null){
if((c.lastSignal+timeout)<now) {
Common.info(1,"FOUND GHOST!");
c.close();
connections.remove(c.Id);
}
}else Common.info(1,"* c="+c);
}
}
}
}
class RestartMatch extends Thread {
public void run() {
try {
while (active) {
Thread.sleep(duration);
syncrun();
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
public void syncrun()
{
synchronized(connections)
{
restartGame();
}
}
}
Thread t1 = new autoupdate();
t1.setName("Thread principale");
t1.setPriority(10);
t1.start();
Thread t2 = new FindTimeout();
t2.setName("Thread ghost");
t2.setPriority(10);
t2.start();
Thread t3 = new RestartMatch();
t3.setName("Thread mathes");
t3.setPriority(10);
t3.start();
Common.info(1, "Server.newGame() refreshtime=" + refresh + " level="
+ level);
}
public void run() {
try {
while (active) {
syncrun();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void syncrun() throws Exception
{
now=Common.now();
g.remaning=start+duration-now;
byte[] data = new byte[dateServer.getSendBufferSize()];
DatagramPacket dp = new DatagramPacket(data, 2048);
// System.out.println("Waiting for datagrams");
dateServer.receive(dp);
data = Arrays.copyOf(data, dp.getLength());
// System.out.println("Waiting for datagrams"+dp.getData().length);
// = dp.getData();
if (data.length >= 2) {
byte ID = Signal.getID(data);
byte Key = Signal.getKey(data);
// Common.info(1,
// "received a packet. ID="+ID+" KEY="+Key+" from:"+dp.getSocketAddress());
synchronized (connections){
if (connections.containsKey(ID)
&& connections.get(ID).Key == Key) {
// Common.info(1, "it matches in the database");
byte sig = Signal.getSignal(data);
// Common.info(1, "signal = "+sig);
byte[] newData = Signal.getData(data);
connections.get(ID).process(newData, sig);
} else if (Key == 0) {
// log the new client
byte newID = (byte) (connections.size() + 1);
byte newKey = (byte) (connections.size());
Common.info(1, "created new connection: ID=" + newID
+ " KEY=" + newKey);
dateServer.send(Signal.send(new byte[0], newID, newKey,
(byte) 0, dp.getSocketAddress()));
Connect c = new Connect(g, dateServer, newKey,newID, dp
.getSocketAddress());
connections.put(newID, c);
}
}
}
}
}