/**
Creep Smash, a multiplayer towerdefence game
created as a project at the Hochschule fuer
Technik Stuttgart (University of Applied Science)
http://www.hft-stuttgart.de
Copyright (C) 2008 by
* Andreas Wittig
* Bernd Hietler
* Christoph Fritz
* Fabian Kessel
* Levin Fritz
* Nikolaj Langner
* Philipp Schulte-Hubbert
* Robert Rapczynski
* Ron Trautsch
* Sven Supper
http://creepsmash.sf.net/
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
history:
- initial version by hft-stuttgart (see above)
- 2008-2009, changed by creepsmash.de team
- Jun 2009, updated by ch_f for creepsmash.de
**/
package de.creepsmash.server;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.log4j.Logger;
import de.creepsmash.common.IConstants;
import de.creepsmash.common.messages.client.BuildTowerMessage;
import de.creepsmash.common.messages.client.ClientMessage;
import de.creepsmash.common.messages.client.SellTowerMessage;
import de.creepsmash.common.messages.client.UpgradeTowerMessage;
import de.creepsmash.common.messages.server.ErrorMessage;
import de.creepsmash.common.messages.server.ServerMessage;
/**
* This Class represents the instance of a client inside the server-package.
* Every client has a ClientID, is in a GameState and is able to send or to
* receive Messages.
* Adds also calculation of the clients current money that can be used for
* cheating detection.
*
*/
public class Client {
private int clientID;
private ClientState clientState;
private ClientInThread clientInThread;
private QueueConsumerThread<ServerMessage> clientOutThread;
private String userName;
private Socket socket;
private String mac;
private int anticheatCurrentMoney;
private int anticheatCurrentIncome;
private int anticheatTicksPerIncomeInterval;
private long anticheatLastTickWhereHappendSomething;
List<String> anticheatTowerId = new ArrayList<String>();
private static Logger clientLogger = Logger.getLogger(Client.class);
/**
* Constructor for TestClient, do not use otherwise.
*/
protected Client() {
}
/**
* Constructor ...
*
* @param id
* the client's ID is a simple integer.
* @param socket
* the client's socket is a simple client-socket which is used to
* connect to the server.
* @param authenticationService
* the AuthenticationService
*/
public Client(int id, Socket socket,
AuthenticationService authenticationService) {
this.anticheatCurrentMoney = 0;
this.anticheatCurrentIncome = IConstants.CREDITS;
this.anticheatTicksPerIncomeInterval = IConstants.INCOME_TIME / IConstants.TICK_MS;
this.anticheatLastTickWhereHappendSomething = 0;
this.clientID = id;
this.socket = socket;
try {
OutTranslator outTranslator = new OutTranslator(socket
.getOutputStream(), id);
BlockingQueue<QueueMessage<ServerMessage>> outQueue =
new LinkedBlockingQueue<QueueMessage<ServerMessage>>();
this.clientOutThread = new QueueConsumerThread<ServerMessage>(outTranslator, outQueue);
this.clientOutThread.start();
this.clientInThread = new ClientInThread(socket.getInputStream(), this);
this.clientInThread.start();
this.clientState = new AnonymousState(outQueue, this, authenticationService);
} catch (IOException e) {
clientLogger.error(
"Problem with Client constructor in serverpacket.", e);
}
}
/**
* Receive a message from the client (over the network).
*
* @param message
* the message, or null if the connection to the client has been
* closed.
*/
public void receive(ClientMessage message) {
if (message == null) {
this.clientState = this.clientState.receiveMessage(null);
this.clientOutThread.interrupt();
try {
this.socket.close();
} catch (IOException e) {
String socketState;
if (socket.isClosed()) {
socketState = "closed";
} else {
socketState = "open";
}
clientLogger.error("Disconnect client" + this.getClientID()
+ this.getUserName() + "failed" + "socket is "
+ socketState, e);
}
} else {
try {
message.setClientId(this.getClientID());
this.clientState = this.clientState.receiveMessage(message);
} catch (NullPointerException e) {
clientLogger.fatal(
"Failed to receice message. Clientstate is: "
+ clientState + " message is: "
+ message.getMessageString(), e);
}
}
}
/**
* Sends an object of ServerMessage to the outgoing queue.
*
* @param message
* Expects an ServerMessage object.
*/
public void send(ServerMessage message) {
this.clientState = this.clientState.sendMessage(message);
}
/**
* Instantiates the ErrorHandler for handling Errors occurring in the
* Client/Server Communication and sends the ErrorMessage.
*
* @param type
* the type
* @param msg
* the message text
*/
public void handleError(IConstants.ErrorType type, String msg) {
ErrorMessage errMsg = new ErrorMessage(type, msg);
this.send(errMsg);
clientLogger.error(msg);
}
public void disconnect(){
try {
this.clientInThread.terminate();
this.socket.close();
//this.clientInThread.terminate();
//this.clientOutThread.interrupt();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Shutdown the server (only called from the ShutdownTD pseudo-client).
*/
public void stopServer() {
if (this.socket.getInetAddress().equals(this.socket.getLocalAddress())) {
this.handleError(IConstants.ErrorType.Error,
"Server is shutting down. ");
System.exit(0);
} else {
clientLogger
.error("Somebody tried to shutdown server form a remote host!");
}
}
/**
* Returns a string with id and userName.
*
* @return a string with id and userName.
*/
@Override
public String toString() {
return this.clientID + "/" + this.userName;
}
/**
* Returns the client's ID.
*
* @return clientID
*/
public int getClientID() {
return this.clientID;
}
/**
* Sets the users name for the client.
*
* @param name
* Expects a String with the users name.
*/
public void setUserName(String name) {
this.userName = name;
}
/**
* Returns the users name.
*
* @return String users name
*/
public String getUserName() {
return this.userName;
}
/**
* Check the Permission, modulo n function
*
* @return String users name
*/
public boolean getUserPermissionFor(int Permission) {
int modulo = 0;
modulo = AuthenticationService.getPlayer(this.getUserName()).getPermission() & Permission;
if (modulo == 0){
return false;
}else{
return true;
}
}
/**
* Wenn users socket closed ist,
* dann Killt alle Threads und liefert
* false zuruck, sonst true
*
* @return boolean
*/
public boolean check() {
if (this.socket.isClosed() ||
!this.clientInThread.isAlive() ||
!this.clientOutThread.isAlive()) {
try {
this.socket.close();
this.clientInThread.terminate();
this.clientOutThread.interrupt();
} catch(Exception e) {
}
return false;
}
return true;
}
public String getIPAddress() {
return this.socket.getInetAddress().toString().replaceAll("/", "");
}
/**
* @return the mac
*/
public String getMACAddress() {
return mac;
}
/**
* @param mac the mac to set
*/
public void setMACAddress(String mac) {
this.mac = mac;
}
/*
* Anticheat stuff starts here,
* could be used in the handlers of RunningGameState
*/
/**
* Get the clients calculated current money.
*
* The server doesn't get a message if a creep walks through a players
* game context (map). So it's purposed that all incoming creeps are getting killed.
* This leads to a error.
*
* So bear in mind that you have to add for example 2* IConstants.LIVE * bounty of the
* best creeps sent * the number of alive opponents * 2. So you can be sure that if
* this player gets over this amount of money, he's definitely cheating!
*
* This is a ugly approximation!
*
* Possible solution:
* - Every client sends a message if a creep crosses the context (map)
* - Calculate the game on the server-side
*
* If this return value plus tolerance is less than zero,
* this client is pretty sure cheating!
*
* @param atTick the current tick-time
* @return money the client has at this tick-time (with error!)
*
*/
public int anticheat_getNotExaktCurrentMoney(long atTick) {
anticheat_updateMoney(atTick);
return anticheatCurrentMoney;
}
/**
* Update money of this client.
*
* @param tick the current tick-time
*/
private void anticheat_updateMoney(long tick) {
int numberOfHappenedRounds = (int)(((tick /*+ IConstants.USER_ACTION_DELAY*/) - this.anticheatLastTickWhereHappendSomething)
/ IConstants.INCOME_TIME);
this.anticheatLastTickWhereHappendSomething = tick;
this.anticheatCurrentMoney += this.anticheatCurrentIncome * numberOfHappenedRounds;
}
/**
* Handles towers being built.
*
* Creates a new tower by id in a list. So later the tower can be
* upgraded or sold and we know the price.
* It also decreases the currentMoney Value.
*
* @param m BuildTowerMessage to extract type of tower
* @param id Identification Number of the new generated tower
*
*/
public void anticheat_TowerBuilt(BuildTowerMessage m, int id) {
anticheatTowerId.add(id, IConstants.Towers.valueOf(IConstants.Towers.class, m.getTowerType()).getName());
anticheatCurrentMoney -= IConstants.Towers.valueOf(IConstants.Towers.class, anticheatTowerId.get(id)).getPrice();
clientLogger.warn(anticheatCurrentMoney);
}
/**
* Handles towers being sold.
*
* @param m SellTowerMessage
*
*/
public void anticheat_TowerSold(SellTowerMessage m) {
anticheatCurrentMoney -= (int) IConstants.Towers.valueOf
(
IConstants.Towers.class,
anticheatTowerId.get(m.getTowerId())
).getPrice() * 0.75;
}
/**
* Handles towers being upgraded.
*
* @param m UpgradeTowerMessage
*/
public void anticheat_TowerUpgraded(UpgradeTowerMessage m) {
String nameOfNextTower = IConstants.Towers.valueOf(
IConstants.Towers.class, anticheatTowerId.get(m.getTowerId()))
.getNext().getName();
anticheatTowerId.set(m.getTowerId(), nameOfNextTower );
anticheatCurrentMoney -= IConstants.Towers.valueOf(
IConstants.Towers.class, anticheatTowerId.get(m.getTowerId()))
.getNext().getPrice();
}
/**
* Handles creeps this client sent.
*
* It decreases the anticheatCurrentMoney with the price of the creep
* from this client and increases anticheatCurrentIncome according to the
* type of creep.
*
* @param creepType
*/
public void anticheat_sentThisCreep(String creepType, long atTick) {
anticheat_updateMoney(atTick);
anticheatCurrentMoney -= IConstants.Creeps.valueOf(IConstants.Creeps.class, creepType).getPrice();
anticheatCurrentIncome += IConstants.Creeps.valueOf(IConstants.Creeps.class, creepType).getIncome();
}
/**
* Handles received creeps.
*
* Bear in mind that this function is called for the right client!
*
* Every smashed creep gives money.
*
* This increases the anticheatCurrentMoney according to the
* type of creep. It is purposed that the client always kills all enemies
* an gets the bounty of it. Because the server doesn't know when a creep walks through the
* client's game context (map).
* This leads to a small error in the calculation.
*
* @param creepType
*/
public void anticheat_receivedThisCreep(String creepType) {
this.anticheatCurrentMoney = IConstants.Creeps.valueOf(IConstants.Creeps.class, creepType).getBounty();
}
}