Logo Search packages:      
Sourcecode: xblast-tnt version File versions  Download package

game_client.c

/*
 * file game_client.c - run game as client
 *
 * $Id: game_client.c,v 1.42 2005/03/12 14:51:29 lodott Exp $
 *
 * Program XBLAST
 * (C) by Oliver Vogel (e-mail: m.vogel@ndh.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 2; or (at your option)
 * any later version
 *
 * This program is distributed in the hope that it will be entertaining,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILTY 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, write to the Free Software Foundation, Inc.
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#include "game_client.h"

#include "atom.h"
#include "bomb.h"
#include "client.h"
#include "intro.h"
#include "demo.h"
#include "cfg_level.h"
#include "game.h"
#include "level.h"
#include "status.h"
#include "geom.h"
#include "bot.h"
#include "snd.h"

/*
 * local variables
 */
static CFGGame      clientGame;
static PlayerAction clientAction[MAX_PLAYER];
static XBBool       playerLinked[MAX_PLAYER];
static int          pa[MAX_PLAYER];
static int          teamActive;
static int          numActive;
static XBBool       autoPress;
static char         exitMsg[100];
/*
 * initialize linked players
 */
static void
InitPlayerLink (void)
{
  int i;
  assert (clientGame.players.num <= MAX_PLAYER);
  for (i = 0; i < clientGame.players.num; i ++) {
    playerLinked[i] = XBTrue;
  }
  for (; i < MAX_PLAYER; i ++) {
    playerLinked[i] = XBFalse;
  }
} /* InitPlayerLink */

/*
 * set unlinked active players to inactive
 */
static XBBool
UpdatePlayerLink (void)
{
  int    i;
  XBBool result = XBFalse;
  for (i = 0; i < clientGame.players.num; i ++) {
    if (! playerLinked[i] &&
      ! player_stat[i].in_active) {
      player_stat[i].in_active = XBTrue;
      player_stat[i].lives = 0;
      result = XBTrue;
    }
  }
  return result;
} /* UpdatePlayerLink */

/*
 * determine active players and counts
 */
static void
GetActivePlayers (int *pa, int *pl, int *tm)
{
  int i, reinco;
  *pl = 0;
  *tm = 0;
  reinco=0;
  for (i = 0; i < clientGame.players.num; i ++) {
    pa[i] = !player_stat[i].in_active;
    if (! player_stat[i].in_active) {
      *pl = *pl +1;
      if (clientGame.setup.teamMode) {
      if (! ( reinco & (1 << player_stat[i].team) ) ) {
        reinco|=1 << player_stat[i].team;
        *tm = *tm +1;;
      }
      } else {
      *tm = *tm +1;;
      }
    }
  }
  Dbg_Game("%i active player, %i active teams (%s mode)\n", *pl, *tm, clientGame.setup.teamMode ? "team" : "chaos");
} /* GetActivePlayers */

/*
 * wait for an event from server
 */
static XBBool
WaitForServerEvent (XBNetworkEvent waitEvent, XBBool needFlush)
{
  XBEventCode    xbEvent;
  XBEventData    eData;
  XBNetworkEvent netEvent;
  unsigned       id;
  XBPlayerHost   host;
  int            i;

  /* set timer */
  GUI_SetTimer (FRAME_TIME, XBTrue);
  /* no keys/mouse */
  GUI_SetKeyboardMode (KB_XBLAST);
  GUI_SetMouseMode (XBFalse);
  /* get events */
  while (1) {
    /* update window */
    GameUpdateWindow ();
    /* wait for xblast event */
    xbEvent = GUI_WaitEvent (&eData);
    switch (xbEvent) {
    case XBE_XBLAST:
      /* check for escape */
      if (eData.value == XBXK_EXIT) {
      return XBFalse;
      }
      break;
    case XBE_TIMER:
      /* try to flush udp connections */
      if (needFlush) {
      needFlush = Client_FlushPlayerAction ();
      }
      /* loop through all network events that accumulated */
      while (XBNW_None != (netEvent = Network_GetEvent (&id) ) ) {
      if (netEvent == waitEvent) {
        /* event waited for, return success*/
        return XBTrue;
      } else {
        /* otherwise check for disconnect */
        switch (netEvent) {
        case XBNW_Disconnected:
          if (id == 0 && waitEvent != XBNW_None) {
            /* server disconnected, but client still waiting, return failure */
            sprintf(exitMsg, "server disconnected");
            Dbg_Game("exit - %s\n", exitMsg);
            return XBFalse;
          } else {
            /* another client disconnected, unlike its players */
            Dbg_Game("client #%u disconnected\n", id);
            host = XBPH_Client1 + id - 1;
            for (i = 0; i < clientGame.players.num; i ++) {
            if (clientGame.players.host[i] == host) {
              playerLinked[i] = XBFalse;
              Dbg_Game ("unlink player %d\n", i);
            }
            }
          }
          break;
        case XBNW_Error:
          /* network error, return failure */
          sprintf(exitMsg, "network error to server");
          Dbg_Game("exit - %s\n", exitMsg);
          return XBFalse;
        default:
          break;
        }
      }
      /* network queue is now emptied, check if that was waited for */
      if (waitEvent == XBNW_None) {
        return XBTrue;
      }
      break;
      default:
      /* check for chat event */
      (void) Chat_Event(xbEvent, eData);
      break;
      }
    }
  }
  return XBFalse;
} /* WaitForServerEvent */

/*
 * send sync signal to server and wait for acknowledgement
 */
static XBBool
SyncWithServer (XBNetworkEvent syncEvent, XBBool needFlush, XBBool showMsg)
{
  Dbg_Game("waiting for others \n");
  if (showMsg) {
    SetMessage ("Waiting for others ...", XBTrue);
  }
  Client_SendSync (syncEvent);
  if (!WaitForServerEvent (syncEvent, XBFalse)) {
    return XBFalse;
  }
  return XBTrue;
} /* SyncWithServer */

/*
 * wait for level data from server
 */
static const DBRoot *
LevelFromServer (void)
{
  int cnt = 0;
  do {
    Dbg_Game("level negotiations running\n");
    if (! WaitForServerEvent (XBNW_LevelConfig, XBFalse) ) {
      Dbg_Game("%i level proposals processed\n", cnt/2);
      return NULL;
    }
    cnt++;
    if (!Client_FixedLevel()) {
      Dbg_Game("received level data from server\n");
      if (Client_RejectsLevel()) {
      Dbg_Game("rejecting level, resetting\n");
      ClearRemoteLevelConfig();
      } else {
      Dbg_Game("accepting level proposal from server\n");
      }
      Client_SendLevelCheck();
    }
  } while (!Client_FixedLevel());
  Dbg_Game("received level activation by server\n");
  if (Client_RejectsLevel()) {
    sprintf(exitMsg, "rejected activated level, protocol error!");
    Dbg_Game("exit - %s\n", exitMsg);
    return NULL;
  }
  Dbg_Game("level negotiations successful\n");
  return GetRemoteLevelConfig ();
} /* LevelFromServer */

/*
 * run a level as client
 */
static int
ClientRunLevel (int numActive, const DBRoot *level)
{
  int          gameTime;
  int          pauseStatus;
  int          lastTeam, winner;
  const char  *msg;
  XBEventData  data;
  /* sanity check */
  assert (NULL != level);
  /* necesary inits */
  winner      = -1;
  gameTime    = 0;
  pauseStatus = -1;
  lastTeam    = -1;
  /* start demo recording if requested */
  if (clientGame.setup.recordDemo) {
    DemoInitLevel (DB_Atom (level));
  }
  /* Config level */
  if (!ConfigLevel (level)) {
    Dbg_Game("level config failed!\n");
  }
  /* clear async flag */
  Client_SetLevelAsync(XBFalse);
  /* clear action array of client */
  Client_ClearPlayerAction ();
  Dbg_Game("cleared client game data\n");
  /* show level info */
  if (! LevelIntro (clientGame.players.num, level, autoPress ? -1 : clientGame.setup.infoTime)) {
    Dbg_Game("game aborted in level intro\n");
    goto Exit;
  }
  /* sync with server */
  Dbg_Game("syncing level-intro with server\n");
  if (!SyncWithServer (XBNW_SyncLevelIntro, XBFalse, XBTrue) ) {
    Dbg_Game("sync failed, disconnecting\n");
    goto Exit;
  }
  Dbg_Game("received level-intro sync\n");
  /* reset dgram socket after sync, so that early server data triggers no events */
  Client_ResetPlayerAction ();
  /* update active players/teams */
  UpdatePlayerLink();
  GetActivePlayers(pa,&numActive,&teamActive);
  /* show level map */
  LevelBegin (GetLevelName (level));
  /* set status bar to new level */
  ResetStatusBar (player_stat, GetLevelName (level), XBTrue);
  /* init events */
  GUI_SetTimer (0, XBFalse);
  GUI_SetKeyboardMode (KB_XBLAST);
  GUI_SetMouseMode (XBTrue);
  /* play music if requested */
  if (clientGame.setup.Music) {
    SND_Load (clientGame.setup.Music);
    SND_Play (clientGame.setup.Music, SOUND_MIDDLE_POSITION);
  }
  /* now start level */
  do {
    /* increment game clock */
    gameTime ++;
    Dbg_Action("----------\n");
    /* clear input data */
    ClearPlayerAction (clientAction);
  Wait:
    /* get events */
    Dbg_Action("waiting for server event at gt=%u\n", gameTime);
    if (! GameEventLoop (XBE_SERVER, &data) ) {
      Dbg_Game("game aborted in game event loop\n");
      goto Exit;
    }
    /* handle vents */
    switch ((XBServerEvent) data.value) {
    case XBSE_FINISH:
      Dbg_Action("server sent level finish\n");
      goto Finish;
    case XBSE_ERROR:
      sprintf(exitMsg, "server error received during level");
      Dbg_Game("exit - %s\n", exitMsg);
      goto Exit;
    default:
      /* show next frame */
      Dbg_Action ("received action for gt=%u, current=%u\n", data.value, gameTime);
      if (gameTime < data.value) {
      /* requeue event, for accelerated processing of intermediate game times */
      Dbg_Action("requeue %u\n", data.value);
      GUI_SendEventValue (XBE_SERVER, data.value);
      } else if (gameTime == data.value) {
      Dbg_Action("expected\n");
      } else {
      Dbg_Action("repeat\n");
      goto Wait;
      }
      break;
    }
    Dbg_Action("do game stuff for gt=%u\n",gameTime);
    /* bot */
    Player_BotAction(clientAction);
    /* handle game turn */
    GameTurn (gameTime, clientGame.players.num, &numActive);
    /* send own keys to server */
    Client_SendPlayerAction (gameTime, clientAction);
    /* insert keys from server */
    Client_GetPlayerAction (gameTime, clientAction);
    /* record demo if needed */
    if (clientGame.setup.recordDemo) {
      DemoRecordFrame (gameTime, clientAction);
    }
    /* evaluate input data */
    (void) GameEvalAction (clientGame.players.num, clientAction);
    /* update window */
    GameUpdateWindow ();
  } while (1);

 Finish:
  Dbg_Game("level ended\n");
  /* stop music if necessary */
  if (clientGame.setup.Music){
    SND_Stop(clientGame.setup.Music);
  }
  /* activate bot if client was idle */
  Player_CheckLocalAway();
  /* now update autoPress */
  autoPress = Player_CheckLocalBot();
  if (autoPress) {
    Dbg_Out("AUTOPRESS!!!\n");
  }
  /* send finish message */
  Dbg_Game("sending level finish\n");
  Client_FinishPlayerAction (gameTime);
  /* determine last team, don't store */
  LevelResult (gameTime, &lastTeam, clientGame.players.num, level, XBFalse);
  /* finish demo file if needed */
  if (clientGame.setup.recordDemo) {
    DemoFinishLevel (gameTime,winner, "c");
  }
  /* send winner team */
  Dbg_Game("sending winner team %u\n", lastTeam);
  Client_SendWinner(lastTeam);
  /* wait for server to send response */
  Dbg_Game("waiting for server level-result sync\n");
  if (!WaitForServerEvent (XBNW_SyncLevelResult, XBFalse)) {
    Dbg_Game("sync failed\n");
    goto Exit;
  }
  Dbg_Game("level-result sync received\n");
  /* check if async was responded */
  if (Client_LevelAsynced()) {
    Dbg_Game("level async received, ignoring result\n");
    GUI_ErrorMessage("Async level, making it a draw\n");
    lastTeam=MAX_PLAYER;
  } else {
    Dbg_Game("level was sync\n");
    /* now store result */
    msg = LevelResult (gameTime, &lastTeam, clientGame.players.num, level, XBTrue);
    /* show message and winner Animation */
    if (! LevelEnd (clientGame.players.num, lastTeam, msg, autoPress ? -1 : 1) ) {
      Dbg_Game("aborted game during winner animation\n");
      lastTeam = -1;
    }
  }
  /* clean up */
 Exit:
  /* stop music if necessary */
  if (clientGame.setup.Music){
    SND_Stop(clientGame.setup.Music);
  }
  DeleteAllExplosions ();
  FinishLevel ();
  /* fade out image */
  DoFade (XBFM_BLACK_OUT, PIXH+1);
  /* that's all */
  return lastTeam;
} /* ClientRunLevel */

/*
 * run game as client
 */
void
RunClientGame (XBPlayerHost hostType)
{
  const DBRoot  *level;
  int lastTeam;
  int i, maxNumWins;
  /* clear exit message */
  memset(exitMsg, 0, sizeof(exitMsg));
  /* get game configs */
  if (! RetrieveGame (CT_Remote, SERVERGAMECONFIG, &clientGame) ) {
    Dbg_Game("failed to get game config!\n");
    sprintf(exitMsg, "game config error");
    Dbg_Game("exit - %s\n", exitMsg);
    goto Disconnect;
  }
  /* common inits */
  if (! InitGame (hostType, CT_Remote, &clientGame, clientAction)) {
    sprintf(exitMsg, "game init error");
    Dbg_Game("exit - %s\n", exitMsg);
    goto Disconnect;
  }
  /* init local data */
  maxNumWins = 0;
  numActive  = 0;
  teamActive = 0;
  autoPress = Player_CheckLocalBot();
  /* init player link flags */
  InitPlayerLink ();
  /* clear level data for next run */
  ClearRemoteLevelConfig ();
  Client_LevelFix(XBFalse);
  Client_LevelRejection(XBTrue);
  /* beep once if requested */
  if (clientGame.setup.beep) {
    SND_Beep();
  }
  /* sync with server */
  Dbg_Game("waiting for server end-of-init sync\n");
  if (!SyncWithServer (XBNW_SyncEndOfInit, XBFalse, XBFalse) ) {
    Dbg_Game("sync failed\n");
    goto Exit;
  }
  Dbg_Game("end-of-init sync received\n");
  /* update active players/teams */
  UpdatePlayerLink ();
  GetActivePlayers(pa,&numActive,&teamActive);
  /* start the game */
  do {
    /* load and run next level */
    if (NULL == (level = LevelFromServer () ) ) {
      Dbg_Game("level negotiations failed, exiting\n");
      goto Exit;
    }
    /* update active players/teams */
    UpdatePlayerLink ();
    GetActivePlayers(pa,&numActive,&teamActive);
    /* run the level */
    Dbg_Game ("starting level\n");
    lastTeam = ClientRunLevel (teamActive, level);
    /* check for quick exit */
    if (-1 == lastTeam) {
      Dbg_Game("game aborted during level\n");
      goto Exit;
    }
    /* update current winner */
    for (i = 0; i < clientGame.players.num; i ++) {
      if (player_stat[i].victories > maxNumWins) {
      maxNumWins = player_stat[i].victories;
      }
    }
    /* sync before showing score board */
    Dbg_Game("waiting for server sync level-end\n");
    if (!SyncWithServer (XBNW_SyncLevelEnd, XBFalse, XBTrue) ) {
      Dbg_Game("sync failed!\n");
      goto Exit;
    }
    Dbg_Game("level-end sync received\n");
    /* show scores */
    if (! ShowScoreBoard (lastTeam, maxNumWins, clientGame.players.num, player_stat, autoPress ? -1 : 1)) {
      Dbg_Game("game aborted during scoreboard\n");
      goto Exit;
    }
    /* clear level data for next run, must be before scoreboard sync */
    ClearRemoteLevelConfig ();
    Client_LevelFix(XBFalse);
    Client_LevelRejection(XBTrue);
    Dbg_Game("reset level data, now waiting for server sync scoreboard\n");
    if (!SyncWithServer (XBNW_SyncScoreboard, XBFalse, XBTrue) ) {
      Dbg_Game("sync failed!\n");
      goto Exit;
    }
    /* determine active players/teams */
    UpdatePlayerLink();
    GetActivePlayers(pa,&numActive,&teamActive);
  } while (numActive > 1 && teamActive > 1 &&
         maxNumWins < clientGame.setup.numWins);
  Dbg_Game("game ended\n");
  SetMessage ("Waiting for server", XBTrue);
  /* show winner */
  InitWinner (clientGame.players.num);
  ShowWinner (lastTeam, clientGame.players.num, player_stat);
  /* wait till network queue is cleared */
  Dbg_Game("waiting for server to shutdown!\n");
  (void) WaitForServerEvent (XBNW_None, XBFalse);
 Exit:
  FinishGame (&clientGame);
 Disconnect:
  Client_Disconnect ();
  if (strlen(exitMsg)>0) {
    GUI_ErrorMessage(exitMsg);
  }
  return;
} /* RunClientGame */

/*
 * end of file game_client.c
 */

Generated by  Doxygen 1.6.0   Back to index