/**
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.client.game;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.TreeMap;
import java.util.logging.Logger;
import de.creepsmash.client.creep.Creep;
import de.creepsmash.client.creep.CreepFactory;
import de.creepsmash.client.game.GameContext.BoardLocation;
import de.creepsmash.client.network.MessageListener;
import de.creepsmash.client.network.Network;
import de.creepsmash.client.panel.GamePanel;
import de.creepsmash.client.sound.SoundManagement;
import de.creepsmash.client.tower.Tower;
import de.creepsmash.common.IConstants;
import de.creepsmash.common.messages.server.MessageMessage;
import de.creepsmash.common.messages.server.PlayerQuitMessage;
import de.creepsmash.common.messages.server.RoundMessage;
import de.creepsmash.common.messages.server.ServerMessage;
import de.creepsmash.common.messages.server.StartGameMessage;
/**
* Main GameLoop for updates and repaint. Sync with Server tick and framerate
* approximation.
*/
public class GameLoop extends Thread implements MessageListener, IConstants {
private static Logger logger = Logger.getLogger(GameLoop.class.getName());
private GameContext myContext;
private ArrayList<GameContext> contexts = new ArrayList<GameContext>();
//key PlayerID, value position
private TreeMap<Integer, Integer> playersOrder
= new TreeMap<Integer, Integer>();
private TreeMap<Integer, String> players = new TreeMap<Integer, String>();
private Network tdNetwork;
private GamePanel gamePanel;
private Map map;
private boolean running = false;
private static final int MAX_ROUNDS_BEHIND = IConstants.USER_ACTION_DELAY;
// one loop-cycle lasts TICK_INTERVAL time
public static final int TICK_INTERVAL = TICK_MS * 1000000; // Mil * 1000000
// If we are MAX_DELAY_BEHIND_MAXROUND, we'll stop
// sleeping every round and accelerate FPS *wuuhuuu*
public static final int MAX_DELAY_BEHIND_MAXROUND = USER_ACTION_DELAY + 40;
// we need this to measure the time
private static long nextTime;
private long roundID = 0;
private long maxRound = 0;
private int incomeCounter = 0;
private boolean gameOver;
private SoundManagement managementSound;
/**
* @return the gameOver
*/
public boolean isGameOver() {
return gameOver;
}
/**
* @param gameOver the gameOver to set
*/
public void setGameOver(boolean gameOver) {
this.gameOver = gameOver;
}
/**
* Creates a new GameLoop.
*
* @param gamePanel
* the gamePanel
* @param network
* the network instance
*/
public GameLoop(GamePanel gamePanel, Network network, SoundManagement soundM) {
this.gamePanel = gamePanel;
tdNetwork = network;
tdNetwork.addListener(this);
this.managementSound = soundM;
network.makeContact();
}
/**
* finds the PlayerContext.
*/
private void findMyContext() {
for (GameContext gc : contexts) {
if (gc instanceof PlayerContext) {
myContext = gc;
}
}
}
/**
* HelperMethod. How much time has left compared to nextTime
*
* @return how much time is left
*/
private long timeLeft() {
long now;
now = System.nanoTime();
if (nextTime <= now) {
return 0;
} else {
return nextTime - now;
}
}
/**
* some configurations before the gameloop starts.
*/
public void init() {
contextSetup();
findMyContext();
gamePanel.setSoundManagementObject(managementSound);
gamePanel.getCreepPanel().setContext(myContext);
gamePanel.getChatPanel().setContext(myContext);
gamePanel.getTowerPanel().setContext(myContext);
gamePanel.getBuildTowerInfoPanel().setContext(myContext);
gamePanel.getSelectTowerInfoPanel().setContext(myContext);
gamePanel.getCreepInfoPanel().setContext(myContext);
gamePanel.getNoInfoPanel().setContext(myContext);
gamePanel.setContext(myContext);
}
// 2nd GameLoop
/**
* Calls the update() and repaint() methods on GameContext.
*/
@Override
public void run() {
logger.info("GameLoop running...");
init();
nextTime = System.nanoTime() + TICK_INTERVAL;
running = true;
gamePanel.getQuit().setEnabled(true);
// The gameloop
while (running) {
if (roundID > maxRound) {
while (roundID > maxRound) {
try {
// logger.info("DBG: waiting for \"bigger\" maxRound");
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
nextTime = System.nanoTime() + TICK_INTERVAL;
}
gameUpdate(); // updates the gamestate
gameRender(); // paints new screen in a buffer
// draw buffer to screen
gamePanel.getBoardPanel().getStrategy().show();
logger.fine("update");
roundID++;
if (roundID + MAX_DELAY_BEHIND_MAXROUND < maxRound) {
Thread.yield();
nextTime = System.nanoTime();
} else if (timeLeft() != 0) {
logger.fine("NormalSleep " + timeLeft() / 1000000);
try {
Thread.sleep(timeLeft() / 1000000); // to millisec
} catch (InterruptedException e) {
e.printStackTrace();
}
}
while (maxRound - roundID > MAX_ROUNDS_BEHIND) {
// logger.info("Speeding up because we are to slow");
gameUpdate();
roundID++;
}
nextTime += TICK_INTERVAL;
}
// only painting when game is over
while (gameOver) {
gameRender();
gamePanel.getBoardPanel().getStrategy().show();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* Transfers the creeps from one Context to the next.
*/
public void transferCreeps() {
int size = contexts.size();
for (int i = 0; i < size; i++) {
ArrayList<Creep> transCopy = new ArrayList<Creep>(contexts.get(i)
.getTransfer());
GameContext copyTo = findNextContext(contexts.get(i));
// If some Playaer is dead, find other living Player
while (copyTo.isDead()) {
copyTo = findNextContext(copyTo);
}
for (Creep c : transCopy) {
Creep copy = CreepFactory.createCreep(copyTo, c.getType());
copy.setHealth(c.getHealth());
copy.setBuildTime(c.getBuildTime());
copy.setSenderId(c.getSenderId());
if (c.getSenderId() == copyTo.getPlayerId()) { // If sender and receiver the same Player
copyTo.getTransfer().add(copy);
} else {
copyTo.getCreeps().add(copy);
/********************************
* Here is calculating of taked *
* lives from each Player *
********************************/
for (int j = 0; j < size; j++) {
if (contexts.get(j).getPlayerId() == c.getSenderId()) {
contexts.get(j).takedlives++;
break;
}
}
}
contexts.get(i).getTransfer().remove(c);
}
}
}
/**
*
* find the next context.
* @param start startcontext
* @return found context
*/
private GameContext findNextContext(GameContext start) {
GameContext found = null;
BoardLocation loc = start.getLocation();
while (found == null) {
loc = getSuccessor(loc);
found = findContextByLocation(loc);
}
return found;
}
/**
* find a context by the location.
* @param loc the location
* @return found context
*/
private GameContext findContextByLocation(BoardLocation loc) {
for (GameContext gc : contexts) {
if (gc.getLocation().equals(loc)) {
return gc;
}
}
return null;
}
/**
* get the Successor.
* @param loc location
* @return location
*/
private BoardLocation getSuccessor(BoardLocation loc) {
switch(loc) {
case TOPLEFT:
return BoardLocation.TOPRIGHT;
case TOPRIGHT:
return BoardLocation.BOTTOMRIGHT;
case BOTTOMRIGHT:
return BoardLocation.BOTTOMLEFT;
case BOTTOMLEFT:
return BoardLocation.TOPLEFT;
default:
return null;
}
}
/**
* Instatiates the game context for all players.
*/
private void contextSetup() {
int cnt = 0;
GameContext.BoardLocation loc = null;
GameContext context = null;
GameContext.setWinningPosition(0);
for (Integer id : players.keySet()) {
switch (playersOrder.get(id)) {
case 0:
loc = GameContext.BoardLocation.TOPLEFT;
break;
case 1:
loc = GameContext.BoardLocation.TOPRIGHT;
break;
case 2:
loc = GameContext.BoardLocation.BOTTOMRIGHT;
break;
case 3:
loc = GameContext.BoardLocation.BOTTOMLEFT;
break;
default:
logger.warning("Creating context without location");
}
cnt++;
if (id == gamePanel.getCore().getPlayerId()) {
logger.info("own context created");
context = new PlayerContext(loc, tdNetwork, managementSound,
map);
gamePanel.getBoardPanel().addMouseMotionListener(
context.getGameBoard());
gamePanel.getBoardPanel().addMouseListener(
context.getGameBoard());
gamePanel.getGameInfoPanel().addPlayerContext(
(PlayerContext) context);
} else {
context = new OpponentContext(loc, tdNetwork, map);
gamePanel.getGameInfoPanel().addOpponentContext(
(OpponentContext) context);
}
context.setPlayerId(id);
context.setPlayerName(players.get(id));
contexts.add(context);
context.fireCreditsChangedEvent();
context.fireIncomeChangedEvent();
context.fireLivesChangedEvent();
}
}
/**
* Receive messages from the server.
*
* @param m
* the ServerMessage
*/
public void update(ServerMessage m) {
if (m instanceof StartGameMessage) {
if (!this.isAlive()) {
this.start();
}
} else if (m instanceof MessageMessage) {
MessageMessage mMChat = (MessageMessage) m;
gamePanel.getChatPanel().setMessage(mMChat.getPlayerName(),
mMChat.getMessage());
} else if (m instanceof PlayerQuitMessage) {
PlayerQuitMessage pqm = (PlayerQuitMessage) m;
for (GameContext con : contexts) {
if (pqm.getPlayerName().equals(con.getPlayerName())) {
con.setLives(0);
con.sendDeathMessage();
con.fireLivesChangedEvent();
}
}
}
if (m instanceof RoundMessage) {
maxRound = ((RoundMessage) m).getRoundId();
}
}
/**
* updates the income every INCOME_TIME.
*/
private void updateIncome() {
for (GameContext gc : contexts) {
gc.setCredits(gc.getCredits() + gc.getIncome());
}
logger.info("new income");
}
/**
* updates the gamestate.
*/
private void gameUpdate() {
if (gameOver) {
return;
}
int deadCount = 0;
transferCreeps();
for (GameContext gc : contexts) {
gc.update(roundID);
if (gc.isDead()) {
gc.sendDeathMessage();
deadCount++;
for (Tower t : gc.getTowers()) {
t.setCoolDownNow(t.getCoolDown() - 1);
}
}
}
// Game is finished at this point
// stop the thread.
if (deadCount >= contexts.size() - 1) {
if (!myContext.isDead()) {
// send gameover Message to the server
((PlayerContext) myContext).sendDeathMessage();
// set the context to dead so that no more
// user actions are allowed
myContext.sendDeathMessage();
}
gameOver = true;
this.running = false;
}
if (myContext.getStartCounter() >= 0) {
if (roundID % (1000 / TICK_MS) == 0) {
gameRender();
gamePanel.getBoardPanel().getStrategy().show();
myContext.setStartCounter(myContext.getStartCounter() - 1);
}
}
if (myContext.getStartCounter() < 0) {
// only do that if player is alive
if (!myContext.isDead()) {
// updates income everu INCOME_TIME millis
if (roundID % (INCOME_TIME / TICK_MS) == 0) {
updateIncome();
}
// countdown for next income
if (roundID % (1000 / TICK_MS) == 0) {
if (--incomeCounter < 1) {
// milis>>secs
incomeCounter = IConstants.INCOME_TIME / 1000;
}
gamePanel.getGameInfoPanel()
.setIncomeCounter(incomeCounter);
}
}
}
}
/**
* prepares the game for painting.
*/
private void gameRender() {
Graphics2D g2 = gamePanel.getBoardPanel().getImgGraphics();
for (GameContext gc : contexts) {
gc.paint(g2);
}
}
/**
* @return the players
*/
public TreeMap<Integer, String> getPlayers() {
return players;
}
/**
* @param players
* the players to set
*/
public void setPlayers(TreeMap<Integer, String> players) {
this.players = players;
}
/**
* @return the map
*/
public Map getMap() {
return map;
}
/**
* @param map
* the map to set
*/
public void setMap(Map map) {
this.map = map;
}
/**
* @return the running
*/
public boolean isRunning() {
return running;
}
/**
* @param running
* the running to set
*/
public void setRunning(boolean running) {
this.running = running;
}
/**
* @return the playersOrder
*/
public TreeMap<Integer, Integer> getPlayersOrder() {
return playersOrder;
}
/**
* @param playersOrder the playersOrder to set
*/
public void setPlayersOrder(TreeMap<Integer, Integer> playersOrder) {
this.playersOrder = playersOrder;
}
}