Host.cpp
上传用户:royluo
上传日期:2007-01-05
资源大小:1584k
文件大小:37k
源码类别:

游戏

开发平台:

Visual C++

  1. /*****************************************************************************
  2. *                                                                             
  3. *   Host.cpp                                                            
  4. *                                                                             
  5. *   Electrical Engineering Faculty - Software Lab                             
  6. *   Spring semester 1998                                                      
  7. *                                                                             
  8. *   Tanks game                                                                
  9. *                                                                             
  10. *   Module description: Handles the network messages directed to the host of 
  11. *                       the game session. Acts as the game judge using the game
  12. *                       manager of the local player on this machine.
  13. *                       
  14. *                                                                             
  15. *   Authors: Eran Yariv - 28484475                                           
  16. *            Moshe Zur  - 24070856                                           
  17. *                                                                            
  18. *                                                                            
  19. *   Date: 23/09/98                                                           
  20. *                                                                            
  21. ******************************************************************************/
  22. #include "stdafx.h"
  23. #include "Tanks.h"
  24. #include "Host.h"
  25. #include <time.h>
  26. #include <math.h>
  27. #ifdef GATHER_SYNC_STATS
  28.     #define INC_SYNC_MSG(t)                     m_TanksSyncStats[(t)].dwSyncMsgsArrived++;
  29.     #define INC_SYNC_MSG_SIZE(t,sz)             m_TanksSyncStats[(t)].dwTotSyncMsgSize+=(sz);
  30.     #define INC_BONUS_MISMATCH(t)               m_TanksSyncStats[(t)].dwBonusMismatch++;
  31.     #define INC_MINES_MISMATCH(t)               m_TanksSyncStats[(t)].dwMinesMismatch++;
  32.     #define INC_ZOMBIE_MISMATCH(t)              m_TanksSyncStats[(t)].dwZombieMismatch++;
  33.     #define INC_OTHER_TANK_STATUS_MISMATCH(t)   m_TanksSyncStats[(t)].dwOtherTankStatusMismatch++;
  34.     #define INC_MISSING_TANK(t)                 m_TanksSyncStats[(t)].dwMissingTanks++;
  35.     #define INC_EXTRA_TANKS(t)                  m_TanksSyncStats[(t)].dwExtraTanks++;
  36.     #define INC_ZOMBIE_IN(t)                    m_TanksSyncStats[(t)].dwZombieIn++;
  37.     #define INC_ZOMBIE_OUT(t)                   m_TanksSyncStats[(t)].dwZombieOut++;
  38.     #define UPDATE_SYNC_MSG_TIME(t)             UpdateSyncMsgTime((t));
  39.     #define ZERO_TANK_SYNC_STATS(t)             ZeroTankSyncStats((t));
  40.     #define PRINT_TANK_SYNC_STATS(t)            PrintTankSyncStats((t));
  41. #else   // !defined GATHER_SYNC_STATS
  42.     #define INC_SYNC_MSG(t)    
  43.     #define INC_SYNC_MSG_SIZE(t,sz)                 
  44.     #define INC_BONUS_MISMATCH(t)               
  45.     #define INC_MINES_MISMATCH(t)               
  46.     #define INC_ZOMBIE_MISMATCH(t)
  47.     #define INC_OTHER_TANK_STATUS_MISMATCH(t)   
  48.     #define INC_MISSING_TANK(t)                 
  49.     #define INC_EXTRA_TANKS(t)                  
  50.     #define INC_ZOMBIE_IN(t)    
  51.     #define INC_ZOMBIE_OUT(t)   
  52.     #define UPDATE_SYNC_MSG_TIME(t)             
  53.     #define ZERO_TANK_SYNC_STATS(t)             
  54.     #define PRINT_TANK_SYNC_STATS(t)            
  55. #endif // defined GATHER_SYNC_STATS
  56. CHost::CHost (CCommManager &CommManager) : 
  57.     m_CommManager(CommManager),
  58.     m_GameManager(TANKS_APP->m_gGameManager),
  59.     m_Timer (TANKS_APP->m_gTimer),
  60.     m_Message(CommManager.ExposeMessage()),
  61.     m_bComplexity (BYTE(TANKS_APP->GetStoredMapComplexity())),
  62. #if defined (REAL_RANDOM)
  63.     m_wSeed (WORD(time( NULL )))   // Randomness according to current time
  64. #else
  65.     m_wSeed (WORD(10))
  66. #endif
  67. {
  68.     for (int i = 0; i < MAX_TANKS; i++)
  69.     {
  70.         m_aPlayersID[i] = CCommManager::INVALID_PLAYER_ID;
  71.         m_JudgeViewOfTanks[i].Exists = FALSE;
  72.         m_JudgeViewOfTanks[i].Zombie = FALSE;
  73.         m_JudgeViewOfTanks[i].BadRTTCount = 0;
  74.         m_JudgeViewOfTanks[i].LastRTTWasGood = TRUE;
  75.     }
  76. }
  77. CHost::~CHost()
  78. {
  79.     for (UINT i = 0; i < MAX_TANKS; i++)
  80.         if (CCommManager::INVALID_PLAYER_ID != m_aPlayersID[i])
  81.             // Player still exists upon termination - display its sync stats
  82.             PRINT_TANK_SYNC_STATS (i);
  83. }
  84. /*------------------------------------------------------------------------------
  85.   Function: HandleRequestTank
  86.   Purpose:  Handles the tank request message send by a new game player.
  87.             Position the new tank in a vacant and safe (far enough from all 
  88.             other tanks) place, and maps a vacant ID for the player (the 
  89.             requested ID if still vacant). The ADD_TANK message is then send to 
  90.             all game players. 
  91.             This is also a trigger for the ADD_BOARD message to the new player.
  92.   Input:    bReqTankID - the players requested ID (defines the tanks color).
  93.   Output:   None.
  94.   Remarks:  
  95. ------------------------------------------------------------------------------*/
  96. void
  97. CHost::HandleRequestTank (BYTE bReqTankID)
  98. {
  99.     CMessage::MessageData MsgData;
  100.     //
  101.     //  Prepare add tank message
  102.     //
  103.     int x,y,dir;
  104.         // Find a safe and vacant position, and point the tank toward the 
  105.         // center of the map:
  106.     LocateNewTankPos (x,y,dir);
  107.     MsgData.TankParam.XPos = x;
  108.     MsgData.TankParam.YPos = y;
  109.     MsgData.TankParam.Direction = dir;
  110.         // Get a vacant ID:
  111.     MsgData.TankParam.ID = MapAndGetAvailTankID(bReqTankID); 
  112.     MsgData.TankParam.Local = FALSE;
  113.     MsgData.TankParam.ShieldLevel = TANK_INIT_SHIELD_LEVEL;
  114.     MsgData.TankParam.Shells = TANK_INIT_SHELLS;
  115.     MsgData.TankParam.Bullets = TANK_INIT_BULLETS;
  116.     MsgData.TankParam.Mines = TANK_INIT_MINES;
  117.     MsgData.TankParam.FireRateBonusSecsLeft = 0;
  118.     MsgData.TankParam.Bomber = FALSE;
  119.     MsgData.TankParam.FastFire = FALSE;
  120.     CMessage Msg(CMessage::ADD_TANK, MsgData, SERVER_MSG);
  121.     m_Message.m_dwLength = Msg.GetBuffer(m_Message.m_aBuffer);
  122.     //
  123.     // Send add tank message
  124.     //
  125.     SendToAllButSender ();    // All players get add remote tank
  126.     // Requesting tank get add local tank:
  127.     Msg.m_UnionData.TankParam.Local = TRUE;
  128.     Msg.GetBuffer(m_Message.m_aBuffer);
  129.     ReplyToSender ();
  130.     PlayerJoinsGame (MsgData.TankParam.ID);
  131.     //
  132.     //  Add board message:
  133.     //
  134.     MsgData.BoardParam.Seed = m_wSeed;
  135.     MsgData.BoardParam.Complexity = m_bComplexity;
  136.     CMessage BoardMsg (CMessage::ADD_BOARD, MsgData, SERVER_MSG);
  137.     m_Message.m_dwLength = BoardMsg.GetBuffer(m_Message.m_aBuffer);
  138.     ReplyToSender ();
  139. }
  140. /*------------------------------------------------------------------------------
  141.   Function: ValueCloseEnough
  142.   Purpose:  A hueristic comparison function. If one of the values is smaller 
  143.             then the minimal tolerance allowed, then perform an accurate 
  144.             comparison, otherwise - allways return "equal".
  145.   Input:    val1, val2 - the values to be compared
  146.             MinTolerance - the value which underneath, an accurate comparison
  147.                            should be performed.
  148.   Output:   Return TRUE if values are "equal"
  149.   Remarks:  Used to decide if a remote player needs an update after sending its
  150.             game checksum.
  151. ------------------------------------------------------------------------------*/
  152. BOOL 
  153. CHost::ValueCloseEnough (int val1, int val2, int MinTolerance)
  154. {
  155.     if ((val1 < MinTolerance) || (val2 < MinTolerance))
  156.         return (val1 == val2);
  157.     return TRUE;
  158. }
  159.     
  160. /*------------------------------------------------------------------------------
  161.   Function: HandleCheckSum
  162.   Purpose:  Handles the check sum message. The message contains the remote 
  163.             player's game status, and is checked against the host game status.
  164.             Updates are send only when the mismatch affects the remote player's
  165.             game severely.
  166.   Input:    msg - the decoded check sum message
  167.             bJudgeReporting - flag indicating the sender is the local player on
  168.                               the host machine (nick name - judge)
  169.   Output:   None.
  170.   Remarks:  The reporting tank's position is dispatched to all player 
  171.             immediatly.
  172. ------------------------------------------------------------------------------*/
  173. void 
  174. CHost::HandleCheckSum (CMessage &msg, BOOL bJudgeReporting)
  175. {
  176.     int i;  // For loops
  177.     int iReportingTank = LookupTankID (m_idFrom);
  178.         // This flag indicates that the host of the session is still working,
  179.         // but its tank was destroyed, means we can ignore some parts of the
  180.         // message:
  181.     BOOL bDeadHostReport = msg.m_UnionData.CheckSumParam.DeadHostReport;
  182.     ASSERT (!bDeadHostReport || bJudgeReporting);
  183.     if (!bDeadHostReport)
  184.     {
  185.         UPDATE_SYNC_MSG_TIME (iReportingTank);
  186.         INC_SYNC_MSG (iReportingTank);
  187.         INC_SYNC_MSG_SIZE (iReportingTank,
  188.                            sizeof (FixedSizeChkSumType) + 
  189.                            msg.m_UnionData.CheckSumParam.NumberOfTanks * 
  190.                             sizeof (SingleTankChkSumType));
  191.     }
  192.     // Update the judge's view of other tanks existance:
  193.     for (i=0; i<MAX_TANKS; i++)
  194.         m_JudgeViewOfTanks[i].Exists = m_GameManager.IsAlive (i);
  195.     ASSERT(bJudgeReporting ||   // Either dead judge reports or reporting tank exists
  196.            msg.m_UnionData.CheckSumParam.TanksChkSums[iReportingTank].TankExists);
  197.     if (!bDeadHostReport)
  198.     {   // The reporting tank is alive -
  199.         // 1st we store it's ammo counts and FF:
  200.         m_JudgeViewOfTanks[iReportingTank].Shells = 
  201.             msg.m_UnionData.CheckSumParam.TanksChkSums[iReportingTank].Shells;
  202.         m_JudgeViewOfTanks[iReportingTank].Bullets = 
  203.             msg.m_UnionData.CheckSumParam.TanksChkSums[iReportingTank].Bullets;
  204.         m_JudgeViewOfTanks[iReportingTank].Mines = 
  205.             msg.m_UnionData.CheckSumParam.TanksChkSums[iReportingTank].Mines;
  206.         m_JudgeViewOfTanks[iReportingTank].FastFire = 
  207.             msg.m_UnionData.CheckSumParam.TanksChkSums[iReportingTank].FastFire;
  208.         // Than we send to all other it's position:
  209.         SendTankPos(iReportingTank,
  210.                     msg.m_UnionData.CheckSumParam.XPos,
  211.                     msg.m_UnionData.CheckSumParam.YPos);
  212.     }
  213.     // Finally we compare the reporting tank's view of others' ammo counts:
  214.     for (i = 0; i<MAX_TANKS; i++) {
  215.         // Skip reporting tank
  216.         if (i == iReportingTank)
  217.             continue;
  218.         // Update reporting tank with tanks he isn't aware off:
  219.         if (!m_JudgeViewOfTanks[i].Exists &&
  220.             msg.m_UnionData.CheckSumParam.TanksChkSums[i].TankExists)
  221.         {
  222.             SendRemoveTank(i);
  223.             INC_EXTRA_TANKS(i)
  224.             continue;
  225.         } else
  226.         if (m_JudgeViewOfTanks[i].Exists &&
  227.             !msg.m_UnionData.CheckSumParam.TanksChkSums[i].TankExists)
  228.         {
  229.             SendAddTank(i);
  230.             INC_MISSING_TANK(i)
  231.             continue;
  232.         }
  233.         // Now both the reporter and the judge agree upon tanks existance / inexistance
  234.         if (!m_JudgeViewOfTanks[i].Exists)
  235.             continue;
  236.         // TODO: this comparison may be to strict, since we ignore the
  237.         // reporting time of the counter. We need to consider a more tolerant
  238.         // comparison.
  239.         if (!ValueCloseEnough (m_JudgeViewOfTanks[i].Shells,
  240.                                msg.m_UnionData.CheckSumParam.TanksChkSums[i].Shells,
  241.                                3) ||
  242.             !ValueCloseEnough (m_JudgeViewOfTanks[i].Bullets,
  243.                                msg.m_UnionData.CheckSumParam.TanksChkSums[i].Bullets,
  244.                                6) ||
  245.             !ValueCloseEnough (m_JudgeViewOfTanks[i].Mines,
  246.                                msg.m_UnionData.CheckSumParam.TanksChkSums[i].Mines,
  247.                                2) ||
  248.             (m_JudgeViewOfTanks[i].FastFire != 
  249.              msg.m_UnionData.CheckSumParam.TanksChkSums[i].FastFire)
  250.              )
  251.         {
  252.             SendTankStatus(i);
  253.             INC_OTHER_TANK_STATUS_MISMATCH(i)
  254.         }
  255.         // Update zombies:
  256.         if (m_JudgeViewOfTanks[i].Zombie != 
  257.                 msg.m_UnionData.CheckSumParam.TanksChkSums[i].Zombie)
  258.         {
  259.             SendTankZombie(i, m_JudgeViewOfTanks[i].Zombie, FALSE);
  260.             INC_ZOMBIE_MISMATCH (i)
  261.         }
  262.     }
  263.     // A judge gets to set a few things and other just follow.
  264.     // A judge is the nickname of the local player on the server's machine.
  265.     if (bJudgeReporting)
  266.     {   // The judge sets the following game checksums for the others to compare with:
  267.         // Save current bonus status:
  268.         m_JudgeViewOfBonus = msg.m_UnionData.CheckSumParam.ActiveBonusType;
  269.         // Save current game sectors mine statuses:
  270.         memcpy (m_JudgeViewOfMines, 
  271.                 msg.m_UnionData.CheckSumParam.MinesSectorsChkSum, 
  272.                 sizeof(BYTE) * (MAX_SECTOR + 1));
  273.     }
  274.     else
  275.     {   // Non-judge, compare with judge settings
  276.         // Compare current bonus status:
  277.         if (msg.m_UnionData.CheckSumParam.ActiveBonusType != 
  278.             m_JudgeViewOfBonus)
  279.         {   // Bonus type mismatch
  280.             SendBonusStatus();
  281.             INC_BONUS_MISMATCH (iReportingTank);
  282.         }
  283.         // Compare current game sectors mine statuses:
  284.         for (i=0; i <= MAX_SECTOR; i++)
  285.             if (m_JudgeViewOfMines[i] !=
  286.                 msg.m_UnionData.CheckSumParam.MinesSectorsChkSum[i])
  287.             {   // Sector i mines mismatch
  288.                 SendMinesStatus (i);
  289.                 INC_MINES_MISMATCH (iReportingTank);
  290.             }
  291.     }
  292. }
  293. /*------------------------------------------------------------------------------
  294.   Function: LocateNewTankPos
  295.   Purpose:  Finds a position for the new tank. Make sure the rectangle is vacant
  296.             and that the distance from other tanks is suitable. Calculates the 
  297.             tanks direction to point to the center of the map.
  298.   Input:    XPos,YPos, Dir - output params.
  299.   Output:   None.
  300.   Remarks:  
  301. ------------------------------------------------------------------------------*/
  302. void
  303. CHost::LocateNewTankPos(int &XPos, int &YPos, int &Dir)
  304. {
  305. #define PI              double( 3.14159265359 )
  306. #define MAX_TRIES        10
  307. #define MIN_SQR_DIST     (100 * 100)
  308.     DWORD dwMaxDist = 0;
  309.     CSize size(TANK_WIDTH, TANK_HEIGHT);
  310.     CPoint BestPos;
  311.     for (int i=0; i<MAX_TRIES; i++)
  312.     {   // Start looking
  313.         CPoint pos;
  314.         TANKS_APP->m_gGameManager.FindVacantRect(size, pos);
  315.         DWORD dwCurDist = TANKS_APP->m_gGameManager.GetMinDistanceFromTanks (pos);
  316.         if (MIN_SQR_DIST < dwCurDist)
  317.         {   // We found a spot that satisfies us - stop looking
  318.             BestPos = pos;
  319.             break;
  320.         }
  321.         if (dwMaxDist < dwCurDist)
  322.         {   // Found better position
  323.             dwMaxDist = dwCurDist;
  324.             BestPos = pos;
  325.         }
  326.     }
  327.     XPos = BestPos.x;
  328.     YPos = BestPos.y;
  329.     // Let's place all tanks facing the center of the board:
  330.     double dx = XPos - (MAP_WIDTH / 2);
  331.     double dy = YPos - (MAP_HEIGHT / 2);
  332.     if (0.0 == dx)
  333.     {   // Either point up or down
  334.         Dir = (dy > 0.0) ? 18 : 6;
  335.     }
  336.     double alpha = atan2 (dy, dx) * 180.0 / PI;     // -180 <= alpha <= 180
  337.     Dir = (MAX_DIRECTIONS - int(alpha / 15.0)) % MAX_DIRECTIONS;
  338.     ASSERT (Dir >= 0 && Dir < MAX_DIRECTIONS);
  339.     if (Dir < 0 || Dir >= MAX_DIRECTIONS)
  340.         Dir = 0;
  341. }
  342. /*------------------------------------------------------------------------------
  343.   Function: MapAndGetAvailTankID
  344.   Purpose:  Get the new tank's ID. Try to satisfy the player's request.
  345.   Input:    bReqTankID - the requested tank ID.
  346.   Output:   The new tanks ID.
  347.   Remarks:  
  348. ------------------------------------------------------------------------------*/
  349. BYTE
  350. CHost::MapAndGetAvailTankID(BYTE bReqTankID)
  351. {
  352.     BYTE bInd = bReqTankID; // Assume we take the request
  353.     if (m_aPlayersID[bInd] != CCommManager::INVALID_PLAYER_ID) {
  354.         // We can't satisfy the request - lets find a vacant place:
  355.         for (int i = 0; i < MAX_TANKS; i++)
  356.             if (CCommManager::INVALID_PLAYER_ID == m_aPlayersID[i])
  357.                 break;
  358.         ASSERT(i != MAX_TANKS);     // We must have a vacant place
  359.         if (i == MAX_TANKS)         // In case of emergency - take any place:
  360.             i = bReqTankID;
  361.         bInd = BYTE(i);
  362.     }
  363.     m_aPlayersID[bInd] = m_idFrom;
  364.     return bInd;
  365. }
  366. int  
  367. CHost::LookupTankID (DPID id)
  368. {   // Returns 0..MAX_TANKS-1 or -1 (no found) or -2 (host)
  369.     if (id == m_CommManager.GetHostID())
  370.         return -2; //Host
  371.     for (UINT i = 0; i < MAX_TANKS; i++)
  372.         if (id == m_aPlayersID[i])
  373.             return int(i);
  374.     return -1; // Not found
  375. }
  376. /*------------------------------------------------------------------------------
  377.   Function: HandleMessage
  378.   Purpose:  Handle message send to the game host from a game player (either 
  379.             remote or local).
  380.   Input:    idFrom - the DirectPlay ID of the sending player
  381.             bFromJudge - flag indicating the message was send by the local tank.
  382.   Output:   None.
  383.   Remarks:  
  384. ------------------------------------------------------------------------------*/
  385. void
  386. CHost::HandleMessage(DPID idFrom, BOOL bFromJudge)
  387. {
  388.         // Save message originator locally for reply options:
  389.     m_idFrom = idFrom;
  390.         // Decode the buffer into a message:
  391.     CMessage Msg(m_Message.m_aBuffer, (BYTE)m_Message.m_dwLength, SERVER_MSG);
  392.         // Update table of player's RTTs:
  393.     UpdatePlayerRTT (Msg.GetTime());
  394.     switch (Msg.GetType()) {
  395.         case CMessage::MANOUVER_SET:// All players get the manouver set (excluding the sender)
  396.             // One of the tanks is dispatching his manouver set notification to all:
  397.             // The message contains the data that should be
  398.             // send to all other tanks except the sender of this message.
  399.             // The message params are exactly the same, so we simply re-dispatch it!
  400.             NET_GAME_TRACE (("Host got MANOUVER_SET from tank %d (game time = %d)", 
  401.                             LookupTankID(m_idFrom), Msg.GetTime ()));
  402.             SendToAllButSender();    
  403.             break;
  404.         case CMessage::REMOVE_TANK: // Tell all the players a tank dies
  405.             NET_GAME_TRACE (("Host got REMOVE_TANK from tank %d (game time = %d)", 
  406.                             LookupTankID(m_idFrom), Msg.GetTime ()));
  407.             SendToAllButSender ();
  408.             break;
  409.             
  410.         case CMessage::ADD_BONUS:   // Tell all the players a bonus is added / eaten
  411.             // Set message time to current game time:
  412.             *(PDWORD(&m_Message.m_aBuffer[1])) = TANKS_APP->m_gTimer.GetLocalTime();
  413.             NET_GAME_TRACE (("Host got ADD_BONUS from tank %d (game time = %d)", 
  414.                             LookupTankID(m_idFrom), *(PDWORD(&m_Message.m_aBuffer[1]))));
  415.             SendToAll ();
  416.             break;
  417.         case CMessage::ADD_BOMBER:  // Tell all the players a bomber is in the air
  418.             NET_GAME_TRACE (("Host got ADD_BOMBER from tank %d (game time = %d)", 
  419.                             LookupTankID(m_idFrom), Msg.GetTime ()));
  420.             SendToAll ();
  421.             break;
  422.         case CMessage::REQUEST_TANK:    // A new tank (player) is joining the game session:
  423.             NET_GAME_TRACE (("Host got REQUEST_TANK from tank %d (game time = %d)", 
  424.                             LookupTankID(m_idFrom), Msg.GetTime ()));
  425.             HandleRequestTank (BYTE(Msg.m_UnionData.TankRequestParam.bID));
  426.             break;
  427.         case CMessage::CHECK_SUM:       // Check sum message from a remote or local player
  428.             NET_SYNC_TRACE (("Host got CHECK_SUM from tank %d (game time = %d)", 
  429.                             LookupTankID(m_idFrom), Msg.GetTime ()));
  430.             HandleCheckSum (Msg, bFromJudge);
  431.             break;
  432.         case CMessage::CHAT:            // Dispatch msg to all players except sender:
  433.             SendToAll();
  434.             break;
  435.         default:
  436.             NET_GAME_TRACE (("Host got unknown game message from tank %d (game time = %d)", 
  437.                             LookupTankID(m_idFrom), Msg.GetTime ()));
  438.             ASSERT(FALSE);
  439.     }
  440. }
  441. void
  442. CHost::RemovePlayerFromArray(DPID idPlayer)
  443. {
  444.     int i = LookupTankID (idPlayer);
  445.     ASSERT (i >= 0);
  446.     if (i < 0)  // This should never happen
  447.         return;
  448.     PRINT_TANK_SYNC_STATS (i);
  449.     m_aPlayersID[i] = CCommManager::INVALID_PLAYER_ID;
  450.     if (m_GameManager.IsAlive (i)) 
  451.     {    // Non-gracefull player disconnection happaned
  452.         CMessage::MessageData Params;
  453.             // Create tank removal notification message:
  454.         Params.TankRemoveParam.ID = BYTE(i);
  455.         Params.TankRemoveParam.Explode = TRUE;
  456.         // Compose message from data:
  457.         CMessage Msg(CMessage::REMOVE_TANK, Params, SERVER_MSG);
  458.         // Pack message to buffer:
  459.         m_Message.m_dwLength = Msg.GetBuffer(m_Message.m_aBuffer);
  460.         SendToAll ();
  461.     }
  462. }
  463. void
  464. CHost::SendToAllButSender()
  465. {
  466.     for (UINT i = 0; i < MAX_TANKS; i++)
  467.         if ((m_aPlayersID[i] != m_idFrom) &&
  468.             (m_aPlayersID[i] != CCommManager::INVALID_PLAYER_ID)) {
  469.                 m_CommManager.SendAsHost (m_aPlayersID[i]);
  470.         }
  471. }
  472. void
  473. CHost::SendToAll ()
  474. {
  475.     for (UINT i = 0; i < MAX_TANKS; i++)
  476.         if (m_aPlayersID[i] != CCommManager::INVALID_PLAYER_ID) 
  477.         {   // A valid player
  478.                 m_CommManager.SendAsHost (m_aPlayersID[i]);    // Send as host
  479.         }
  480. }
  481. void 
  482. CHost::SendBonusStatus()
  483. {
  484.     CMessage::MessageData MsgData;
  485.     // Get Bonus state from GameManager:
  486.     m_GameManager.GetBonusState(MsgData);
  487.     // Compose new message from data:
  488.     CMessage Msg(CMessage::ADD_BONUS, MsgData, SERVER_MSG);
  489.     // Pack message to buffer:
  490.     m_Message.m_dwLength = Msg.GetBuffer(m_Message.m_aBuffer);
  491.     // Send to last reported:
  492.     ReplyToSender();
  493. }
  494. void 
  495. CHost::SendAddTank(int iTankID)
  496. {
  497.     CMessage::MessageData MsgData;
  498.     // Get tank's state and position:
  499.     BOOL bRes = m_GameManager.GetTankStatusAndPos(iTankID, MsgData);
  500.     if (!bRes)
  501.         return; // Tank just died between our fingers...
  502.     // Under no-circumstances should a remote player get it's local tanks this way:
  503.     MsgData.TankParam.Local = FALSE;
  504.     CMessage Msg(CMessage::ADD_TANK, MsgData, SERVER_MSG);
  505.     // Pack message to buffer:
  506.     m_Message.m_dwLength = Msg.GetBuffer(m_Message.m_aBuffer);
  507.     // Send to last reporter:
  508.     ReplyToSender();
  509. }
  510. void
  511. CHost::SendTankPos 
  512. (   int iTankID,                // Setting position of tank ID
  513.     int nXPos,                  // X Pos
  514.     int nYPos)                  // Y Pos
  515. {
  516.     CMessage::MessageData MsgData;
  517.     MsgData.TankPosParam.XPos = nXPos;
  518.     MsgData.TankPosParam.YPos = nYPos;
  519.     MsgData.TankPosParam.ID   = iTankID;
  520.     // Compose message from data:
  521.     CMessage Msg(CMessage::SET_TANK_POS, MsgData, SERVER_MSG);
  522.     // Pack message to buffer:
  523.     m_Message.m_dwLength = Msg.GetBuffer(m_Message.m_aBuffer);
  524.     // Send to all other tanks, except reporter:
  525.     SendToAllButSender();
  526. }
  527. void
  528. CHost::SendTankZombie(int iTankID, BOOL bZombie, BOOL bToAll)
  529. {
  530.     CMessage::MessageData MsgData;
  531.     MsgData.TankZombieParam.ID = BYTE(iTankID);
  532.     MsgData.TankZombieParam.Zombie = BYTE(bZombie);
  533.     // Compose message from data:
  534.     CMessage Msg(CMessage::SET_TANK_ZOMBIE, MsgData, SERVER_MSG);
  535.     // Pack message to buffer:
  536.     m_Message.m_dwLength = Msg.GetBuffer(m_Message.m_aBuffer);
  537.     if (bToAll) 
  538.     {   // Send to all tanks:
  539.         SendToAll();
  540.     } else
  541.     {
  542.         ReplyToSender();
  543.     }
  544. }
  545. void
  546. CHost::SendTankStatus(int iTankID)
  547. {
  548.     CMessage::MessageData MsgData;
  549.     // Get tank's status:
  550.     BOOL bRes = m_GameManager.GetTankStatus(iTankID, MsgData);
  551.     if (! bRes)
  552.         return;
  553.     // Override the judge's game-manager ammo counters and FF flag, with
  554.     // data from the judge's tables:
  555.     MsgData.TankParam.Shells    = m_JudgeViewOfTanks[iTankID].Shells;
  556.     MsgData.TankParam.Bullets   = m_JudgeViewOfTanks[iTankID].Bullets;
  557.     MsgData.TankParam.Mines     = m_JudgeViewOfTanks[iTankID].Mines;
  558.     MsgData.TankParam.FastFire  = 
  559.         m_JudgeViewOfTanks[iTankID].FastFire;
  560.     // Under no-circumstances should a remote player get it's local tanks this way:
  561.     MsgData.TankParam.Local = FALSE;
  562.     // Compose message from data:
  563.     CMessage Msg(CMessage::SET_TANK_STATUS, MsgData, SERVER_MSG);
  564.     // Pack message to buffer:
  565.     m_Message.m_dwLength = Msg.GetBuffer(m_Message.m_aBuffer);
  566.     // Send to last reporter:
  567.     ReplyToSender();
  568. }
  569. void
  570. CHost::SendRemoveTank(int iTankID)
  571. {
  572.     CMessage::MessageData MsgData;
  573.     MsgData.TankRemoveParam.ID = BYTE(iTankID);
  574.     MsgData.TankRemoveParam.Explode = TRUE;
  575.     // Compose message from data:
  576.     CMessage Msg(CMessage::REMOVE_TANK, MsgData, SERVER_MSG);
  577.     // Pack message to buffer:
  578.     m_Message.m_dwLength = Msg.GetBuffer(m_Message.m_aBuffer);
  579.     // Send to last reporter:
  580.     ReplyToSender();
  581. }
  582. void CHost::SendMinesStatus(int iSector)
  583. {
  584.     /*  NOTICE: This is a special case. Since the information on all the mines in
  585.         a given sector is of variable length and may get very big, we don't construct
  586.         a structure for it in the MessageData union like we do for all the other
  587.         message type.
  588.         Instead, we make the encoding in-place directly to the DPlay output buffer
  589.         (using a static member function of CMessage) and don't keep any message structure.
  590.     */
  591.     DWORD AllMinesInSector[MAX_MINES_PER_SECTOR];
  592.     // Fill array of positions with the data of all the existing mines in the given sector
  593.     DWORD dwMinesFound = m_GameManager.GetMinesInSector (iSector, AllMinesInSector);
  594.     m_Message.m_aBuffer[0] = CMessage::SET_MINES;   // Set message type in 1st byte
  595.  
  596.     m_Message.m_dwLength = 1 +  // The message type takes 1 byte
  597.         CMessage::EncodeSectorMines (&m_Message.m_aBuffer[1],  
  598.                                      iSector, 
  599.                                      dwMinesFound, 
  600.                                      AllMinesInSector);
  601.     // Send to last reporter:
  602.     ReplyToSender();
  603. }
  604. void
  605. CHost::PlayerJoinsGame (UINT uID)
  606. {
  607.     memset (&m_PlayersRTT[uID], 0, sizeof (PlayerRTT));
  608.     m_PlayersRTT[uID].dwPlayerJoinTime  = 
  609.     m_PlayersRTT[uID].dwLastMsgTime     = GetTickCount();
  610.     m_PlayersRTT[uID].dwPlayerMinRTT = MAX_DWORD; 
  611.     ZERO_TANK_SYNC_STATS (uID);
  612. }
  613. /*------------------------------------------------------------------------------
  614.   Function: UpdatePlayerRTT
  615.   Purpose:  Calculates the round trip time from our host to the reporting player.
  616.             Check if the zombie status should be updated.
  617.   Input:    dwMsgTime - the time stamp of the message
  618.   Output:   None.
  619.   Remarks:  A tank becomes zombie if 5 sequential messages have RTT larger then
  620.             the maximal allowed (this parameter is configurable via the 
  621.             registry, and is set to 1.5 sec. by default).
  622. ------------------------------------------------------------------------------*/
  623. void 
  624. CHost::UpdatePlayerRTT (DWORD dwMsgTime)
  625. {
  626.     int i = LookupTankID (m_idFrom);
  627.     if (0 > i)
  628.         // Tank ID not associated yet   
  629.         return;
  630.     DWORD dwNow = m_Timer.GetLocalTime();
  631.     DWORD dwCurLat = (dwNow >= dwMsgTime) ?
  632.         (dwNow - dwMsgTime) : (dwMsgTime - dwNow);
  633.     m_PlayersRTT[i].dwLastMsgTime = dwNow;
  634.     m_PlayersRTT[i].dwPlayerCurRTT = dwCurLat;
  635.     
  636.     // TRACE ("          Now = %d, MsgTime = %d,  RTT = %dn", dwNow, dwMsgTime, dwCurLat);
  637. #ifdef GATHER_SYNC_STATS
  638.     m_PlayersRTT[i].dwPlayerMaxRTT = max (
  639.         m_PlayersRTT[i].dwPlayerMaxRTT, dwCurLat);
  640.     m_PlayersRTT[i].dwPlayerMinRTT = min (
  641.         m_PlayersRTT[i].dwPlayerMinRTT, dwCurLat);
  642.     m_PlayersRTT[i].dwPlayerTotalRTTs += dwCurLat;
  643.     m_PlayersRTT[i].dwTotMsgs++;
  644.     m_PlayersRTT[i].adwPlayerRTTCounts[min (7, dwCurLat >> 7)]++;
  645.     m_PlayersRTT[i].adwPlayerRTTTotals[min (7, dwCurLat >> 7)] += dwCurLat;
  646. #endif // GATHER_SYNC_STATS
  647.     //
  648.     // Check for zombies:
  649.     //
  650.     BOOL bZombie = m_JudgeViewOfTanks[i].Zombie;    // Store current state
  651.     if (m_JudgeViewOfTanks[i].Zombie)
  652.     {   // Check if zombie's condition is improving:
  653.         if (dwCurLat < TANKS_APP->GetMaxRTT())                   // RTT is valid again
  654.         {
  655.             if (m_JudgeViewOfTanks[i].LastRTTWasGood)   // if the former one was too -
  656.                 m_JudgeViewOfTanks[i].BadRTTCount--;    // dec. counter, or else
  657.             else                                        // reset counter as we begin a new series
  658.                 m_JudgeViewOfTanks[i].BadRTTCount = MAX_BAD_RTT_COUNT - 1;
  659.         }
  660.         // If there were MAX_BAD_RTT_COUNT good RTTs in a row - 
  661.         // tank is back in business:
  662.         if (m_JudgeViewOfTanks[i].BadRTTCount <= 0)
  663.         {
  664.             m_JudgeViewOfTanks[i].BadRTTCount = 0;
  665.             bZombie = FALSE;
  666.         }
  667.     } else
  668.     {   // Check if tank needs to become zombie:
  669.         if (dwCurLat > TANKS_APP->GetMaxRTT())                       // RTT is too big
  670.         {
  671.             if (! m_JudgeViewOfTanks[i].LastRTTWasGood)     // if the former one was too
  672.                 m_JudgeViewOfTanks[i].BadRTTCount++;        // inc. counter, or else
  673.             else                                            // reset counter as a new series starts
  674.                 m_JudgeViewOfTanks[i].BadRTTCount = 1;
  675.         }
  676.         // If there were MAX_BAD_RTT_COUNT bad RTTs in a row - 
  677.         // tank is out of business:
  678.         if (m_JudgeViewOfTanks[i].BadRTTCount >= MAX_BAD_RTT_COUNT)
  679.         {
  680.             bZombie = TRUE;
  681.         }
  682.     }
  683.     // Update last RTT evaluation:
  684.     m_JudgeViewOfTanks[i].LastRTTWasGood = (dwCurLat < TANKS_APP->GetMaxRTT());
  685.     
  686.     if (bZombie != m_JudgeViewOfTanks[i].Zombie)    // There is a status change:
  687.     {
  688.         m_JudgeViewOfTanks[i].Zombie = bZombie;
  689.         if (bZombie)
  690.         {
  691.             INC_ZOMBIE_IN (i)
  692.         }
  693.         else
  694.         {
  695.             INC_ZOMBIE_OUT (i)
  696.         }
  697.         NET_SYNC_TRACE (("Tank %d is %s zombie: "
  698.                          "current latency = %d (max = %d),"
  699.                          " bad RTT count = %dn",
  700.                          i, 
  701.                          bZombie ? "becoming" : "stopping being", 
  702.                          dwCurLat, 
  703.                          m_PlayersRTT[i].dwPlayerMaxRTT,
  704.                          m_JudgeViewOfTanks[i].BadRTTCount));
  705.         SendTankZombie (i, bZombie, TRUE /* Send to all */);
  706.     }
  707. }
  708. /*------------------------------------------------------------------------------
  709.   Function: ChkForZombies
  710.   Purpose:  Another zombie check - this method is called by the game manager 
  711.             every iteration, to check for tanks that have stopped reporting for
  712.             a long period of time (MAX_COMM_MUTE_PERIOD)
  713.   Input:    None.
  714.   Output:   None.
  715.   Remarks:  
  716. ------------------------------------------------------------------------------*/
  717. void
  718. CHost::ChkForZombies()
  719. {
  720.     DWORD dwNow = m_Timer.GetLocalTime();
  721.     for (int i = 0; i < MAX_TANKS; i++)
  722.     {
  723.         if (CCommManager::INVALID_PLAYER_ID == m_aPlayersID[i])
  724.             continue;
  725.         // Check if time of last message from tank[i] arrived in reasonable time:
  726.         if (((dwNow - m_PlayersRTT[i].dwLastMsgTime) > MAX_COMM_MUTE_PERIOD) &&
  727.             !m_JudgeViewOfTanks[i].Zombie)
  728.         {   // This player wasn't heard for too long time:
  729.             m_JudgeViewOfTanks[i].Zombie = TRUE;
  730.             INC_ZOMBIE_IN (i)
  731.             NET_SYNC_TRACE (("Tank %d is becoming zombie"
  732.                              " - last message arrived %d millisecs ago.n",
  733.                              i, dwNow - m_PlayersRTT[i].dwLastMsgTime));
  734.                     
  735.             SendTankZombie (i, TRUE, TRUE /* Send to all */);
  736.         }
  737.     }
  738. }
  739. BOOL 
  740. CHost::GetPlayerInfo (UINT ind, DWORD &dwDuration, DWORD &dwRTT)
  741. {
  742.     ASSERT (ind < MAX_TANKS);
  743.     if (CCommManager::INVALID_PLAYER_ID == m_aPlayersID[ind])
  744.         return FALSE;   // Player not playing now
  745.     dwDuration = GetTickCount() - m_PlayersRTT[ind].dwPlayerJoinTime;
  746.     dwRTT = m_PlayersRTT[ind].dwPlayerCurRTT;
  747.     return TRUE;
  748. }
  749. DPID
  750. CHost::GetDirectPlayID (UINT uPlayerID)
  751. {
  752.     ASSERT (uPlayerID < MAX_TANKS);
  753.     return m_aPlayersID[uPlayerID];
  754. }
  755. #ifdef GATHER_SYNC_STATS
  756. void 
  757. CHost::ZeroTankSyncStats (UINT uTankID)
  758. {
  759.     TRACE ("tTank #%d is joining session, starting sync statistics for it.n", uTankID);
  760.     memset (&m_TanksSyncStats[uTankID], 0, sizeof (TankSyncStats));
  761.     m_TanksSyncStats[uTankID].dwSmallestTimeGap = MAX_DWORD;
  762.     m_TanksSyncStats[uTankID].dwStartTime = GetTickCount();
  763. }
  764. void 
  765. CHost::PrintTankSyncStats (UINT uTankID)
  766. {
  767.     TRACE ( "nnttt**** Game sync statistics for tank %d ****n",
  768.                 uTankID);
  769.     TRACE ( "Total time player %d was active = %.3f secsn",
  770.                 uTankID,
  771.                 double(GetTickCount() - m_TanksSyncStats[uTankID].dwStartTime) / 1000.0);
  772.     TRACE ( "Sync messages arriving from tank %d = %dn",
  773.                 uTankID, m_TanksSyncStats[uTankID].dwSyncMsgsArrived);
  774.     TRACE ("Average size of sync message = %dn",
  775.                 m_TanksSyncStats[uTankID].dwSyncMsgsArrived ?
  776.                 (uTankID, m_TanksSyncStats[uTankID].dwTotSyncMsgSize / 
  777.                  m_TanksSyncStats[uTankID].dwSyncMsgsArrived) : 0);
  778.     TRACE ( "Sync mismatches:n");
  779.     TRACE ( "tBonus = %d, Mines = %d, Zombie = %dn",
  780.                 m_TanksSyncStats[uTankID].dwBonusMismatch,
  781.                 m_TanksSyncStats[uTankID].dwMinesMismatch,
  782.                 m_TanksSyncStats[uTankID].dwZombieMismatch);
  783.     TRACE ( "tTanks status = %dn",
  784.                 m_TanksSyncStats[uTankID].dwOtherTankStatusMismatch);
  785.     TRACE ( "tMissing tanks = %d, Extra tanks = %dn",
  786.                 m_TanksSyncStats[uTankID].dwMissingTanks,
  787.                 m_TanksSyncStats[uTankID].dwExtraTanks);
  788.     TRACE ( "Zombie:n");
  789.     TRACE ( "tTank became zombie %d times, and came back alive %d times.n",
  790.                 m_TanksSyncStats[uTankID].dwZombieIn,
  791.                 m_TanksSyncStats[uTankID].dwZombieOut);
  792.     TRACE ( "Sync msgs timing:n");
  793.     TRACE ( "tTime gap between reports: min = %d millisecs, max = %d, avg = %dn",
  794.                 m_TanksSyncStats[uTankID].dwSmallestTimeGap,
  795.                 m_TanksSyncStats[uTankID].dwBiggestTimeGap,
  796.                 m_TanksSyncStats[uTankID].dwTotalGapsCnt ?
  797.                     DWORD(double(m_TanksSyncStats[uTankID].dwTotalGaps) / 
  798.                           double(m_TanksSyncStats[uTankID].dwTotalGapsCnt)) : 0);
  799.     DWORD dwAvg = m_PlayersRTT[uTankID].dwPlayerTotalRTTs ? 
  800.         DWORD(double(m_PlayersRTT[uTankID].dwPlayerTotalRTTs) /
  801.               double(m_PlayersRTT[uTankID].dwTotMsgs)) : 0;
  802.     TRACE ( "Round Trip Times:n");
  803.     TRACE ( "tMax = %d msecs, Min = %d msecs , Avg = %d msecsn",
  804.                 m_PlayersRTT[uTankID].dwPlayerMaxRTT,
  805.                 m_PlayersRTT[uTankID].dwPlayerMinRTT,
  806.                 dwAvg);
  807.     TRACE ( "tRTT histogram:n" );
  808.     for (int i = 0; i < sizeof(m_PlayersRTT[uTankID].adwPlayerRTTCounts) /
  809.         sizeof (m_PlayersRTT[uTankID].adwPlayerRTTCounts[0]) - 1; i++)
  810.     {
  811.         TRACE ( "tRTT between %d and %d was counted %d times (Avg = %d)n",
  812.             i << 7, 
  813.             ((i + 1) << 7) - 1, 
  814.             m_PlayersRTT[uTankID].adwPlayerRTTCounts[i],
  815.             m_PlayersRTT[uTankID].adwPlayerRTTCounts[i] ?
  816.             m_PlayersRTT[uTankID].adwPlayerRTTTotals[i] /
  817.             m_PlayersRTT[uTankID].adwPlayerRTTCounts[i] : 0);
  818.     }
  819.     TRACE ( "tRTT above %d was counted %d times (Avg = %d)nn",
  820.             i << 7,
  821.             m_PlayersRTT[uTankID].adwPlayerRTTCounts[i],
  822.             m_PlayersRTT[uTankID].adwPlayerRTTCounts[i] ?
  823.             m_PlayersRTT[uTankID].adwPlayerRTTTotals[i] /
  824.             m_PlayersRTT[uTankID].adwPlayerRTTCounts[i] : 0);
  825.     UNREFERENCED_PARAMETER(uTankID);
  826. }
  827. void 
  828. CHost::UpdateSyncMsgTime (UINT uTankID)
  829. {
  830.     if (!m_TanksSyncStats[uTankID].dwLastReportTime)
  831.     {   // First time around
  832.         m_TanksSyncStats[uTankID].dwLastReportTime = GetTickCount();
  833.         m_TanksSyncStats[uTankID].dwTotalGapsCnt = 0;
  834.         m_TanksSyncStats[uTankID].dwTotalGaps = 0;
  835.         return;
  836.     }
  837.     DWORD dwNow = GetTickCount();
  838.     DWORD dwTimeGap = dwNow - m_TanksSyncStats[uTankID].dwLastReportTime;
  839.     m_TanksSyncStats[uTankID].dwLastReportTime = dwNow;
  840.     m_TanksSyncStats[uTankID].dwSmallestTimeGap = min (dwTimeGap,
  841.         m_TanksSyncStats[uTankID].dwSmallestTimeGap);
  842.     m_TanksSyncStats[uTankID].dwBiggestTimeGap = max (dwTimeGap,
  843.         m_TanksSyncStats[uTankID].dwBiggestTimeGap);
  844.     m_TanksSyncStats[uTankID].dwTotalGaps += dwTimeGap;
  845.     m_TanksSyncStats[uTankID].dwTotalGapsCnt++;
  846. }
  847. #endif // defined GATHER_SYNC_STATS