/**
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/>.
**/
package de.creepsmash.server.game;
import java.awt.Point;
import java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Logger;
import de.creepsmash.common.IConstants;
import de.creepsmash.common.IConstants.ErrorType;
import de.creepsmash.common.messages.client.BuildCreepMessage;
import de.creepsmash.common.messages.client.BuildTowerMessage;
import de.creepsmash.common.messages.client.ChangeStrategyMessage;
import de.creepsmash.common.messages.client.ExitGameMessage;
import de.creepsmash.common.messages.client.GameMessage;
import de.creepsmash.common.messages.client.GameOverMessage;
import de.creepsmash.common.messages.client.KickPlayerRequestMessage;
import de.creepsmash.common.messages.client.LogoutMessage;
import de.creepsmash.common.messages.client.SellTowerMessage;
import de.creepsmash.common.messages.client.SendMessageMessage;
import de.creepsmash.common.messages.client.StartGameRequestMessage;
import de.creepsmash.common.messages.client.UpgradeTowerMessage;
import de.creepsmash.common.messages.server.BuildCreepRoundMessage;
import de.creepsmash.common.messages.server.BuildTowerRoundMessage;
import de.creepsmash.common.messages.server.ChangeStrategyRoundMessage;
import de.creepsmash.common.messages.server.RoundMessage;
import de.creepsmash.common.messages.server.SellTowerRoundMessage;
import de.creepsmash.common.messages.server.UpgradeTowerRoundMessage;
import de.creepsmash.server.Client;
import de.creepsmash.server.HighscoreService;
/**
* GameState for a game that's running (has started).
*/
public class RunningGameState extends GameState {
private int nextTowerId;
private long maxTick;
private Map<String, Integer> playerNamePositionMap;
private long startDate;
private static Logger logger = Logger.getLogger(RunningGameState.class);
/**
* Constructor.
* @param game the game. Must not be null.
* Must not be null or empty.
*/
public RunningGameState(Game game) {
super(game);
this.nextTowerId = 1;
this.maxTick = 0;
this.playerNamePositionMap = new HashMap<String, Integer>();
this.startDate = System.currentTimeMillis() / 1000;
}
/**
* Advance the maxTick counter and send a "ROUND n OK" message to all
* players.
*/
public void tick() {
if (this.maxTick % IConstants.USER_ACTION_DELAY == 0) {
RoundMessage message = new RoundMessage();
message.setRoundId(this.maxTick + IConstants.USER_ACTION_DELAY);
this.getGame().sendAll(message);
}
this.maxTick += 1;
}
/**
* Handle a message (from a client, presumably).
* @param message the message. Must not be null.
* @param sender the player who sent the message. Must not be null.
* @return the new state
*/
public GameState consume(GameMessage message, PlayerInGame sender) {
if (message == null) {
throw new IllegalArgumentException("'message' was null");
}
if (sender == null) {
throw new IllegalArgumentException("'sender' was null");
}
if (message instanceof StartGameRequestMessage
|| message instanceof KickPlayerRequestMessage) {
this.wrongStateForMessage(message, sender);
} else if (message instanceof BuildTowerMessage) {
this.handle((BuildTowerMessage) message);
} else if (message instanceof UpgradeTowerMessage) {
this.handle((UpgradeTowerMessage) message, sender);
} else if (message instanceof ChangeStrategyMessage) {
this.handle((ChangeStrategyMessage) message, sender);
} else if (message instanceof SellTowerMessage) {
this.handle((SellTowerMessage) message, sender);
} else if (message instanceof BuildCreepMessage) {
this.handle((BuildCreepMessage) message);
} else if (message instanceof GameOverMessage) {
return this.handle((GameOverMessage) message, sender);
} else if (message instanceof ExitGameMessage) {
return this.handle((ExitGameMessage) message);
} else if (message instanceof SendMessageMessage) {
this.handle((SendMessageMessage) message);
} else if (message instanceof LogoutMessage) {
return this.handle((LogoutMessage) message);
} else {
logger.error("cannot handle message: " + message);
}
return this;
}
private void handle(ChangeStrategyMessage message, PlayerInGame sender) {
int towerId = message.getTowerId();
// sanity checks on the id
Client client = sender.getClient();
if (towerId <= 0) {
client.handleError(ErrorType.Error,
"Invalid tower id (tried to change strategy " + towerId + ")");
return;
} else if (towerId >= this.nextTowerId) {
client.handleError(ErrorType.Error,
"No such tower (tried to change strategy " + towerId + ")");
return;
}
ChangeStrategyRoundMessage csm = new ChangeStrategyRoundMessage();
csm.setRoundId(this.maxTick + IConstants.USER_ACTION_DELAY);
csm.setPlayerId(message.getClientId());
csm.setTowerId(towerId);
csm.setStrategyType(message.getStrategyType());
csm.setLocked(message.isLocked());
this.getGame().sendAll(csm);
}
/**
* Handles the BuildTowerMessage.
* @param m the message
*/
private void handle(BuildTowerMessage m) {
String type = m.getTowerType();
Point position = m.getPosition();
BuildTowerRoundMessage n = new BuildTowerRoundMessage();
n.setRoundId(this.maxTick + IConstants.USER_ACTION_DELAY);
n.setPlayerId(m.getClientId());
n.setTowerType(type);
n.setTowerPosition(position);
n.setTowerId(this.nextTowerId++);
this.getGame().sendAll(n);
}
/**
* Handles the UpgradeTowerMessage.
* @param m the message.
* @param sender the player who sent the message.
*/
private void handle(UpgradeTowerMessage m, PlayerInGame sender) {
int towerId = m.getTowerId();
// sanity checks on the id
Client client = sender.getClient();
if (towerId <= 0) {
client.handleError(ErrorType.Error,
"Invalid tower id (tried to upgrade tower " + towerId + ")");
return;
} else if (towerId >= this.nextTowerId) {
client.handleError(ErrorType.Error,
"No such tower (tried to upgrade tower " + towerId + ")");
return;
}
UpgradeTowerRoundMessage n = new UpgradeTowerRoundMessage();
n.setRoundId(this.maxTick + IConstants.USER_ACTION_DELAY);
n.setPlayerId(m.getClientId());
n.setTowerId(towerId);
this.getGame().sendAll(n);
}
/**
* Handles the SellTowerMessage.
* @param m the message.
* @param sender the player who sent the message.
*/
private void handle(SellTowerMessage m, PlayerInGame sender) {
int towerId = m.getTowerId();
// sanity checks on the id
Client client = sender.getClient();
if (towerId <= 0) {
client.handleError(ErrorType.Error,
"Invalid tower id (tried to sell tower " + towerId + ")");
return;
} else if (towerId >= this.nextTowerId) {
client.handleError(ErrorType.Error,
"No such tower (tried to sell tower " + towerId + ")");
return;
}
SellTowerRoundMessage n = new SellTowerRoundMessage();
n.setRoundId(this.maxTick + IConstants.USER_ACTION_DELAY);
n.setPlayerId(m.getClientId());
n.setTowerId(towerId);
this.getGame().sendAll(n);
}
/**
* Handles the BuildCreepMessage.
* @param m the message
*/
private void handle(BuildCreepMessage m) {
String type = m.getCreepType();
int senderId = m.getClientId();
int recipientId =
this.getGame().getClients().succ(senderId).getClient().getClientID();
BuildCreepRoundMessage n = new BuildCreepRoundMessage();
n.setRoundId(this.maxTick + IConstants.USER_ACTION_DELAY);
n.setPlayerId(recipientId);
n.setCreepType(type);
n.setSenderId(senderId);
this.getGame().sendAll(n);
}
/**
* Handles the GameOverMessage.
* @param m the message
* @param sender the player who sent the message.
* @return the new state.
*/
private GameState handle(GameOverMessage m, PlayerInGame sender) {
int position = m.getPosition();
Client client = sender.getClient();
if (position < 1) {
client.handleError(ErrorType.Error, "position in GAME_OVER must be > 0");
return this;
}
this.playerNamePositionMap.put(client.getUserName(), position);
logger.info("game over for " + client + " (position: " + position + ")");
sender.gameOver();
if (gameOverForAll()) {
logger.info("saving scores: " + this.playerNamePositionMap);
(new HighscoreService()).createHighscoreEntry(this.playerNamePositionMap);
//save Game in DB
this.getGame().SaveToJournal(this.playerNamePositionMap, startDate);
return new EndedGameState(this.getGame());
} else {
return this;
}
}
/**
* Checks if all clients have sent GAME_OVER.
* @return true if all clients have sent a GAME_OVER message, false otherwise.
*/
private boolean gameOverForAll() {
for (PlayerInGame p : this.getGame().getClients()) {
if (!p.getGameOver()) {
return false;
}
}
return true;
}
/**
* Remove client from the game.
* @param client the client. Must not be null.
* @param reason a short string to be used in PLAYER_QUIT messages. Must not
* be null.
* @return the new state.
*/
protected GameState removeClient(Client client, String reason) {
// save score, if necessary
if (!this.playerNamePositionMap.containsKey(client.getUserName())) {
int position = getGame().getCurrentPlayers();
for (int p : this.playerNamePositionMap.values()) {
if (p >= position) {
position = p - 1;
}
}
this.playerNamePositionMap.put(client.getUserName(), position);
}
// let GameState.removeClient(..) do the rest of the work
return super.removeClient(client, reason);
}
/**
* Returns a string identifying this state.
* @return "running"
*/
@Override
public String toString() {
return "running";
}
}