/*******************************************************************************
* Copyright 2015 Maximilian Stark | Dakror <mail@dakror.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package de.dakror.arise.net;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.BindException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.CopyOnWriteArrayList;
import de.dakror.arise.AriseServer;
import de.dakror.arise.game.Game;
import de.dakror.arise.net.packet.Packet;
import de.dakror.arise.net.packet.Packet.PacketTypes;
import de.dakror.arise.net.packet.Packet00Handshake;
import de.dakror.arise.net.packet.Packet01Login;
import de.dakror.arise.net.packet.Packet01Login.Response;
import de.dakror.arise.net.packet.Packet02Disconnect;
import de.dakror.arise.net.packet.Packet02Disconnect.Cause;
import de.dakror.arise.net.packet.Packet03World;
import de.dakror.arise.net.packet.Packet04City;
import de.dakror.arise.net.packet.Packet05Resources;
import de.dakror.arise.net.packet.Packet06Building;
import de.dakror.arise.net.packet.Packet07RenameCity;
import de.dakror.arise.net.packet.Packet08PlaceBuilding;
import de.dakror.arise.net.packet.Packet09BuildingStage;
import de.dakror.arise.net.packet.Packet10Attribute;
import de.dakror.arise.net.packet.Packet10Attribute.Key;
import de.dakror.arise.net.packet.Packet11DeconstructBuilding;
import de.dakror.arise.net.packet.Packet12UpgradeBuilding;
import de.dakror.arise.net.packet.Packet15BarracksBuildTroop;
import de.dakror.arise.net.packet.Packet16BuildingMeta;
import de.dakror.arise.net.packet.Packet17CityAttack;
import de.dakror.arise.net.packet.Packet19Transfer;
import de.dakror.arise.server.DBManager;
import de.dakror.arise.server.ServerUpdater;
import de.dakror.arise.settings.CFG;
import de.dakror.gamesetup.util.Helper;
/**
* @author Dakror
*/
public class Server extends Thread {
public static Server currentServer;
public static final int PORT = 14744;
public static final int PACKETSIZE = 255; // bytes
public static File dir;
public boolean running;
public CopyOnWriteArrayList<User> clients = new CopyOnWriteArrayList<>();
ServerUpdater updater;
DatagramSocket socket;
public BufferedWriter logWriter;
public Server(InetAddress ip) {
currentServer = this;
try {
dir = new File(CFG.DIR, "Server");
dir.mkdir();
socket = new DatagramSocket(new InetSocketAddress(ip, Server.PORT));
setName("Server Thread");
setPriority(MAX_PRIORITY);
out("Connecting to database");
DBManager.init();
out("Fetching configuration");
Game.loadConfig();
if (AriseServer.isLogging()) {
logWriter = new BufferedWriter(new FileWriter(new File(new File(AriseServer.properties.getProperty("logfile")), "status.log")));
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
logWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
updater = new ServerUpdater();
out("Starting server at " + socket.getLocalAddress().getHostAddress() + ":" + socket.getLocalPort());
start();
} catch (BindException e) {
err("There is a server already running on this machine!");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
running = true;
while (running) {
byte[] data = new byte[PACKETSIZE];
DatagramPacket packet = new DatagramPacket(data, data.length);
try {
socket.receive(packet);
parsePacket(data, packet.getAddress(), packet.getPort());
} catch (SocketException e) {} catch (Exception e) {
e.printStackTrace();
}
}
}
public void parsePacket(byte[] data, InetAddress address, int port) {
PacketTypes type = Packet.lookupPacket(data[0]);
final User user = getUserForIP(address, port);
if (user != null) user.interact();
if (AriseServer.trafficLog != null) {
AriseServer.trafficLog.setText(AriseServer.trafficLog.getText() + new SimpleDateFormat("'['HH:mm:ss']: '").format(new Date()) + "< " + address.getHostAddress() + ":" + port
+ " " + type.name() + "\n");
AriseServer.trafficLog.setCaretPosition(AriseServer.trafficLog.getDocument().getLength());
}
switch (type) {
case INVALID: {
err("Received invalid packet: " + new String(data));
break;
}
case HANDSHAKE: {
try {
sendPacket(new Packet00Handshake(), user == null ? new User(0, 0, "", address, port) : user);
if (user == null) out("Shook hands with: " + address.getHostAddress() + ":" + port);
break;
} catch (Exception e) {
e.printStackTrace();
}
}
case LOGIN: {
try {
Packet01Login p = new Packet01Login(data);
String s = Helper.getURLContent(new URL("http://dakror.de/mp-api/login_noip.php?username=" + p.getUsername() + "&password=" + p.getPwdMd5()));
boolean loggedIn = s.contains("true");
boolean worldExists = DBManager.getWorldForId(p.getWorldId()).getId() != -1;
if (loggedIn && worldExists) {
String[] parts = s.split(":");
User u = new User(Integer.parseInt(new String(parts[1]).trim()), p.getWorldId(), new String(parts[2]), address, port);
boolean alreadyLoggedIn = getUserForId(u.getId()) != null;
if (alreadyLoggedIn) {
out("Refused login of " + address.getHostAddress() + ":" + port + " (" + Response.ALREADY_LOGGED_IN.name() + ")");
sendPacket(new Packet01Login(p.getUsername(), 0, p.getWorldId(), Response.ALREADY_LOGGED_IN), u);
} else {
out("User " + new String(parts[2]).trim() + " (#" + u.getId() + ")" + " logged in on world #" + p.getWorldId() + ".");
sendPacket(new Packet01Login(new String(parts[2]), u.getId(), p.getWorldId(), Response.LOGIN_OK), u);
clients.add(u);
}
} else {
out("Refused login of " + address.getHostAddress() + ":" + port + " (" + (!loggedIn ? Response.BAD_LOGIN : Response.BAD_WORLD_ID).name() + ")");
sendPacket(new Packet01Login(p.getUsername(), 0, p.getWorldId(), !loggedIn ? Response.BAD_LOGIN : Response.BAD_WORLD_ID), new User(0, 0, "", address, port));
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
case DISCONNECT: {
Packet02Disconnect p = new Packet02Disconnect(data);
for (User u : clients) {
if (u.getId() == p.getUserId() && address.equals(u.getIP())) {
try {
sendPacket(new Packet02Disconnect(0, Cause.SERVER_CONFIRMED), u);
out("User disconnected: #" + u.getId() + " (" + p.getCause().name() + ")");
clients.remove(u);
} catch (Exception e) {
e.printStackTrace();
}
}
}
break;
}
case WORLD: {
try {
Packet03World p = new Packet03World(data);
boolean spawn = DBManager.spawnPlayer(p.getId(), user);
out("Player's first visit on world? " + spawn);
sendPacket(DBManager.getWorldForId(p.getId()), user);
for (User u : clients) {
if (u.getWorldId() == user.getWorldId() && !(user.getIP().equals(u.getIP()) && user.getPort() == u.getPort())) {
sendPacket(DBManager.getSpawnCity(p.getId(), user.getId()), u);
}
}
break;
} catch (Exception e) {
e.printStackTrace();
}
}
case RESOURCES: {
try {
Packet05Resources p = new Packet05Resources(data);
if (DBManager.isCityFromUser(p.getCityId(), user)) sendPacket(new Packet05Resources(p.getCityId(), DBManager.getCityResources(p.getCityId())), user);
break;
} catch (Exception e) {
e.printStackTrace();
}
}
case BUILDING: {
Packet06Building p = new Packet06Building(data);
if (p.getBuildingType() == 0 && DBManager.isCityFromUser(p.getCityId(), user)) {
try {
for (Packet06Building packet : DBManager.getCityBuildings(p.getCityId()))
sendPacket(packet, user);
} catch (Exception e) {
e.printStackTrace();
}
}
break;
}
case RENAMECITY: {
Packet07RenameCity p = new Packet07RenameCity(data);
if (DBManager.isCityFromUser(p.getCityId(), user)) {
boolean worked = DBManager.renameCity(p.getCityId(), p.getNewName(), user);
try {
sendPacketToAllClients(new Packet07RenameCity(p.getCityId(), worked ? p.getNewName() : "#false#"));
} catch (Exception e) {
e.printStackTrace();
}
}
break;
}
case PLACEBUILDING: {
Packet08PlaceBuilding p = new Packet08PlaceBuilding(data);
if (DBManager.isCityFromUser(p.getCityId(), user)) {
int id = DBManager.placeBuilding(p.getCityId(), p.getBuildingType(), p.getX(), p.getY());
if (id != 0) {
try {
sendPacket(DBManager.getCityBuilding(p.getCityId(), id), user);
sendPacket(new Packet05Resources(p.getCityId(), DBManager.getCityResources(p.getCityId())), user);
} catch (Exception e) {
e.printStackTrace();
}
}
}
break;
}
case ATTRIBUTE: {
Packet10Attribute p = new Packet10Attribute(data);
if (user != null) {
switch (p.getKey()) {
case city: {
user.setCity(Integer.parseInt(p.getValue()));
break;
}
case world_data: {
try {
// -- cities -- //
for (Packet04City packet : DBManager.getCities(Integer.parseInt(p.getValue())))
sendPacket(packet, user);
// -- transfers -- //
for (Packet19Transfer packet : DBManager.getTransfers(user))
sendPacket(packet, user);
sendPacket(new Packet10Attribute(Key.loading_complete), user);
} catch (Exception e) {
e.printStackTrace();
}
break;
}
default:
;
}
}
break;
}
case DECONSTRUCTBUILDING: {
Packet11DeconstructBuilding p = new Packet11DeconstructBuilding(data);
if (DBManager.isCityFromUser(p.getCityId(), user)) {
int timeleft = 0;
if ((timeleft = DBManager.deconstructBuilding(p.getCityId(), p.getBuildingId())) > -1) {
try {
sendPacket(new Packet09BuildingStage(p.getBuildingId(), 2, timeleft), user);
} catch (Exception e) {
e.printStackTrace();
}
}
}
break;
}
case UPGRADEBUILDING: {
Packet12UpgradeBuilding p = new Packet12UpgradeBuilding(data);
if (DBManager.isCityFromUser(p.getCityId(), user)) {
int timeleft = 0;
if ((timeleft = DBManager.upgradeBuilding(p.getCityId(), p.getBuildingId())) > -1) {
try {
sendPacket(new Packet09BuildingStage(p.getBuildingId(), 3, timeleft), user);
} catch (Exception e) {
e.printStackTrace();
}
}
}
break;
}
case BARRACKSBUILDTROOP: {
Packet15BarracksBuildTroop p = new Packet15BarracksBuildTroop(data);
if (DBManager.isCityFromUser(p.getCityId(), user)) {
try {
int timeleft = DBManager.barracksBuildTroops(p);
if (timeleft > -1) {
sendPacket(new Packet09BuildingStage(p.getBuildingId(), 1, timeleft), user);
sendPacket(new Packet16BuildingMeta(p.getBuildingId(), p.getTroopType().ordinal() + ":" + p.getAmount()), user);
sendPacket(new Packet05Resources(p.getCityId(), DBManager.getCityResources(p.getCityId())), user);
}
} catch (Exception e) {
e.printStackTrace();
}
}
break;
}
case CITYATTACK: {
final Packet17CityAttack p = new Packet17CityAttack(data);
if (DBManager.isCityFromUser(p.getAttCityId(), user) && !DBManager.isCityFromUser(p.getDefCityId(), user) && p.getAttArmy().getLength() > 0) {
try {
sendPacket(DBManager.transferAttackTroops(p), user);
sendPacket(new Packet05Resources(p.getAttCityId(), DBManager.getCityResources(p.getAttCityId())), user);
} catch (Exception e) {
e.printStackTrace();
}
}
break;
}
default:
err("Received unhandled packet (" + address.getHostAddress() + ":" + port + ") " + type + " [" + Packet.readData(data) + "]");
}
}
public void sendPacketToAllClients(Packet p) throws Exception {
for (User u : clients)
sendPacket(p, u);
}
public void sendPacketToAllClientsExceptOne(Packet p, User exception) throws Exception {
for (User u : clients) {
if (exception.getId() == 0) {
if (exception.getIP().equals(u.getIP()) && exception.getPort() == u.getPort()) continue;
} else if (exception.getId() == u.getId()) continue;
sendPacket(p, u);
}
}
public void sendPacketToAllClientsOnWorld(Packet p, int worldId) throws Exception {
for (User u : clients)
if (u.getWorldId() == worldId) sendPacket(p, u);
}
public void sendPacket(Packet p, User u) throws Exception {
if (u == null) throw new NullPointerException("user = null");
byte[] data = p.getData();
DatagramPacket packet = new DatagramPacket(data, data.length, u.getIP(), u.getPort());
socket.send(packet);
if (AriseServer.trafficLog != null) {
AriseServer.trafficLog.setText(AriseServer.trafficLog.getText() + new SimpleDateFormat("'['HH:mm:ss']: '").format(new Date()) + "> " + u.getIP().getHostAddress() + ":"
+ u.getPort() + " " + p.getType().name() + "\n");
AriseServer.trafficLog.setCaretPosition(AriseServer.trafficLog.getDocument().getLength());
}
}
public User getUserForIP(InetAddress address, int port) {
for (User u : clients)
if (u.getIP().equals(address) && u.getPort() == port) return u;
return null;
}
public User getUserForId(int id) {
for (User u : clients)
if (u.getId() == id) return u;
return null;
}
public void shutdown() {
try {
sendPacketToAllClients(new Packet02Disconnect(0, Packet02Disconnect.Cause.SERVER_CLOSED));
} catch (Exception e) {
e.printStackTrace();
}
running = false;
socket.close();
}
public static void out(Object... p) {
String timestamp = new SimpleDateFormat("'['HH:mm:ss']: '").format(new Date());
if (p.length == 1) System.out.println(timestamp + p[0]);
else System.out.println(timestamp + Arrays.toString(p));
}
public static void err(Object... p) {
String timestamp = new SimpleDateFormat("'['HH:mm:ss'] [ERROR]: '").format(new Date());
if (p.length == 1) System.err.print(timestamp + p[0]);
else System.err.print(timestamp + Arrays.toString(p));
}
}