/* Copyright (c) 2002-2012 Croteam Ltd. 
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as published by
the Free Software Foundation


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, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */

#include "stdh.h"

#include <Engine/Build.h>
#include <Engine/Network/Network.h>
#include <Engine/Network/Server.h>
#include <Engine/Network/NetworkMessage.h>
#include <Engine/Network/Diff.h>
#include <Engine/Base/ErrorTable.h>
#include <Engine/Base/Translation.h>
#include <Engine/Base/ProgressHook.h>
#include <Engine/Base/CRCTable.h>
#include <Engine/Base/Shell.h>
#include <Engine/Network/SessionState.h>
#include <Engine/Network/PlayerSource.h>
#include <Engine/Entities/EntityClass.h>
#include <Engine/Math/Float.h>
#include <Engine/Network/PlayerTarget.h>
#include <Engine/Network/NetworkProfile.h>
#include <Engine/World/PhysicsProfile.h>
#include <Engine/Network/CommunicationInterface.h>
#include <Engine/Network/Compression.h>
#include <Engine/Entities/InternalClasses.h>
#include <Engine/Base/Console.h>
#include <Engine/Entities/EntityProperties.h>
#include <Engine/Network/LevelChange.h>

#include <Engine/Templates/DynamicContainer.cpp>
#include <Engine/Templates/StaticArray.cpp>
#include <Engine/Base/ListIterator.inl>
#include <Engine/Base/CRC.h>

#define SESSIONSTATEVERSION_OLD 1
#define SESSIONSTATEVERSION_WITHBULLETTIME 2
#define SESSIONSTATEVERSION_CURRENT SESSIONSTATEVERSION_WITHBULLETTIME

//#define DEBUG_LERPING 1

extern INDEX net_bLerping;
extern FLOAT net_tmConnectionTimeout;
extern FLOAT net_tmProblemsTimeOut;
extern FLOAT net_tmDisconnectTimeout;

// this is from ProgresHook.cpp - so we tell the progresshook to run client/srever updates
extern BOOL _bRunNetUpdates;

#if DEBUG_LERPING

FLOAT avfStats[1000][4];
INDEX ctCounter=0;
INDEX ctMax = sizeof(avfStats)/sizeof(avfStats[0]);

#endif // DEBUG_LERPING

// get a pseudo-random number (safe for network gaming)
ULONG CSessionState::Rnd(void) {
  ASSERTMSG(ses_bAllowRandom, "Use RND only in entity AI!");
  // NOTE: 
  // using multiplicative congruent method with Greanberger's lambda = 2^18+3
  ses_ulRandomSeed = ses_ulRandomSeed*262147;
  ASSERT(ses_ulRandomSeed!=0);
  return ses_ulRandomSeed;
}

// reset random number generator (always randomizes to same sequence!)
void CSessionState::ResetRND(void)
{
  BOOL bOldAllow = ses_bAllowRandom;
  ses_bAllowRandom = TRUE;
  // random must start at a number different than zero!
  ses_ulRandomSeed = 0x87654321;
  // run rnd a few times to make it go random
  for(INDEX i=0; i<32; i++) {
    Rnd();
  }
  ses_bAllowRandom = bOldAllow;
}

/*
 * Constructor.
 */
CSessionState::CSessionState(void)
{
  ses_bKeepingUpWithTime = TRUE;
  ses_tmLastUpdated = -100;
  ses_bAllowRandom = TRUE;  // random allowed when not in game
  ses_bPredicting = FALSE;
  ses_tmPredictionHeadTick = -2.0f;
  ses_tmLastSyncCheck = 0;
  ses_tmLastPredictionProcessed = -200;

  ses_bPause = FALSE;
  ses_bWantPause = FALSE;
  ses_bGameFinished = FALSE;
  ses_bWaitingForServer = FALSE;
  ses_strDisconnected = "";
  ses_ctMaxPlayers = 1;
  ses_bWaitAllPlayers = FALSE;
  ses_iLevel = 0;
  ses_fRealTimeFactor = 1.0f;

  ses_pstrm = NULL;
  // reset random number generator
  ResetRND();

  ses_apltPlayers.New(NET_MAXGAMEPLAYERS);
}

/*
 * Destructor.
 */
CSessionState::~CSessionState()
{
}

/*
 * Clear the object.
 */
void CSessionState::Stop(void)
{
#if DEBUG_SYNCSTREAMDUMPING
  ClearDumpStream();
#endif
#if DEBUG_SYNCSTREAMDUMPING
#endif

  ses_bKeepingUpWithTime = TRUE;
  ses_tmLastUpdated = -100;
  ses_bAllowRandom = TRUE;  // random allowed when not in game
  ses_bPredicting = FALSE;
  ses_tmPredictionHeadTick = -2.0f;
  ses_tmLastSyncCheck = 0;
  ses_bPause = FALSE;
  ses_bWantPause = FALSE;
  ses_bGameFinished = FALSE;
  ses_bWaitingForServer = FALSE;
  ses_strDisconnected = "";
  ses_ctMaxPlayers = 1;
  ses_fRealTimeFactor = 1.0f;
  ses_bWaitAllPlayers = FALSE;
  ses_apeEvents.PopAll();

  // disable lerping
  _pTimer->DisableLerp();

#if DEBUG_LERPING

  CTFileStream f;
  f.Create_t(CTFILENAME("Temp\\Lerp.stats"), CTStream::CM_TEXT);
  for (INDEX i=0; i<ctCounter; i++) {
    char strBuffer[256];
    sprintf(strBuffer, "%6.2f %6.2f %6.2f %6.2f",
      avfStats[i][0],
      avfStats[i][1],
      avfStats[i][2],
      avfStats[i][3]);
    f.PutLine_t(strBuffer);
  }
  f.Close();

#endif // DEBUG_LERPING

	CNetworkMessage nmConfirmDisconnect(MSG_REP_DISCONNECTED);
  if (_cmiComm.cci_bClientInitialized) {
	  _pNetwork->SendToServerReliable(nmConfirmDisconnect);
  }
  _cmiComm.Client_Close();

  // clear all old levels
  ForgetOldLevels();

  ses_apltPlayers.Clear();
  ses_apltPlayers.New(NET_MAXGAMEPLAYERS);
}

/*
 * Initialize session state and wait for game to be started.
 */
void CSessionState::Start_t(INDEX ctLocalPlayers) 
{
  ses_bKeepingUpWithTime = TRUE;
  ses_tmLastUpdated = -100;
  // clear message stream
  ses_nsGameStream.Clear();
  ses_bAllowRandom = FALSE;  // random not allowed in game
  ses_bPredicting = FALSE;
  ses_tmPredictionHeadTick = -2.0f;
  ses_tmLastSyncCheck = 0;
  ses_bPause = FALSE;
  ses_bWantPause = FALSE;
  ses_bWaitingForServer = FALSE;
  ses_bGameFinished = FALSE;
  ses_strDisconnected = "";
  ses_ctMaxPlayers = 1;
  ses_fRealTimeFactor = 1.0f;
  ses_bWaitAllPlayers = FALSE;
  ses_iMissingSequence = -1;
  ses_apeEvents.PopAll();
  ses_tvMessageReceived.Clear();
  _pNetwork->ga_strRequiredMod = "";

  // reset random number generator
  ResetRND();

  // clear all old levels
  ForgetOldLevels();

#if DEBUG_LERPING
  // clear lerp stats
  ctCounter = 0;
#endif // DEBUG_LERPING

  // ses_LastProcessedTick and ses_LastReceivedTick tick counters are
  // irrelevant now, will be initialized when initialization message
  // from server is received, no need to set them here

  // if this computer is server
  if (_pNetwork->IsServer()) {
    // initialize local client
    _cmiComm.Client_Init_t(0UL);
    // connect as main session state
    try {
      Start_AtServer_t();
    } catch(char *) {
      _cmiComm.Client_Close();
      throw;
    }

  // if this computer is client
  } else {
    // connect client to server computer
    _cmiComm.Client_Init_t((char*)(const char*)_pNetwork->ga_strServerAddress);
    // connect as remote session state
    try {
      Start_AtClient_t(ctLocalPlayers);
    } catch(char *) {
      // if failed due to wrong mod
      if (strncmp(ses_strDisconnected, "MOD:", 4)==0) {
        // remember the mod
        _pNetwork->ga_strRequiredMod = ses_strDisconnected+4;
        // make sure that the string is never empty
        if (_pNetwork->ga_strRequiredMod=="") {
          _pNetwork->ga_strRequiredMod=" ";
        }
      }
      _cmiComm.Client_Close();
      throw;
    }
  }
}

void CSessionState::Start_AtServer_t(void)     // throw char *
{
  // send registration request
  CNetworkMessage nmRegisterMainSessionState(MSG_REQ_CONNECTLOCALSESSIONSTATE);
  ses_sspParams.Update();
  nmRegisterMainSessionState<<ses_sspParams;
  _pNetwork->SendToServerReliable(nmRegisterMainSessionState);

  for(TIME tmWait=0; tmWait<net_tmConnectionTimeout*1000; 
      Sleep(NET_WAITMESSAGE_DELAY), tmWait+=NET_WAITMESSAGE_DELAY) {
    _pNetwork->TimerLoop();
    if (_cmiComm.Client_Update() == FALSE) {
			break;
		}

    // wait for message to come
    CNetworkMessage nmReceived;
    if (!_pNetwork->ReceiveFromServerReliable(nmReceived)) {
      continue;
    }
    // if this is the init message
    if (nmReceived.GetType() == MSG_REP_CONNECTLOCALSESSIONSTATE) {
      // just adjust your tick counters
      nmReceived>>ses_tmLastProcessedTick;
      nmReceived>>ses_iLastProcessedSequence;
      ses_tmInitializationTick  = -1.0f;
      ses_tmInitializationTick2 = -1.0f;
      // finish waiting
      return;
    // otherwise
    } else {
      // it is invalid message
      ThrowF_t(TRANS("Invalid message while waiting for server session registration"));
    }

    // if client is disconnected
    if (!_cmiComm.Client_IsConnected()) {
      // quit
      ThrowF_t(TRANS("Client disconnected"));
    }
  }
  ThrowF_t(TRANS("Timeout while waiting for server session registration"));
}

void CSessionState::Start_AtClient_t(INDEX ctLocalPlayers)     // throw char *
{
  // send one unreliable packet to server to make the connection up and running
  CNetworkMessage nmKeepAlive(MSG_KEEPALIVE);
  _pNetwork->SendToServer(nmKeepAlive);

  // send registration request
  CNetworkMessage nmRegisterSessionState(MSG_REQ_CONNECTREMOTESESSIONSTATE);
  nmRegisterSessionState<<INDEX('VTAG')<<INDEX(_SE_BUILD_MAJOR)<<INDEX(_SE_BUILD_MINOR);
  nmRegisterSessionState<<_strModName;
  extern CTString net_strConnectPassword;
  extern CTString net_strVIPPassword;
  CTString strPasw = net_strConnectPassword;
  if (strPasw=="") {
    strPasw = net_strVIPPassword;
  }
  nmRegisterSessionState<<strPasw;
  nmRegisterSessionState<<ctLocalPlayers;
  ses_sspParams.Update();
  nmRegisterSessionState<<ses_sspParams;
  _pNetwork->SendToServerReliable(nmRegisterSessionState);

  // prepare file or memory stream for state
  CTFileStream strmStateFile; CTMemoryStream strmStateMem;
  CTStream *pstrmState;
  extern INDEX net_bDumpConnectionInfo;
  if (net_bDumpConnectionInfo) {
    strmStateFile.Create_t(CTString("Temp\\DefaultState.bin"));
    pstrmState = &strmStateFile;
  } else {
    pstrmState = &strmStateMem;
  }

  {
    // wait for server's response
    CTMemoryStream strmMessage;
    WaitStream_t(strmMessage, "reply", MSG_REP_CONNECTREMOTESESSIONSTATE);
    // get motd
    strmMessage>>ses_strMOTD;
    // get info for creating default state
    CTFileName fnmWorld;
    strmMessage>>fnmWorld;
    ULONG ulSpawnFlags;
    strmMessage>>ulSpawnFlags;
    UBYTE aubProperties[NET_MAXSESSIONPROPERTIES];
    strmMessage.Read_t(aubProperties, NET_MAXSESSIONPROPERTIES);
    // create default state
    NET_MakeDefaultState_t(fnmWorld, ulSpawnFlags, aubProperties, *pstrmState);
    pstrmState->SetPos_t(0);
  }

  // send one unreliable packet to server to make the connection up and running
  {CNetworkMessage nmKeepAlive(MSG_KEEPALIVE);
  _pNetwork->SendToServer(nmKeepAlive); }
  // send data request
  CPrintF(TRANS("Sending statedelta request\n"));
  CNetworkMessage nmRequestDelta(MSG_REQ_STATEDELTA);
  _pNetwork->SendToServerReliable(nmRequestDelta);

  {
    // wait for server's response
    CTMemoryStream strmMessage;
    WaitStream_t(strmMessage, "data", MSG_REP_STATEDELTA);
    // decompress saved session state
    CTMemoryStream strmDelta;
    CzlibCompressor comp;
    comp.UnpackStream_t(strmMessage, strmDelta);
    CTMemoryStream strmNew;
    DIFF_Undiff_t(pstrmState, &strmDelta, &strmNew);
    strmNew.SetPos_t(0);

    // read the initialization information from the stream
    Read_t(&strmNew);
    ses_tmInitializationTick  = -1.0f;
    ses_tmInitializationTick2 = -1.0f;
  }

  // send one unreliable packet to server to make the connection up and running
  {CNetworkMessage nmKeepAlive(MSG_KEEPALIVE);
  _pNetwork->SendToServer(nmKeepAlive); }

  CPrintF(TRANS("Sending CRC request\n"));
  // send data request
  CNetworkMessage nmRequestCRC(MSG_REQ_CRCLIST);
  _pNetwork->SendToServerReliable(nmRequestCRC);

  {
    // wait for CRC challenge
    CTMemoryStream strmMessage;
    WaitStream_t(strmMessage, "CRC", MSG_REQ_CRCCHECK);

    // make response
    CNetworkMessage nmCRC(MSG_REP_CRCCHECK);
    nmCRC<<CRCT_MakeCRCForFiles_t(strmMessage)<<ses_iLastProcessedSequence;
    _pNetwork->SendToServerReliable(nmCRC);
  }

  // send one unreliable packet to server to make the connection up and running
  {CNetworkMessage nmKeepAlive(MSG_KEEPALIVE);
  _pNetwork->SendToServer(nmKeepAlive); }
}

// notify entities of level change
void CSessionState::SendLevelChangeNotification(CEntityEvent &ee)
{
  // for each entity in the world
  {FOREACHINDYNAMICCONTAINER(_pNetwork->ga_World.wo_cenEntities, CEntity, iten) {
    // if it should be notified
    if (iten->en_ulFlags&ENF_NOTIFYLEVELCHANGE) {
      // send the event
      iten->SendEvent(ee);
    }
  }}
}

// wait for a stream to come from server
void CSessionState::WaitStream_t(CTMemoryStream &strmMessage, const CTString &strName, 
                                 INDEX iMsgCode)
{

  // start waiting for server's response
  SetProgressDescription(TRANS("waiting for ")+strName);
  CallProgressHook_t(0.0f);
  SLONG slReceivedLast = 0;

  // yes, we need the client/server updates in the progres hook
  _bRunNetUpdates = TRUE;
  // repeat until timed out
  for(TIME tmWait=0; tmWait<net_tmConnectionTimeout*1000;
    Sleep(NET_WAITMESSAGE_DELAY), tmWait+=NET_WAITMESSAGE_DELAY) {
    // update network connection sockets
    if (_cmiComm.Client_Update() == FALSE) {
			break;
		}

    // check how much is received so far
    SLONG slExpectedSize; // slReceivedSoFar;
    SLONG slReceivedSize;
    _cmiComm.Client_PeekSize_Reliable(slExpectedSize,slReceivedSize);
    // if nothing received yet
    if (slExpectedSize==0) {
      // progress with waiting
      CallProgressHook_t(tmWait/(net_tmConnectionTimeout*1000));
    // if something received
    } else {
      // if some new data received
      if (slReceivedSize!=slReceivedLast) {
        slReceivedLast = slReceivedSize;
        // reset timeout
        tmWait=0;
      }
      // progress with receiving
      SetProgressDescription(TRANS("receiving ")+strName+"  ");
      CallProgressHook_t((float)slReceivedSize/slExpectedSize);
    }

    // if not everything received yet
    if (!_pNetwork->ReceiveFromServerReliable(strmMessage)) {
      // continue waiting
      continue;
    }
    // read message identifier
    strmMessage.SetPos_t(0);
    INDEX iID;
    strmMessage>>iID;

    // if this is the message
    if (iID == iMsgCode) {
      // all ok
      CallProgressHook_t(1.0f);
      // no more client/server updates in the progres hook
      _bRunNetUpdates = TRUE;
      return;
    // if disconnected
    } else if (iID == MSG_INF_DISCONNECTED) {
			// confirm disconnect
			CNetworkMessage nmConfirmDisconnect(MSG_REP_DISCONNECTED);			
			_pNetwork->SendToServerReliable(nmConfirmDisconnect);
      // report the reason
      CTString strReason;
      strmMessage>>strReason;
      ses_strDisconnected = strReason;
      // no more client/server updates in the progres hook
      _bRunNetUpdates = FALSE;
      ThrowF_t(TRANS("Disconnected: %s\n"), strReason);
	  // otherwise
    } else {
      // no more client/server updates in the progres hook
      _bRunNetUpdates = FALSE;
      // it is invalid message
      ThrowF_t(TRANS("Invalid stream while waiting for %s"), strName);
    }

    // if client is disconnected
    if (!_cmiComm.Client_IsConnected()) {
      // no more client/server updates in the progres hook
      _bRunNetUpdates = FALSE;
      // quit
      ThrowF_t(TRANS("Client disconnected"));
    }
  }

  // no more client/server updates in the progres hook
  _bRunNetUpdates = FALSE;

//	CNetworkMessage nmConfirmDisconnect(MSG_REP_DISCONNECTED);			
//	_pNetwork->SendToServerReliable(nmConfirmDisconnect);

  
  ThrowF_t(TRANS("Timeout while waiting for %s"), strName);
}

// check if disconnected
BOOL CSessionState::IsDisconnected(void)
{
  return ses_strDisconnected!="";
}

// print an incoming chat message to console
void CSessionState::PrintChatMessage(ULONG ulFrom, const CTString &strFrom, const CTString &strMessage)
{
  CTString strSender;
  // if no sender players
  if (ulFrom==0) {
    // take symbolic sender string
    strSender = strFrom;
  // if there are sender players
  } else {
    // for each sender player
    for(INDEX ipl=0; ipl<ses_apltPlayers.Count(); ipl++) {
      CPlayerTarget &plt = ses_apltPlayers[ipl];
      if (plt.IsActive() && (ulFrom & (1UL<<ipl)) ) {
        // add its name to the sender list
        if (strSender!="") {
          strSender+=", ";
        }
        strSender+=plt.plt_penPlayerEntity->GetPlayerName();
      }
    }
  }

  // let eventual script addon process the message
  extern CTString cmd_strChatSender ;
  extern CTString cmd_strChatMessage;
  extern CTString cmd_cmdOnChat;
  cmd_strChatSender = strSender;
  cmd_strChatMessage = strMessage;
  if (cmd_cmdOnChat!="") {
    _pShell->Execute(cmd_cmdOnChat);
  }

  // if proccessing didn't kill it
  if (cmd_strChatSender!="" && cmd_strChatMessage!="") {
    // print the message
    CPrintF("%s: ^o^cFFFFFF%s^r\n", (const char*)cmd_strChatSender, (const char*)cmd_strChatMessage);
  }
  extern INDEX net_ctChatMessages;
  net_ctChatMessages++;
}

/* NOTES:
1) New thinkers might be added by current ones, but it doesn't matter,
since they must be added forward in time and the list is sorted, so they
cannot be processed in this tick.
2) Thinkers/Movers can be removed during iteration, but the CEntityPointer
guarantee that they are not freed from memory.
*/

// do physics for a game tick
void CSessionState::HandleMovers(void)
{
  _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_HANDLEMOVERS);

//  CPrintF("---- tick %g\n", _pTimer->CurrentTick());

  // put all movers in active list, pushing ones first
  CListHead lhActiveMovers, lhDoneMovers, lhDummyMovers;
  {FORDELETELIST(CMovableEntity, en_lnInMovers, _pNetwork->ga_World.wo_lhMovers, itenMover) {
    CMovableEntity *pen = itenMover;
    pen->en_lnInMovers.Remove();
    // if predicting, and it is not a predictor
    if (ses_bPredicting && !pen->IsPredictor()) {
      // skip it
      lhDummyMovers.AddTail(pen->en_lnInMovers);
      continue;
    }
    if (!(pen->en_ulFlags&ENF_DELETED)) {
      if ((pen->en_ulPhysicsFlags&EPF_ONBLOCK_MASK)==EPF_ONBLOCK_PUSH) {
        lhActiveMovers.AddHead(pen->en_lnInMovers);
      } else {
        lhActiveMovers.AddTail(pen->en_lnInMovers);
      }
    }
  }}

  // for each active mover
  {FORDELETELIST(CMovableEntity, en_lnInMovers, lhActiveMovers, itenMover) {
    // let it clear its temporary variables to prevent bad syncs
    itenMover->ClearMovingTemp();
  }}

  // for each active mover
  {FORDELETELIST(CMovableEntity, en_lnInMovers, lhActiveMovers, itenMover) {
    // let it calculate its wanted parameters for this tick
    itenMover->PreMoving();
  }}

  // while there are some active movers
  while(!lhActiveMovers.IsEmpty()) {
    // get first one
    CMovableEntity *penMoving = LIST_HEAD(lhActiveMovers, CMovableEntity, en_lnInMovers);
    CEntityPointer penCurrent = penMoving;  // just to keep it alive around the loop
    // first move it to done list (if not done, it will move back to world's movers)
    penMoving->en_lnInMovers.Remove();
    lhDoneMovers.AddTail(penMoving->en_lnInMovers);

/*
    CPrintF("**%s(%08x)", 
      penMoving->en_pecClass->ec_pdecDLLClass->dec_strName,
      penMoving->en_ulID);
    if (penMoving->IsPredictable()) {
      CPrintF(" predictable");
    }
    if (penMoving->IsPredictor()) {
      CPrintF(" predictor");
    }
    if (penMoving->IsPredicted()) {
      CPrintF(" predicted");
    }
    if (penMoving->en_penReference!=NULL) {
      CPrintF("reference id%08x", penMoving->en_penReference->en_ulID);
    }
    CPrintF("\n");
*/

    // let it do its own physics
    penMoving->DoMoving();
//    CPrintF("\n");

    // if any mover is re-added, put it to the end of active list
    lhActiveMovers.MoveList(_pNetwork->ga_World.wo_lhMovers);
  }

  // for each done mover
  {FORDELETELIST(CMovableEntity, en_lnInMovers, lhDoneMovers, itenMover) {
    // if predicting, and it is not a predictor
    if (ses_bPredicting && !itenMover->IsPredictor()) {
      // skip it
      continue;
    }
    // let it calculate its parameters after all movement has been resolved
    itenMover->PostMoving();
  }}

  // for each done mover

  {FORDELETELIST(CMovableEntity, en_lnInMovers, lhDoneMovers, itenMover) {
    CMovableEntity *pen = itenMover;
    // if predicting, and it is not a predictor
    if (ses_bPredicting && !itenMover->IsPredictor()) {
      // skip it
      continue;
    }
    // if marked for removing from list of movers
    if (pen->en_ulFlags&ENF_INRENDERING) {
      // remove it
      pen->en_ulFlags&=~ENF_INRENDERING;
      pen->en_lnInMovers.Remove();
    }
    // let it clear its temporary variables to prevent bad syncs
    pen->ClearMovingTemp();
  }}
  
  // return all done movers to the world's list
  _pNetwork->ga_World.wo_lhMovers.MoveList(lhDummyMovers);
  _pNetwork->ga_World.wo_lhMovers.MoveList(lhDoneMovers);

  // handle all the sent events
  CEntity::HandleSentEvents();

  _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_HANDLEMOVERS);
}

// do thinking for a game tick
void CSessionState::HandleTimers(TIME tmCurrentTick)
{
#define TIME_EPSILON 0.0001f
  IFDEBUG(TIME tmLast = 0.0f);

  _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_HANDLETIMERS);
  // repeat
  CListHead &lhTimers = _pNetwork->ga_World.wo_lhTimers;
  FOREVER {
    // no entity found initially
    CRationalEntity *penTimer = NULL;
    // for each entity in list of timers
    FOREACHINLIST(CRationalEntity, en_lnInTimers, lhTimers, iten) {
      // if due after current time
      if(iten->en_timeTimer>tmCurrentTick+TIME_EPSILON) {
        // stop searching
        break;
      }
      // if now predicting and it is not a predictor
      if (ses_bPredicting && !iten->IsPredictor()) {
        // skip it
        continue;
      }
      // remember found entity, and stop searching
      penTimer = iten;
      break;
    }

    // if no entity is found
    if (penTimer==NULL) {
      // stop
      break;
    }

    // check that timers are propertly handled
    ASSERT(penTimer->en_timeTimer>tmCurrentTick-_pTimer->TickQuantum-TIME_EPSILON);
    //ASSERT(penTimer->en_timeTimer>=tmLast);
    IFDEBUG(tmLast=penTimer->en_timeTimer);

    // remove the timer from the list
    penTimer->en_timeTimer = THINKTIME_NEVER;
    penTimer->en_lnInTimers.Remove();
    // send timer event to the entity
    penTimer->SendEvent(ETimer());
  }

  // handle all the sent events
  CEntity::HandleSentEvents();
  _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_HANDLETIMERS);
}

// do a warm-up run of the world for a few ticks
void CSessionState::WarmUpWorld(void)
{
#define WORLD_WORMUP_COUNT 20   // run 20 ticks to get stable
  ses_tmLastProcessedTick = _pNetwork->ga_srvServer.srv_tmLastProcessedTick = 0;
  ses_iLastProcessedSequence = _pNetwork->ga_srvServer.srv_iLastProcessedSequence = -1;
  // add a few empty all-action messages to the game stream
  for (INDEX iTick=0; iTick<WORLD_WORMUP_COUNT; iTick++) {
    _pNetwork->ga_srvServer.srv_tmLastProcessedTick += _pTimer->TickQuantum;
    _pNetwork->ga_srvServer.srv_iLastProcessedSequence++;
    CNetworkStreamBlock nsbAllActions(MSG_SEQ_ALLACTIONS, _pNetwork->ga_srvServer.srv_iLastProcessedSequence);
    nsbAllActions<<_pNetwork->ga_srvServer.srv_tmLastProcessedTick;
    nsbAllActions.Rewind();
    ses_nsGameStream.AddBlock(nsbAllActions);
  }

  // process the blocks
  ProcessGameStream();
}

// create a checksum value for sync-check
void CSessionState::ChecksumForSync(ULONG &ulCRC, INDEX iExtensiveSyncCheck)
{
  CRC_AddLONG(ulCRC, ses_iLastProcessedSequence);
  CRC_AddLONG(ulCRC, ses_iLevel);
  CRC_AddLONG(ulCRC, ses_bPause);
  if (iExtensiveSyncCheck>0) {
    CRC_AddLONG(ulCRC, ses_bGameFinished);
    CRC_AddLONG(ulCRC, ses_ulRandomSeed);
  }

  // if all entities should be synced
  if (iExtensiveSyncCheck>1) {
    // for each active entity in the world
    {FOREACHINDYNAMICCONTAINER(_pNetwork->ga_World.wo_cenEntities, CEntity, iten) {
      if (iten->IsPredictor()) {
        continue;
      }
      iten->ChecksumForSync(ulCRC, iExtensiveSyncCheck);
    }}
    // for each entity in the world
    {FOREACHINDYNAMICCONTAINER(_pNetwork->ga_World.wo_cenAllEntities, CEntity, iten) {
      if (iten->IsPredictor()) {
        continue;
      }
      iten->ChecksumForSync(ulCRC, iExtensiveSyncCheck);
    }}
  }

  if (iExtensiveSyncCheck>0) {
    // checksum all movers
    {FOREACHINLIST(CMovableEntity, en_lnInMovers, _pNetwork->ga_World.wo_lhMovers, iten) {
      if (iten->IsPredictor()) {
        continue;
      }
      iten->ChecksumForSync(ulCRC, iExtensiveSyncCheck);
    }}
  }
  // checksum all active players
  {FOREACHINSTATICARRAY(ses_apltPlayers, CPlayerTarget, itclt) {
    CPlayerTarget &clt = *itclt;
    if (clt.IsActive()) {
      clt.plt_paPreLastAction.ChecksumForSync(ulCRC);
      clt.plt_paLastAction.ChecksumForSync(ulCRC);
      clt.plt_penPlayerEntity->ChecksumForSync(ulCRC, iExtensiveSyncCheck);
    }
  }}
}

// dump sync data to text file
void CSessionState::DumpSync_t(CTStream &strm, INDEX iExtensiveSyncCheck)  // throw char *
{
  strm.FPrintF_t("Level: %d\n", ses_iLevel);
  strm.FPrintF_t("Sequence: %d\n", ses_iLastProcessedSequence);
  strm.FPrintF_t("Tick: %g\n", ses_tmLastProcessedTick);
  strm.FPrintF_t("Paused: %d\n", ses_bPause);
  if (iExtensiveSyncCheck>0) {
    strm.FPrintF_t("Finished: %d\n", ses_bGameFinished);
    strm.FPrintF_t("Random seed: 0x%08x\n", ses_ulRandomSeed);
  }

  _pNetwork->ga_World.LockAll();

  strm.FPrintF_t("\n\n======================== players:\n");
  // dump all active players
  {FOREACHINSTATICARRAY(ses_apltPlayers, CPlayerTarget, itclt) {
    CPlayerTarget &clt = *itclt;
    if (clt.IsActive()) {
      clt.plt_penPlayerEntity->DumpSync_t(strm, iExtensiveSyncCheck);
      strm.FPrintF_t("\n -- action:\n");
      clt.plt_paPreLastAction.DumpSync_t(strm);
      clt.plt_paLastAction.DumpSync_t(strm);
    }
  }}

  if (iExtensiveSyncCheck>0) {
    strm.FPrintF_t("\n\n======================== movers:\n");
    // dump all movers
    {FOREACHINLIST(CMovableEntity, en_lnInMovers, _pNetwork->ga_World.wo_lhMovers, iten) {
      if (iten->IsPredictor()) {
        continue;
      }
      iten->DumpSync_t(strm, iExtensiveSyncCheck);
    }}
  }

  // if all entities should be synced
  if (iExtensiveSyncCheck>1) {
    strm.FPrintF_t("\n\n======================== active entities (%d):\n",
      _pNetwork->ga_World.wo_cenEntities.Count());
    // for each entity in the world
    {FOREACHINDYNAMICCONTAINER(_pNetwork->ga_World.wo_cenEntities, CEntity, iten) {
      if (iten->IsPredictor()) {
        continue;
      }
      iten->DumpSync_t(strm, iExtensiveSyncCheck);
    }}
    strm.FPrintF_t("\n\n======================== all entities (%d):\n",
      _pNetwork->ga_World.wo_cenEntities.Count());
    // for each entity in the world
    {FOREACHINDYNAMICCONTAINER(_pNetwork->ga_World.wo_cenAllEntities, CEntity, iten) {
      if (iten->IsPredictor()) {
        continue;
      }
      iten->DumpSync_t(strm, iExtensiveSyncCheck);
    }}
  }

  _pNetwork->ga_World.UnlockAll();
}

#if DEBUG_SYNCSTREAMDUMPING
/* 
 * Obtain valid session dump memory stream
 */
CTMemoryStream *GetDumpStream(void)
{
  if( _pNetwork->ga_sesSessionState.ses_pstrm == NULL)
  {
    _pNetwork->ga_sesSessionState.ses_pstrm = new CTMemoryStream;
  }
  return _pNetwork->ga_sesSessionState.ses_pstrm;
}

void ClearDumpStream(void)
{
  if( _pNetwork->ga_sesSessionState.ses_pstrm != NULL)
  {
    delete _pNetwork->ga_sesSessionState.ses_pstrm;
    _pNetwork->ga_sesSessionState.ses_pstrm = NULL;
  }
}
#endif

/*
 * Process a game tick.
 */

extern INDEX cli_bEmulateDesync;
void CSessionState::ProcessGameTick(CNetworkMessage &nmMessage, TIME tmCurrentTick)
{
  ses_tmLastPredictionProcessed = -1;

  _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_PROCESSGAMETICK);
  ASSERT(this!=NULL);

#if DEBUG_SYNCSTREAMDUMPING
  try
  {
    CTMemoryStream *pstrm = GetDumpStream();
    pstrm->FPrintF_t("Time tick: %.2f\n", tmCurrentTick);
  }
  catch (char *strError)
  {
    CPrintF("Cannot dump sync data: %s\n", strError);
  }
#endif
  //CPrintF("normal: %.2f\n", tmCurrentTick);

  // FPU must be in 24-bit mode
  CSetFPUPrecision FPUPrecision(FPT_24BIT);

  // copy the tick to process into tick used for all tasks
  _pTimer->SetCurrentTick(tmCurrentTick);
  _pfNetworkProfile.IncrementAveragingCounter();
  _pfPhysicsProfile.IncrementAveragingCounter();

  // random is allowed only here, during entity ai
  ses_bAllowRandom = TRUE;

  _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_APPLYACTIONS);
  // for all clients
  INDEX iClient = 0;
  FOREACHINSTATICARRAY(ses_apltPlayers, CPlayerTarget, itplt) {
    // if client is active
    if (itplt->IsActive()) {
// player indices transmission is unneccessary unless if debugging
//      // check that it is present in message
//      INDEX iClientInMessage;
//      nmMessage>>iClientInMessage;     // client index
//      ASSERT(iClient==iClientInMessage);
      // read the action
      CPlayerAction paAction;
      nmMessage>>paAction;
      // apply the action
      itplt->ApplyActionPacket(paAction);

      // desync emulation!
      if( cli_bEmulateDesync) {
        itplt->plt_penPlayerEntity->SetHealth(1.0f);
      }
    }
    iClient++;
  }
  cli_bEmulateDesync = FALSE;

  // handle all the sent events
  CEntity::HandleSentEvents();
  _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_APPLYACTIONS);

  // do thinking
  HandleTimers(tmCurrentTick);
  // do physics
  HandleMovers();

  // notify all entities of level change as needed
  if (_lphCurrent==LCP_INITIATED) {
    EPreLevelChange ePreChange;
    ePreChange.iUserData = _pNetwork->ga_iNextLevelUserData;
    SendLevelChangeNotification(ePreChange);
    CEntity::HandleSentEvents();
    _lphCurrent=LCP_SIGNALLED;
  }
  if (_lphCurrent==LCP_CHANGED) {
    EPostLevelChange ePostChange;
    ePostChange.iUserData = _pNetwork->ga_iNextLevelUserData;
    SendLevelChangeNotification(ePostChange);
    CEntity::HandleSentEvents();
    _lphCurrent=LCP_NOCHANGE;
  }

  _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_WORLDBASETICK);
  // let the worldbase execute its tick function
  if (_pNetwork->ga_World.wo_pecWorldBaseClass!=NULL
    &&_pNetwork->ga_World.wo_pecWorldBaseClass->ec_pdecDLLClass!=NULL
    &&_pNetwork->ga_World.wo_pecWorldBaseClass->ec_pdecDLLClass->dec_OnWorldTick!=NULL) {
    _pNetwork->ga_World.wo_pecWorldBaseClass->ec_pdecDLLClass->
      dec_OnWorldTick(&_pNetwork->ga_World);
  }
  // handle all the sent events
  CEntity::HandleSentEvents();
  _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_WORLDBASETICK);

  // make sync-check and send to server if needed
  MakeSynchronisationCheck();

#if DEBUG_SYNCSTREAMDUMPING
  extern INDEX cli_bDumpSyncEachTick;
  if( cli_bDumpSyncEachTick)
  {
    DumpSyncToMemory();
  }
#endif
  ses_bAllowRandom = FALSE;

  ses_tmPredictionHeadTick = Max(ses_tmPredictionHeadTick, tmCurrentTick);

  _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_PROCESSGAMETICK);

  // assure that FPU precision was low all the rendering time
  ASSERT( GetFPUPrecision()==FPT_24BIT);

  // let eventual script do something on each tick
  extern FLOAT cmd_tmTick;
  extern CTString cmd_cmdOnTick;
  if (cmd_cmdOnTick!="") {
    cmd_tmTick = tmCurrentTick;
    _pShell->Execute(cmd_cmdOnTick);
  }
}

/* Process a predicted game tick. */
void CSessionState::ProcessPredictedGameTick(INDEX iPredictionStep, FLOAT fFactor, TIME tmCurrentTick)
{
  _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_PROCESSGAMETICK);
  ASSERT(this!=NULL);

  //CPrintF("predicted: %.2f\n", tmCurrentTick);

  // FPU must be in 24-bit mode
  CSetFPUPrecision FPUPrecision(FPT_24BIT);

  // now predicting
  ses_bPredicting = TRUE;

  // copy the tick to process into tick used for all tasks
  _pTimer->SetCurrentTick(tmCurrentTick);
  _pfNetworkProfile.IncrementAveragingCounter();
  _pfPhysicsProfile.IncrementAveragingCounter();

  // random is allowed only here, during entity ai
  ses_bAllowRandom = TRUE;

  _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_APPLYACTIONS);
  // for all clients
  FOREACHINSTATICARRAY(ses_apltPlayers, CPlayerTarget, itplt) {
    // if client is active
    if (itplt->IsActive()) {
      // apply the predicted action
      itplt->ApplyPredictedAction(iPredictionStep, fFactor);
    }
  }

  // handle all the sent events
  CEntity::HandleSentEvents();
  _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_APPLYACTIONS);

  // do thinking
  HandleTimers(tmCurrentTick);
  // do physics
  HandleMovers();

  _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_WORLDBASETICK);

  // handle all the sent events
  CEntity::HandleSentEvents();
  _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_WORLDBASETICK);

  ses_bAllowRandom = FALSE;

  // not predicting any more
  ses_bPredicting = FALSE;

  ses_tmPredictionHeadTick = Max(ses_tmPredictionHeadTick, tmCurrentTick);

  _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_PROCESSGAMETICK);

  // assure that FPU precision was low all the rendering time
  ASSERT( GetFPUPrecision()==FPT_24BIT);
}

/*
 * Process all eventual available gamestream blocks.
 */
void CSessionState::ProcessGameStream(void)
{
  _pfNetworkProfile.StartTimer(CNetworkProfile::PTI_SESSIONSTATE_PROCESSGAMESTREAM);

  // must be in 24bit mode when managing entities
  CSetFPUPrecision FPUPrecision(FPT_24BIT);

  TIME tmDemoNow = _pNetwork->ga_fDemoTimer;
  // if playing a demo
  if (_pNetwork->ga_bDemoPlay) {
    // find how much ticks to step by now
    if (ses_tmLastDemoSequence<0.0f) {
      ses_tmLastDemoSequence=tmDemoNow;
    }
    // if it is finished
    if (_pNetwork->ga_bDemoPlayFinished) {
      _pfNetworkProfile.StopTimer(CNetworkProfile::PTI_SESSIONSTATE_PROCESSGAMESTREAM);
      // do nothing
      return;
    }
  }

  // repeat
  FOREVER {
    // if playing a demo
    if (_pNetwork->ga_bDemoPlay) {
      // if finished for this pass
      if (ses_tmLastDemoSequence>=tmDemoNow) {
        _pfNetworkProfile.StopTimer(CNetworkProfile::PTI_SESSIONSTATE_PROCESSGAMESTREAM);
        // end the loop
        return;
      }
      // try to
      try {
        // read a stream block from the demo
        CChunkID cid = _pNetwork->ga_strmDemoPlay.PeekID_t();
        if (cid == CChunkID("DTCK")) {
          _pNetwork->ga_strmDemoPlay.ExpectID_t("DTCK");// demo tick
          CNetworkStreamBlock nsbBlock;
          nsbBlock.Read_t(_pNetwork->ga_strmDemoPlay);
          ses_nsGameStream.AddBlock(nsbBlock);
          _pNetwork->ga_bDemoPlayFinished = FALSE;
        } else {
          _pNetwork->ga_strmDemoPlay.ExpectID_t("DEND"); // demo end
          _pNetwork->ga_bDemoPlayFinished = TRUE;
          _pfNetworkProfile.StopTimer(CNetworkProfile::PTI_SESSIONSTATE_PROCESSGAMESTREAM);
          return;
        }
      // if not successful
      } catch(char *strError) {
        // report error
        CPrintF(TRANS("Error while playing demo: %s"), strError);
        _pfNetworkProfile.StopTimer(CNetworkProfile::PTI_SESSIONSTATE_PROCESSGAMESTREAM);
        return;
      }
    }

    // calculate index of next expected sequence
    INDEX iSequence = ses_iLastProcessedSequence+1;
    // get the stream block with that sequence
    CNetworkStreamBlock *pnsbBlock;
    CNetworkStream::Result res = ses_nsGameStream.GetBlockBySequence(iSequence, pnsbBlock);
    // if it is found
    if (res==CNetworkStream::R_OK) {
      // if recording a demo
      if (_pNetwork->ga_bDemoRec) {
        // try to
        try {
          // write the stream block to the demo
          _pNetwork->ga_strmDemoRec.WriteID_t("DTCK");
          pnsbBlock->Write_t(_pNetwork->ga_strmDemoRec);
        // if not successful
        } catch(char *strError) {
          // report error
          CPrintF(TRANS("Error while recording demo: %s"), strError);
          // stop recording
          _pNetwork->StopDemoRec();
        }
      }

      // remember the message type
      int iMsgType=pnsbBlock->GetType();
      // remember the processed sequence
      ses_iLastProcessedSequence = iSequence;
      // process the stream block
      ProcessGameStreamBlock(*pnsbBlock);
      // remove the block from the stream
      pnsbBlock->RemoveFromStream();
      delete pnsbBlock;
      // remove eventual resent blocks that have already been processed
      ses_nsGameStream.RemoveOlderBlocksBySequence(ses_iLastProcessedSequence-2);

      // if the message is all actions
      if (iMsgType==MSG_SEQ_ALLACTIONS) {
        // if playing a demo
        if (_pNetwork->ga_bDemoPlay) {
          // step demo sequence
          ses_tmLastDemoSequence+=_pTimer->TickQuantum;
        }
      }

    // if it is not avaliable yet
    } else if (res==CNetworkStream::R_BLOCKNOTRECEIVEDYET) {
      // finish
      _pfNetworkProfile.StopTimer(CNetworkProfile::PTI_SESSIONSTATE_PROCESSGAMESTREAM);
      return;
    // if it is missing
    } else if (res==CNetworkStream::R_BLOCKMISSING) {
      
      // if it is a new sequence
      if (iSequence>ses_iMissingSequence) {
        ses_iMissingSequence = iSequence;
        // setup timeout
        ses_tvResendTime = _pTimer->GetHighPrecisionTimer();
        ses_tmResendTimeout = 0.1f;
      }

      // if timeout occured
      if (_pTimer->GetHighPrecisionTimer()>ses_tvResendTime+CTimerValue(ses_tmResendTimeout)) {

        _pNetwork->AddNetGraphValue(NGET_MISSING, 1.0f); // missing sequence

        // find how many are missing
        INDEX iNextValid = ses_nsGameStream.GetOldestSequenceAfter(iSequence);
        INDEX ctSequences = Max(iNextValid-iSequence, INDEX(1));

          // create a request for resending the missing packet
        CNetworkMessage nmResendRequest(MSG_REQUESTGAMESTREAMRESEND);
        nmResendRequest<<iSequence;
        nmResendRequest<<ctSequences;
        // send the request to the server
        _pNetwork->SendToServer(nmResendRequest);

        extern INDEX net_bReportMiscErrors;
        if (net_bReportMiscErrors) {
          CPrintF(TRANS("Session State: Missing sequences %d-%d(%d) timeout %g\n"), 
            iSequence, iSequence+ctSequences-1, ctSequences, ses_tmResendTimeout);
        }

        // increase the timeout
        ses_tvResendTime = _pTimer->GetHighPrecisionTimer();
        ses_tmResendTimeout *= 2.0f;
      }

      // finish
      _pfNetworkProfile.StopTimer(CNetworkProfile::PTI_SESSIONSTATE_PROCESSGAMESTREAM);
      return;
    }
  }
}

// flush prediction actions that were already processed
void CSessionState::FlushProcessedPredictions(void)
{
  // for all clients
  FOREACHINSTATICARRAY(ses_apltPlayers, CPlayerTarget, itplt) {
    // flush
    itplt->FlushProcessedPredictions();
  }
}

/* Find out how many prediction steps are currently pending. */
INDEX CSessionState::GetPredictionStepsCount(void)
{
  // start with no prediction
  INDEX ctPredictionSteps = 0;
  // for all clients
  FOREACHINSTATICARRAY(ses_apltPlayers, CPlayerTarget, itplt) {
    // update maximum number of possible predictions
    ctPredictionSteps = Max(ctPredictionSteps, itplt->GetNumberOfPredictions());
  }

  return ctPredictionSteps;
}

/* Process all eventual available prediction actions. */
void CSessionState::ProcessPrediction(void)
{
  // FPU must be in 24-bit mode
  CSetFPUPrecision FPUPrecision(FPT_24BIT);

  // get number of steps that could be predicted
  INDEX ctSteps = GetPredictionStepsCount();

  // limit prediction
  extern INDEX cli_iMaxPredictionSteps;
  ctSteps = ClampUp(ctSteps, cli_iMaxPredictionSteps);

  // if none
  if(ctSteps<=0) {
    // do nothing
    return;
  }

  // if this would not result in any new tick predicted
  TIME tmNow = ses_tmLastProcessedTick+ctSteps*_pTimer->TickQuantum;
  if (Abs(ses_tmLastPredictionProcessed-tmNow)<_pTimer->TickQuantum/10.0f) {
    // do nothing
    return;
  }
  // remeber what was predicted now
  ses_tmLastPredictionProcessed = tmNow;

  // remember random seed and entity ID
  ULONG ulOldRandom = ses_ulRandomSeed;
  ULONG ulEntityID = _pNetwork->ga_World.wo_ulNextEntityID;

  // delete all predictors (if any left from last time)
  _pNetwork->ga_World.DeletePredictors();
  // create new predictors
  _pNetwork->ga_World.CreatePredictors();

  // for each step
  TIME tmPredictedTick = ses_tmLastProcessedTick;
  for(INDEX iPredictionStep=0; iPredictionStep<ctSteps; iPredictionStep++) {
    tmPredictedTick+=_pTimer->TickQuantum;
    //ses_tmPredictionHeadTick = Max(ses_tmPredictionHeadTick, tmPredictedTick);
    // predict it
    ProcessPredictedGameTick(iPredictionStep, FLOAT(iPredictionStep)/ctSteps, tmPredictedTick);
  }
  // restore random seed and entity ID
  ses_ulRandomSeed = ulOldRandom;
  _pNetwork->ga_World.wo_ulNextEntityID = ulEntityID;
}

/*
 * Process a gamestream block.
 */
void CSessionState::ProcessGameStreamBlock(CNetworkMessage &nmMessage)
{
  // copy the tick to process into tick used for all tasks
  _pTimer->SetCurrentTick(ses_tmLastProcessedTick);

  // check message type
  switch (nmMessage.GetType()) {
  // if adding a new player
  case MSG_SEQ_ADDPLAYER: {
      _pNetwork->AddNetGraphValue(NGET_NONACTION, 1.0f); // non-action sequence
      INDEX iNewPlayer;
      CPlayerCharacter pcCharacter;
      nmMessage>>iNewPlayer;      // player index
      nmMessage>>pcCharacter;     // player character

      // delete all predictors
      _pNetwork->ga_World.DeletePredictors();

      // activate the player
      ses_apltPlayers[iNewPlayer].Activate();

      // if there is no entity with that character in the world
      CPlayerEntity *penNewPlayer = _pNetwork->ga_World.FindEntityWithCharacter(pcCharacter);
      if (penNewPlayer==NULL) {
        // create an entity for it
        CPlacement3D pl(FLOAT3D(0.0f,0.0f,0.0f), ANGLE3D(0,0,0));
        try {
          CTFileName fnmPlayer = CTString("Classes\\Player.ecl"); // this must not be a dependency!
          penNewPlayer = (CPlayerEntity*)(_pNetwork->ga_World.CreateEntity_t(pl, fnmPlayer));
          // attach entity to client data
          ses_apltPlayers[iNewPlayer].AttachEntity(penNewPlayer);
          // attach the character to it
          penNewPlayer->en_pcCharacter = pcCharacter;
          // prepare the entity
          penNewPlayer->Initialize();
        } catch (char *strError) {
          (void)strError;
          FatalError(TRANS("Cannot load Player class:\n%s"), strError);
        }
        if (!_pNetwork->IsPlayerLocal(penNewPlayer)) {
          CPrintF(TRANS("%s joined\n"), penNewPlayer->GetPlayerName());
        }
      } else {
        // attach entity to client data
        ses_apltPlayers[iNewPlayer].AttachEntity(penNewPlayer);
        // make it update its character
        penNewPlayer->CharacterChanged(pcCharacter);

        if (!_pNetwork->IsPlayerLocal(penNewPlayer)) {
          CPrintF(TRANS("%s rejoined\n"), penNewPlayer->GetPlayerName());
        }
      }

    } break;
  // if removing a player
  case MSG_SEQ_REMPLAYER: {
      _pNetwork->AddNetGraphValue(NGET_NONACTION, 1.0f); // non-action sequence
      INDEX iPlayer;
      nmMessage>>iPlayer;      // player index

      // delete all predictors
      _pNetwork->ga_World.DeletePredictors();

      // inform entity of disconnnection
      CPrintF(TRANS("%s left\n"), ses_apltPlayers[iPlayer].plt_penPlayerEntity->GetPlayerName());
      ses_apltPlayers[iPlayer].plt_penPlayerEntity->Disconnect();
      // deactivate the player
      ses_apltPlayers[iPlayer].Deactivate();
      // handle all the sent events
      ses_bAllowRandom = TRUE;
      CEntity::HandleSentEvents();
      ses_bAllowRandom = FALSE;

    } break;

  // if changing character
  case MSG_SEQ_CHARACTERCHANGE: {
      _pNetwork->AddNetGraphValue(NGET_NONACTION, 1.0f); // non-action sequence
      INDEX iPlayer;
      CPlayerCharacter pcCharacter;
      nmMessage>>iPlayer>>pcCharacter;

      // delete all predictors
      _pNetwork->ga_World.DeletePredictors();

      // change the character
      ses_apltPlayers[iPlayer].plt_penPlayerEntity->CharacterChanged(pcCharacter);

      // handle all the sent events
      ses_bAllowRandom = TRUE;
      CEntity::HandleSentEvents();
      ses_bAllowRandom = FALSE;

                                } break;
  // if receiving client actions
  case MSG_SEQ_ALLACTIONS: {
      // read time from packet
      TIME tmPacket;
      nmMessage>>tmPacket;    // packet time

      // time must be greater by one than that on the last packet received
      TIME tmTickQuantum = _pTimer->TickQuantum;
      TIME tmPacketDelta = tmPacket-ses_tmLastProcessedTick;
      if(! (Abs(tmPacketDelta-tmTickQuantum) < (tmTickQuantum/10.0f)) ) {
        // report debug info
        CPrintF(
          TRANS("Session state: Mistimed MSG_ALLACTIONS: Last received tick %g, this tick %g\n"),
          ses_tmLastProcessedTick, tmPacket);
      }
      // remember the received tick
      ses_tmLastProcessedTick = tmPacket;

      // NOTE: if we got a tick, it means that all players have joined
      // don't wait for new players any more
      ses_bWaitAllPlayers = FALSE;

      // delete all predictors
      _pNetwork->ga_World.DeletePredictors();
      // process the tick
      ProcessGameTick(nmMessage, tmPacket);

    } break;

  // if receiving pause message
  case MSG_SEQ_PAUSE: {
    _pNetwork->AddNetGraphValue(NGET_NONACTION, 1.0f); // non-action sequence

    // delete all predictors
    _pNetwork->ga_World.DeletePredictors();

    BOOL bPauseBefore = ses_bPause;
    // read the pause state and pauser from it
    nmMessage>>(INDEX&)ses_bPause;
    CTString strPauser;
    nmMessage>>strPauser;
    // if paused by some other machine
    if (strPauser!=TRANS("Local machine")) {
      // report who paused
      if (ses_bPause!=bPauseBefore) {
        if (ses_bPause) {
          CPrintF(TRANS("Paused by '%s'\n"), strPauser);
        } else {
          CPrintF(TRANS("Unpaused by '%s'\n"), strPauser);
        }
      }
    }
    // must keep wanting current state
    ses_bWantPause = ses_bPause;
                          } break;
  // otherwise
  default:
    // error
    ASSERT(FALSE);
  }
}

// Set lerping factor for current frame.
void CSessionState::SetLerpFactor(CTimerValue tvNow)
{
  // if no lerping
  if (!net_bLerping) {
    // set lerping factor without lerping
    _pTimer->SetLerp(1.0f);
    _pTimer->SetCurrentTick(ses_tmPredictionHeadTick);
    return;
  }
  
  FLOAT fFactor  = 0.0f;
  FLOAT fFactor2 = 0.0f;
  
  // ---- primary factor - used for prediction
  {
    TIME tmLastTick = ses_tmPredictionHeadTick;

    // if lerping was never set before
    if (ses_tmInitializationTick<0) {
      // initialize it
      ses_tvInitialization = tvNow;
      ses_tmInitializationTick = tmLastTick;
    }

    // get passed time from session state starting in precise time and in ticks
    FLOAT tmRealDelta = FLOAT((tvNow-ses_tvInitialization).GetSeconds())
      *_pNetwork->ga_fGameRealTimeFactor*_pNetwork->ga_sesSessionState.ses_fRealTimeFactor;
    FLOAT tmTickDelta = tmLastTick-ses_tmInitializationTick;
    // calculate factor
    fFactor = 1.0f-(tmTickDelta-tmRealDelta)/_pTimer->TickQuantum;

    // if the factor starts getting below zero
    if (fFactor<0) {
      //CPrintF("Lerp=%.2f <0 @ %.2fs\n", fFactor, tmLastTick);
      // clamp it
      fFactor = 0.0f;
      // readjust timers so that it gets better
      ses_tvInitialization = tvNow;
      ses_tmInitializationTick = tmLastTick-_pTimer->TickQuantum;
    }
    if (fFactor>1) {
      //CPrintF("Lerp=%.2f >1 @ %.2fs\n", fFactor, tmLastTick);
      // clamp it
      fFactor = 1.0f;
      // readjust timers so that it gets better
      ses_tvInitialization = tvNow;
      ses_tmInitializationTick = tmLastTick;
    }

    #if DEBUG_LERPING

      avfStats[ctCounter][0] = tmRealDelta/_pTimer->TickQuantum;
      avfStats[ctCounter][1] = tmTickDelta/_pTimer->TickQuantum;
      avfStats[ctCounter][2] = fFactor;
      avfStats[ctCounter][3] = (tmLastTick/_pTimer->TickQuantum-1.0f)+fFactor;
      ctCounter++;
      if (ctCounter>=ctMax) {
        ctCounter = 0;
      }
    #endif // DEBUG_LERPING
  }

  // ---- secondary factor - used for non-predicted movement
  {
    TIME tmLastTick = ses_tmLastProcessedTick;
    // if lerping was never set before
    if (ses_tmInitializationTick2<0) {
      // initialize it
      ses_tvInitialization2 = tvNow;
      ses_tmInitializationTick2 = tmLastTick;
    }

    // get passed time from session state starting in precise time and in ticks
    FLOAT tmRealDelta = FLOAT((tvNow-ses_tvInitialization2).GetSeconds())
      *_pNetwork->ga_fGameRealTimeFactor*_pNetwork->ga_sesSessionState.ses_fRealTimeFactor;
    FLOAT tmTickDelta = tmLastTick-ses_tmInitializationTick2;
    // calculate factor
    fFactor2 = 1.0f-(tmTickDelta-tmRealDelta)/_pTimer->TickQuantum;

    // if the factor starts getting below zero
    if (fFactor2<0) {
      //CPrintF("Lerp2=%.2f <0 @ %.2fs\n", fFactor2, tmLastTick);
      // clamp it
      fFactor2 = 0.0f;
      // readjust timers so that it gets better
      ses_tvInitialization2 = tvNow;
      ses_tmInitializationTick2 = tmLastTick-_pTimer->TickQuantum;
    }
    if (fFactor2>1) {
      //CPrintF("Lerp2=%.2f >1 @ %.2fs\n", fFactor2, tmLastTick);
      // clamp it
      fFactor2 = 1.0f;
      // readjust timers so that it gets better
      ses_tvInitialization2 = tvNow;
      ses_tmInitializationTick2 = tmLastTick;
    }
  }

  // set lerping factor2
  _pTimer->SetLerp(fFactor);
  _pTimer->SetLerp2(fFactor2);
  _pTimer->SetCurrentTick(ses_tmPredictionHeadTick);
}

/*
 * Read session state state from a stream.
 */
void CSessionState::Read_t(CTStream *pstr)  // throw char *
{
#if DEBUG_SYNCSTREAMDUMPING
  CPrintF( "Session state read: Sequence %d, Time %g\n", ses_iLastProcessedSequence, ses_tmLastProcessedTick);
#endif
  // read time information and random seed

  INDEX iVersion = SESSIONSTATEVERSION_OLD;
  if (pstr->PeekID_t()==CChunkID("SESV")) {
    pstr->ExpectID_t("SESV");
    (*pstr)>>iVersion;
  }
  (*pstr)>>ses_tmLastProcessedTick;
  (*pstr)>>ses_iLastProcessedSequence;
  (*pstr)>>ses_iLevel;
  (*pstr)>>ses_ulRandomSeed;
  (*pstr)>>ses_ulSpawnFlags;
  (*pstr)>>ses_tmSyncCheckFrequency;
  (*pstr)>>ses_iExtensiveSyncCheck;
  (*pstr)>>ses_tmLastSyncCheck;
  (*pstr)>>ses_ctMaxPlayers;
  (*pstr)>>ses_bWaitAllPlayers;
  (*pstr)>>ses_bPause;
  (*pstr)>>ses_bGameFinished;
  if (iVersion>=SESSIONSTATEVERSION_WITHBULLETTIME) {
    (*pstr)>>ses_fRealTimeFactor;
  }
  ses_bWaitingForServer = FALSE;
  ses_bWantPause = ses_bPause;
  ses_strDisconnected = "";
  _pTimer->SetCurrentTick(ses_tmLastProcessedTick);

  // read session properties from stream
  (*pstr)>>_pNetwork->ga_strSessionName;
  pstr->Read_t(_pNetwork->ga_aubProperties, NET_MAXSESSIONPROPERTIES);

  // read world and its state
  ReadWorldAndState_t(pstr);

#if DEBUG_SYNCSTREAMDUMPING
  ClearDumpStream();

  // dump sync data if needed
  extern INDEX cli_bDumpSyncEachTick;
  if( cli_bDumpSyncEachTick)
  {
    DumpSyncToMemory();
  }
#endif
}

void CSessionState::ReadWorldAndState_t(CTStream *pstr)   // throw char *
{
  // check engine build disallowing reinit
  BOOL bNeedsReinit;
  _pNetwork->CheckVersion_t(*pstr, FALSE, bNeedsReinit);
  ASSERT(!bNeedsReinit);

  // read world filename from stream
  (*pstr)>>_pNetwork->ga_fnmWorld;

  if (CTFileName(pstr->GetDescription()).FileExt()==".dem" &&
    GetFileTimeStamp_t(pstr->GetDescription())<=GetFileTimeStamp_t(_pNetwork->ga_fnmWorld)) {
    ThrowF_t(
      TRANS("Cannot play demo because file '%s'\n"
      "is older than file '%s'!\n"),
      CTString(pstr->GetDescription()),
      CTString(_pNetwork->ga_fnmWorld));
  }

  // prepare the world for loading
  _pNetwork->ga_World.DeletePredictors();
  _pNetwork->ga_World.Clear();
  _pNetwork->ga_World.LockAll();
  // load the world brushes from the world file
  _pNetwork->ga_World.LoadBrushes_t(_pNetwork->ga_fnmWorld);
  // read world situation
  _pNetwork->ga_World.ReadState_t(pstr);

  // create an empty list for relinking timers
  CListHead lhNewTimers;
  // read number of entities in timer list
  pstr->ExpectID_t("TMRS");   // timers
  INDEX ctTimers;
  *pstr>>ctTimers;
//  ASSERT(ctTimers == _pNetwork->ga_World.wo_lhTimers.Count());
  // for each entity in the timer list
  {for(INDEX ienTimer=0; ienTimer<ctTimers; ienTimer++) {
    // read its index in container of all entities
    INDEX ien;
    *pstr>>ien;
    // get the entity
    CRationalEntity *pen = (CRationalEntity*)_pNetwork->ga_World.EntityFromID(ien);
    // remove it from the timer list and add it at the end of the new timer list
    if (pen->en_lnInTimers.IsLinked()) {
      pen->en_lnInTimers.Remove();
      lhNewTimers.AddTail(pen->en_lnInTimers);
    }
  }}
  // use the new timer list instead the old one
  ASSERT(_pNetwork->ga_World.wo_lhTimers.IsEmpty());
  _pNetwork->ga_World.wo_lhTimers.MoveList(lhNewTimers);

  // create an empty list for relinking movers
  CListHead lhNewMovers;
  // read number of entities in mover list
  pstr->ExpectID_t("MVRS");   // movers
  INDEX ctMovers;
  *pstr>>ctMovers;
  ASSERT(ctMovers == _pNetwork->ga_World.wo_lhMovers.Count());
  // for each entity in the mover list
  {for(INDEX ienMover=0; ienMover<ctMovers; ienMover++) {
    // read its index in container of all entities
    INDEX ien;
    *pstr>>ien;
    // get the entity
    CMovableEntity *pen = (CMovableEntity*)_pNetwork->ga_World.EntityFromID(ien);
    // remove it from the mover list and add it at the end of the new mover list
    if (pen->en_lnInMovers.IsLinked()) {
      pen->en_lnInMovers.Remove();
    }
    lhNewMovers.AddTail(pen->en_lnInMovers);
  }}
  // use the new mover list instead the old one
  ASSERT(_pNetwork->ga_World.wo_lhMovers.IsEmpty());
  _pNetwork->ga_World.wo_lhMovers.MoveList(lhNewMovers);

  // read number of players
  INDEX ctPlayers;
  (*pstr)>>ctPlayers;
  ASSERT(ctPlayers==ses_apltPlayers.Count());
  // for all clients
  FOREACHINSTATICARRAY(ses_apltPlayers, CPlayerTarget, itclt) {
    // read from stream
    itclt->Read_t(pstr);
  }

  _pNetwork->ga_World.UnlockAll();
}

void CSessionState::ReadRememberedLevels_t(CTStream *pstr)
{
  pstr->ExpectID_t("RLEV"); // remembered levels
  // read count of remembered levels
  INDEX ctLevels;
  (*pstr)>>ctLevels;
  // for each level
  for(INDEX iLevel=0; iLevel<ctLevels; iLevel++) {
    // create it
    CRememberedLevel *prl = new CRememberedLevel;
    // read it
    (*pstr)>>prl->rl_strFileName;
    //prl->rl_strmSessionState.
    // use readstream() !!!! @@@@

  }
  
};

/*
 * Write session state state into a stream.
 */
void CSessionState::Write_t(CTStream *pstr)  // throw char *
{
#if DEBUG_SYNCSTREAMDUMPING
  CPrintF( "Session state write: Sequence %d, Time %.2f\n", ses_iLastProcessedSequence, ses_tmLastProcessedTick);
#endif
  pstr->WriteID_t("SESV");
  (*pstr)<<INDEX(SESSIONSTATEVERSION_WITHBULLETTIME);
  // write time information and random seed
  (*pstr)<<ses_tmLastProcessedTick;
  (*pstr)<<ses_iLastProcessedSequence;
  (*pstr)<<ses_iLevel;
  (*pstr)<<ses_ulRandomSeed;
  (*pstr)<<ses_ulSpawnFlags;
  (*pstr)<<ses_tmSyncCheckFrequency;
  (*pstr)<<ses_iExtensiveSyncCheck;
  (*pstr)<<ses_tmLastSyncCheck;
  (*pstr)<<ses_ctMaxPlayers;
  (*pstr)<<ses_bWaitAllPlayers;
  (*pstr)<<ses_bPause;
  (*pstr)<<ses_bGameFinished;
  (*pstr)<<ses_fRealTimeFactor;
  // write session properties to stream
  (*pstr)<<_pNetwork->ga_strSessionName;
  pstr->Write_t(_pNetwork->ga_aubProperties, NET_MAXSESSIONPROPERTIES);

  // write world and its state
  WriteWorldAndState_t(pstr);
}

void CSessionState::WriteWorldAndState_t(CTStream *pstr)   // throw char *
{
  // delete all predictor entities before saving
  _pNetwork->ga_World.UnmarkForPrediction();
  _pNetwork->ga_World.DeletePredictors();

  // save engine build
  _pNetwork->WriteVersion_t(*pstr);

  // write world filename to stream
  (*pstr)<<_pNetwork->ga_fnmWorld;

  // write world situation
  _pNetwork->ga_World.LockAll();
  _pNetwork->ga_World.WriteState_t(pstr, TRUE);

  // write number of entities in timer list
  pstr->WriteID_t("TMRS");   // timers
  CListHead &lhTimers = _pNetwork->ga_World.wo_lhTimers;
  *pstr<<lhTimers.Count();
  // for each entity in the timer list
  {FOREACHINLIST(CRationalEntity, en_lnInTimers, lhTimers, iten) {
    // save its index in container
    *pstr<<iten->en_ulID;
  }}

  // write number of entities in mover list
  pstr->WriteID_t("MVRS");   // movers
  CListHead &lhMovers = _pNetwork->ga_World.wo_lhMovers;
  *pstr<<lhMovers.Count();
  // for each entity in the mover list
  {FOREACHINLIST(CMovableEntity, en_lnInMovers, lhMovers, iten) {
    // save its index in container
    *pstr<<iten->en_ulID;
  }}

  // write number of clients
  (*pstr)<<ses_apltPlayers.Count();
  // for all clients
  FOREACHINSTATICARRAY(ses_apltPlayers, CPlayerTarget, itclt) {
    // write to stream
    itclt->Write_t(pstr);
  }

  _pNetwork->ga_World.UnlockAll();
}

void CSessionState::WriteRememberedLevels_t(CTStream *pstr)
{
    // use writestream() !!!! @@@@

};

// remember current level
void CSessionState::RememberCurrentLevel(const CTString &strFileName)
{
  // if level is already remembered
  for(;;) {
    CRememberedLevel *prlOld = FindRememberedLevel(strFileName);
    if (prlOld==NULL) {
      break;
    }
    // remove it
    prlOld->rl_lnInSessionState.Remove();
    delete prlOld;
  }

  // create new remembered level
  CRememberedLevel *prlNew = new CRememberedLevel;
  ses_lhRememberedLevels.AddTail(prlNew->rl_lnInSessionState);
  // remember it
  prlNew->rl_strFileName = strFileName;
  WriteWorldAndState_t(&prlNew->rl_strmSessionState);
}

// find a level if it is remembered
CRememberedLevel *CSessionState::FindRememberedLevel(const CTString &strFileName)
{
  {FOREACHINLIST(CRememberedLevel, rl_lnInSessionState, ses_lhRememberedLevels, itrl) {
    CRememberedLevel &rl = *itrl;
    if (rl.rl_strFileName==strFileName) {
      return &rl;
    }
  }}
  return NULL;
}

// restore some old level
void CSessionState::RestoreOldLevel(const CTString &strFileName)
{
  // find the level
  CRememberedLevel *prlOld = FindRememberedLevel(strFileName);
  // it must exist
  ASSERT(prlOld!=NULL);
  // restore it
  try {
    prlOld->rl_strmSessionState.SetPos_t(0);
    _pTimer->SetCurrentTick(0.0f);
    ReadWorldAndState_t(&prlOld->rl_strmSessionState);
    _pTimer->SetCurrentTick(ses_tmLastProcessedTick);
  } catch (char *strError) {
    FatalError(TRANS("Cannot restore old level '%s':\n%s"), prlOld->rl_strFileName, strError);
  }
  // delete it
  delete prlOld;
}

// make synchronization test message and send it to server (if client), or add to buffer (if server)
void CSessionState::MakeSynchronisationCheck(void)
{
  if (!_cmiComm.cci_bClientInitialized) return;
  // not yet time
  if(ses_tmLastSyncCheck+ses_tmSyncCheckFrequency > ses_tmLastProcessedTick) {
    // don't check yet
    return;
  }

  // make local checksum
  ULONG ulLocalCRC;
  CRC_Start(ulLocalCRC);
  ChecksumForSync(ulLocalCRC, ses_iExtensiveSyncCheck);
  CRC_Finish(ulLocalCRC);

  // create sync-check
  CSyncCheck sc;
  ses_tmLastSyncCheck = ses_tmLastProcessedTick;
  sc.sc_tmTick = ses_tmLastSyncCheck;
  sc.sc_iSequence = ses_iLastProcessedSequence; 
  sc.sc_ulCRC = ulLocalCRC;
  sc.sc_iLevel = ses_iLevel;

  // NOTE: If local client, here we buffer the sync and send it to ourselves.
  // It's because synccheck is piggybacking session message buffer, so we acknowledge
  // the server to flush the buffer up to that sequence.
  
  // if on server
  if (_pNetwork->IsServer()) {
    // buffer the sync-check
    _pNetwork->ga_srvServer.AddSyncCheck(sc);
  }

  // create a message with sync check
  CNetworkMessage nmSyncCheck(MSG_SYNCCHECK);
  nmSyncCheck.Write(&sc, sizeof(sc));
  // send it to server
  _pNetwork->SendToServer(nmSyncCheck);
}

// forget all remembered levels
void CSessionState::ForgetOldLevels(void)
{
  {FORDELETELIST(CRememberedLevel, rl_lnInSessionState, ses_lhRememberedLevels, itrl) {
    delete &*itrl;
  }}
}


extern INDEX cli_bDumpSync;
extern INDEX cli_bDumpSyncEachTick;
void CSessionState::DumpSyncToFile_t(CTStream &strm, INDEX iExtensiveSyncCheck) // throw char *
{
  ULONG ulLocalCRC;
  CRC_Start(ulLocalCRC);
  ChecksumForSync(ulLocalCRC, iExtensiveSyncCheck);
  CRC_Finish(ulLocalCRC);
  strm.FPrintF_t("__________________________________________________________________________________\n", ulLocalCRC);
  strm.FPrintF_t("CRC: 0x%08X\n", ulLocalCRC);
  DumpSync_t(strm, iExtensiveSyncCheck);
}

#if DEBUG_SYNCSTREAMDUMPING
void CSessionState::DumpSyncToMemory(void)
{
  try
  {
    CTMemoryStream *pstrm = GetDumpStream();
    DumpSyncToFile_t(*pstrm);
  }
  catch (char *strError)
  {
    CPrintF("Cannot dump sync data: %s\n", strError);
  }
}
#endif

/* Session state loop. */
void CSessionState::SessionStateLoop(void)
{
  _pfNetworkProfile.StartTimer(CNetworkProfile::PTI_SESSIONSTATE_LOOP);

  // while there is something to do
  BOOL bSomethingToDo = TRUE;
  while (bSomethingToDo && !IsDisconnected()) {
    bSomethingToDo = FALSE;

    // if client was disconnected without a notice
    if (!_cmiComm.Client_IsConnected()) {
      // quit
      ses_strDisconnected = TRANS("Link or server is down");
    }

    CNetworkMessage nmMessage;
    // if there is some unreliable message
    if (_pNetwork->ReceiveFromServer(nmMessage)) {
      bSomethingToDo = TRUE;

      // if it is a gamestream message
      if (nmMessage.GetType() == MSG_GAMESTREAMBLOCKS) {
        ses_tvMessageReceived = _pTimer->GetHighPrecisionTimer();
        ses_bWaitingForServer = FALSE;

        // unpack the message
        CNetworkMessage nmUnpackedBlocks(MSG_GAMESTREAMBLOCKS);
        nmMessage.UnpackDefault(nmUnpackedBlocks);

        // while there are some more blocks in the message
        while (!nmUnpackedBlocks.EndOfMessage()) {
          // read a block to the gamestream
          ses_nsGameStream.ReadBlock(nmUnpackedBlocks);
        }

      // if it is a keepalive
      } else if (nmMessage.GetType() == MSG_KEEPALIVE) {
        // just remember time
        ses_tvMessageReceived = _pTimer->GetHighPrecisionTimer();
        _pNetwork->AddNetGraphValue(NGET_NONACTION, 0.5f); // non-action sequence

      // if it is pings message
      } else if (nmMessage.GetType() == MSG_INF_PINGS) {
        for(INDEX i=0; i<NET_MAXGAMEPLAYERS; i++) {
          CPlayerTarget &plt = ses_apltPlayers[i];
          BOOL bHas = 0;
          nmMessage.ReadBits(&bHas, 1);
          if (bHas) {
            if (plt.IsActive() && plt.plt_penPlayerEntity!=NULL) {
              INDEX iPing = 0;
              nmMessage.ReadBits(&iPing, 10);
              plt.plt_penPlayerEntity->en_tmPing = iPing/1000.0f;
            }
          }
        }
      // if it is chat message
      } else if (nmMessage.GetType() == MSG_CHAT_OUT) {
        // read the message
        ULONG ulFrom;
        CTString strFrom;
        nmMessage>>ulFrom;
        if (ulFrom==0) {
          nmMessage>>strFrom;
        }
        CTString strMessage;
        nmMessage>>strMessage;
        // print it
        PrintChatMessage(ulFrom, strFrom, strMessage);
      // otherwise
      } else {
        CPrintF(TRANS("Session state: Unexpected message during game: %s(%d)\n"),
          ErrorDescription(&MessageTypes, nmMessage.GetType()), nmMessage.GetType());
      }
    }

    CNetworkMessage nmReliable;
    // if there is some reliable message
    if (_pNetwork->ReceiveFromServerReliable(nmReliable)) {
      bSomethingToDo = TRUE;
      // if this is disconnect message
      if (nmReliable.GetType() == MSG_INF_DISCONNECTED) {
				// confirm disconnect
				CNetworkMessage nmConfirmDisconnect(MSG_REP_DISCONNECTED);			
				_pNetwork->SendToServerReliable(nmConfirmDisconnect);
        // report the reason
        CTString strReason;
        nmReliable>>strReason;
        ses_strDisconnected = strReason;
        CPrintF(TRANS("Disconnected: %s\n"), strReason);
        // disconnect
        _cmiComm.Client_Close();
      // if this is recon response
      } else if (nmReliable.GetType() == MSG_ADMIN_RESPONSE) {
        // just print it
        CTString strResponse;
        nmReliable>>strResponse;
        CPrintF("%s", "|"+strResponse+"\n");
      // otherwise
      } else {
        CPrintF(TRANS("Session state: Unexpected reliable message during game: %s(%d)\n"),
          ErrorDescription(&MessageTypes, nmReliable.GetType()), nmReliable.GetType());
      }
    }
  }

  // if network client and not waiting for server
  if (_pNetwork->IsNetworkEnabled() && !_pNetwork->IsServer() && !ses_bWaitingForServer) {
    // check when last message was received.
    if (ses_tvMessageReceived.tv_llValue>0 &&
      (_pTimer->GetHighPrecisionTimer()-ses_tvMessageReceived).GetSeconds()>net_tmDisconnectTimeout &&
      ses_strDisconnected=="") {
      ses_strDisconnected = TRANS("Connection timeout");
      CPrintF(TRANS("Disconnected: %s\n"), (const char*)ses_strDisconnected);
    }
  }

  // if pause state should be changed
  if (ses_bPause!=ses_bWantPause) {
    // send appropriate packet to server
    CNetworkMessage nmReqPause(MSG_REQ_PAUSE);
    nmReqPause<<(INDEX&)ses_bWantPause;
    _pNetwork->SendToServer(nmReqPause);
  }

  // dump sync data if needed
  if( cli_bDumpSync)
  {
    cli_bDumpSync = FALSE;
    try
    {
      CTFileStream strmFile;
      CTString strFileName = CTString("temp\\syncdump.txt");
      strmFile.Create_t(CTString("temp\\syncdump.txt"), CTStream::CM_TEXT);
      
#if DEBUG_SYNCSTREAMDUMPING
      if( cli_bDumpSyncEachTick)
      {
        // get size and buffer from the stream
        void *pvBuffer;
        SLONG slSize;
        ses_pstrm->LockBuffer(&pvBuffer, &slSize);
        strmFile.Write_t(pvBuffer, slSize);
        ses_pstrm->UnlockBuffer();
      }
      else
#endif
      {
        DumpSyncToFile_t(strmFile, ses_iExtensiveSyncCheck);
      }
      // inform user
      CPrintF("Sync data dumped to '%s'\n", strFileName);
    }
    catch (char *strError)
    {
      CPrintF("Cannot dump sync data: %s\n", strError);
    }
  }

  // if some client settings changed
  if (!ses_sspParams.IsUpToDate()) {
    // remember new settings
    ses_sspParams.Update();
    // send message to server
    CNetworkMessage nmSet(MSG_SET_CLIENTSETTINGS);
    nmSet<<ses_sspParams;
    _pNetwork->SendToServerReliable(nmSet);
  }

  _pfNetworkProfile.StopTimer(CNetworkProfile::PTI_SESSIONSTATE_LOOP);
}

/* Get number of active players. */
INDEX CSessionState::GetPlayersCount(void)
{
  INDEX ctPlayers = 0;
  FOREACHINSTATICARRAY(ses_apltPlayers, CPlayerTarget, itplt) {
    if (itplt->IsActive()) {
      ctPlayers++;
    }
  }
  return ctPlayers;
}

/* Remember predictor positions of all players. */
void CSessionState::RememberPlayerPredictorPositions(void)
{
  // for each active player
  FOREACHINSTATICARRAY(ses_apltPlayers, CPlayerTarget, itplt) {
    CPlayerTarget &plt = *itplt;
    if (plt.IsActive()) {
      // remember its current, or predictor position
      CEntity *pen = plt.plt_penPlayerEntity;
      if (pen->IsPredicted()) {
        pen = pen->GetPredictor();
      }
      plt.plt_vPredictorPos = pen->GetPlacement().pl_PositionVector;
    }
  }
}

/* Get player position. */
const FLOAT3D &CSessionState::GetPlayerPredictorPosition(INDEX iPlayer)
{
  return ses_apltPlayers[iPlayer].plt_vPredictorPos;
}

CPredictedEvent::CPredictedEvent(void)
{
}

// check an event for prediction, returns true if already predicted
BOOL CSessionState::CheckEventPrediction(CEntity *pen, ULONG ulTypeID, ULONG ulEventID)
{
  // if prediction is not involved
  if ( !( pen->GetFlags() & (ENF_PREDICTOR|ENF_PREDICTED|ENF_WILLBEPREDICTED) ) ){
    // not predicted
    return FALSE;
  }

  // find eventual prediction tail
  if (pen->IsPredictor()) {
    pen = pen->GetPredictionTail();
  }

  // gather all event relevant data
  ULONG ulEntityID = pen->en_ulID;
  TIME tmNow = _pTimer->CurrentTick();

  BOOL bPredicted = FALSE;
  // for each active event
  INDEX ctpe = ses_apeEvents.Count();
  {for(INDEX ipe=0; ipe<ctpe; ipe++) {
    CPredictedEvent &pe = ses_apeEvents[ipe];
    // if the event is too old
    if (pe.pe_tmTick<tmNow-5.0f) {
      // delete it from list
      pe = ses_apeEvents[ctpe-1];
      ctpe--;
      ipe--;
      continue;
    }
    // if the event is same as the new one
    if (pe.pe_tmTick==tmNow &&
        pe.pe_ulEntityID==ulEntityID && 
        pe.pe_ulTypeID==ulTypeID && 
        pe.pe_ulEventID==ulEventID) {
      // it was already predicted
      bPredicted = TRUE;
      break;
    }
  }}

  // remove unused events at the end
  if (ctpe==0) {
    ses_apeEvents.PopAll();
  } else {
    ses_apeEvents.PopUntil(ctpe-1);
  }

  // if not predicted
  if (!bPredicted) {
    // add the new one
    CPredictedEvent &pe = ses_apeEvents.Push();
    pe.pe_tmTick     = tmNow;
    pe.pe_ulEntityID = ulEntityID;
    pe.pe_ulTypeID   = ulTypeID;
    pe.pe_ulEventID  = ulEventID;
  }

  return bPredicted;
}
