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

游戏

开发平台:

Visual C++

  1. /*****************************************************************************
  2. *                                                                             
  3. *   TankObj.cpp                                                            
  4. *                                                                             
  5. *   Electrical Engineering Faculty - Software Lab                             
  6. *   Spring semester 1998                                                      
  7. *                                                                             
  8. *   Tanks game                                                                
  9. *                                                                             
  10. *   Module description: Implements the characteristics of the tank object.
  11. *                                                                             
  12. *   Authors: Eran Yariv - 28484475                                           
  13. *            Moshe Zur  - 24070856                                           
  14. *                                                                            
  15. *                                                                            
  16. *   Date: 23/09/98                                                           
  17. *                                                                            
  18. ******************************************************************************/
  19. #include "stdafx.h"
  20. #include <math.h>
  21. #include "tanks.h"
  22. #include "TankObj.h"
  23. #include "GameManager.h"
  24. #include "Bullet.h"
  25. #include "Shell.h"
  26. #include "Mine.h"
  27. #include "Message.h"
  28. #include "Bomber.h"
  29. // Regular delay between shells and bullets:
  30. const UINT BULLET_FIRE_DELAY        =   UINT(1000.0 / double(BULLET_FIRE_RATE));    
  31. const UINT SHELL_FIRE_DELAY         =   UINT(1000.0 / double(SHELL_FIRE_RATE));
  32. // Extra short delay, when bonus is effective:
  33. const UINT RAPID_BULLET_FIRE_DELAY  =   UINT(1000.0 / double(BULLET_FIRE_RATE)) / FIRE_RATE_BONUS;
  34. const UINT RAPID_SHELL_FIRE_DELAY   =   UINT(1000.0 / double(SHELL_FIRE_RATE)) / FIRE_RATE_BONUS;
  35. const DirOffsetType CTankObj::m_SpawnOffsets [24] =
  36. { //   Bullet    Shell     Mine
  37.     {   0, 20,  -6,  17,  37,  16 },      //   0     ( <-- )
  38.     {   1, 25,  -4,  22,  37,   8 },      //  15
  39.     {   2, 29,  -3,  28,  35,   3 },      //  30
  40.     {   6, 33,   2,  32,  30,  -3 },      //  45
  41.     {   9, 36,   5,  36,  26,  -5 },      //  60
  42.     {  15, 38,  11,  38,  21,  -7 },      //  75
  43.     {  20, 38,  17,  38,  16,  -9 },      //  90     (  up )
  44.     {  25, 38,  23,  38,   9,  -6 },      // 105     
  45.     {  30, 36,  28,  36,   4,  -4 },      // 120     
  46.     {  32, 32,  32,  32,  -2,   1 },      // 135     
  47.     {  36, 29,  36,  27,  -5,   2 },      // 150    
  48.     {  37, 24,  37,  22,  -7,  10 },      // 165     
  49.     {  37, 18,  37,  16,  -9,  15 },      // 180     ( --> )     
  50.     {  37, 13,  37,  10,  -7,  20 },      // 195     
  51.     {  36,  9,  36,   5,  -4,  26 },      // 210     
  52.     {  32,  5,  32,   1,  -2,  30 },      // 225     
  53.     {  28,  2,  26,  -2,   3,  33 },      // 240     
  54.     {  24,  0,  22,  -5,   9,  36 },      // 255     
  55.     {  17, -1,  15,  -5,  15,  38 },      // 270     ( down )     
  56.     {  13, -1,  10,  -6,  20,  38 },      // 285   
  57.     {   8,  2,   5,  -2,  26,  36 },      // 300   
  58.     {   4,  5,   0,   1,  32,  31 },      // 315   
  59.     {   2,  9,  -3,   6,  34,  26 },      // 330   
  60.     {   0, 14,  -5,  11,  37,  21 }       // 345   
  61. };
  62. const PosEntry UNUSED_POS_ENTRY;
  63. CTankObj::CTankObj ( UINT uID, UINT uXPos, UINT uYPos, UINT uDirIndex, BOOL bLocal, UINT uShieldLevel, UINT uShells, 
  64.                      UINT uBullets, UINT uMines, BYTE bFireRateBonusSecsLeft) :
  65.         CMovingGameObject(uXPos, uYPos, TANK_WIDTH, TANK_HEIGHT, uDirIndex, double(TANK_MAX_VELOCITY)),
  66.         m_uTankID(uID),
  67.         m_uShieldLevel(uShieldLevel),
  68.         m_uShells(uShells),
  69.         m_uBullets(uBullets),
  70.         m_uMines(uMines),
  71.         m_pDlgWnd ((CTanksDlg*)(TANKS_APP->m_pMainWnd)),
  72.         m_ManouverSet(TANKS_APP->m_gManouverSets[uID & 0x3]),
  73.         m_ImageManager(TANKS_APP->m_gImageManager),
  74.         m_ObjList(TANKS_APP->m_gGameManager.ExposeObjects()),
  75.         m_MsgQueue(TANKS_APP->m_gIncomingMsgQueue),
  76.         m_CommManager(TANKS_APP->m_gCommManager),
  77.         m_Timer(TANKS_APP->m_gTimer),
  78.         m_dwLastBulletFiredTick(0),
  79.         m_dwLastShellFiredTick(0),
  80.         m_dwLastMineDropTick(0),
  81.         m_dwBulletFireDelay(BULLET_FIRE_DELAY),
  82.         m_dwShellFireDelay(SHELL_FIRE_DELAY),
  83.         m_bLocal (bLocal),
  84.         m_dwLastRotationTime (0),
  85.         m_bUseManouverSet (TRUE),
  86.         m_bBomberInSetup (FALSE),
  87.         m_bBomberAvail (FALSE),
  88.         m_bForceNewPos (FALSE),
  89.         m_bZombie (FALSE),
  90.         m_PosTable (UNUSED_POS_ENTRY)
  91. {
  92.     ASSERT (uID < MAX_TANKS);
  93.     
  94.     m_hTankImage = m_ImageManager.GetImage ((CImageManager::ImageType)uID);
  95.     m_hExplodedTankImage = m_ImageManager.GetImage (CImageManager::IMG_TANK_EXPLODE);
  96.     m_hZombieOverlay = m_ImageManager.GetImage (CImageManager::IMG_TANK_ZOMBIE);
  97.     // Test code  **************:
  98.     m_ImageManager.RotateImage (m_hTankImage, m_uDirectionIndex);
  99.     // End of test code **************
  100.     m_pCurrentImage = &m_hTankImage;
  101.     m_dwFireRateBonusLastTick = DWORD(bFireRateBonusSecsLeft) * 1000 + GetTickCount();
  102.     if (m_bLocal) {
  103.         m_pDlgWnd->SetShieldLevel (m_uShieldLevel);
  104.         m_pDlgWnd->SetShellsCount (m_uShells);
  105.         m_pDlgWnd->SetBulletsCount (m_uBullets);
  106.         m_pDlgWnd->SetMinesCount (m_uMines);
  107.         m_pDlgWnd->SetAerialSupport (FALSE);
  108.         m_pDlgWnd->SetFastFireRate (m_dwFireRateBonusLastTick != 0);
  109.     }
  110. }
  111. CReaction
  112. CTankObj::React(CGameObject *pTo)
  113. {
  114.     ASSERT(pTo);
  115.     CReaction res;
  116.     switch (pTo->GetType())
  117.     {
  118.     case TANK:      // detect collision, and block the other object movement:
  119.     case SHELL:     
  120.     case BULLET:    
  121.     case BOMB:
  122.         if (CollidesWith(pTo))
  123.         {
  124.             res = CReaction(0, HIT_TANK, BONUS_NONE, m_uTankID);
  125.         }
  126.         break;
  127.     }
  128.     return res;
  129. }
  130. #pragma warning (disable : 4701)
  131. /* warning C4701: local variable 'CurObjHImg', 'himgLastImage', 'uLastDir'
  132.    may be used without having been initialized
  133.    God damn it, trust me on this one - I know what I'm doing here....
  134. */
  135. /*------------------------------------------------------------------------------
  136.   Function: CalcState
  137.   Purpose:  The main Tank object function. Called every game loop, the function
  138.             collects the tank's maneuver, calculates the new position, and gathers
  139.             the other game objects reaction to the new position. Upon return the
  140.             tanks status (shield, ammo, bonuses) are updated.
  141.   Input:    The local game time of the current game loop.
  142.   Output:   Tank's new state.
  143.   Remarks:  There's a difference between local and remote tanks behavior:
  144.             The local tanks are allowed to set their state to dead, they need to
  145.             update the game display (ammo etc.) and to dispatch the maneuver that
  146.             was in use. Remote tanks don't need to do the above.
  147. ------------------------------------------------------------------------------*/
  148. StateType 
  149. CTankObj::CalcState (DWORD dwCurTime)
  150.     m_bImageChanged = FALSE;    // Assume no change since last CalcState
  151.     if (m_pCurrentImage == &m_hExplodedTankImage) 
  152.     {   // if state==exploded - advance explosion or terminate
  153.         if (m_ImageManager.ImageEnded(*m_pCurrentImage))  // That's it - we are done :-(
  154.         {   // Explosion is over
  155.             if (m_bLocal)   // It's the local tank that dies
  156.             {
  157.                 // Go to game over animation mode
  158.                 m_pDlgWnd->GameOver ();
  159.             }
  160.             return STATE_DEAD;
  161.         }
  162.         return STATE_ALIVE; // Still exploding
  163.     } 
  164.     if (m_bZombie && !m_bForceNewPos)
  165.         return STATE_ALIVE; // Zombie tank reacts to nothing !!
  166.     // From here on starts the normal tank behaviour:
  167.     
  168.     CManouverSet ms;        // Detect manouver set (for network messages)
  169.     BOOL bTryToRotate;      // Did the user dare to rotate ?
  170.     HIMAGE himgLastImage;   // Current image, for undo purposes
  171.     UINT uLastDir;          // Current rotation index, for undo purposes
  172.     UINT ManouverSet;
  173.     if (m_bUseManouverSet)
  174.     {   // Can we use the manouver set to react to the user's whim
  175.         //
  176.         // Save movement params (for Undo option)
  177.         //
  178.         SaveMovement ();
  179.         //
  180.         //  handle manouver set:
  181.         //
  182.         ManouverSet = m_ManouverSet.GetAll();   
  183.             // Each time we call GetAll on the MS we need to reset its values:
  184.         TANKS_APP->m_gKbdManager.RefreshManouverSet();
  185.         //
  186.         // Calc new direction:
  187.         //
  188.         bTryToRotate = FALSE;              
  189.         himgLastImage = m_hTankImage;
  190.         uLastDir = m_uDirectionIndex;
  191.         //
  192.         // In cases of remote tanks, we occasionally get an absolute
  193.         // direction from the net.
  194.         //
  195.         if (! m_bLocal && TANKS_APP->m_guRemoteTanksDirs[m_uTankID] != INVALID_DIRECTION)
  196.         {   // We just got an absolute direction from a remote tank
  197.             m_uDirectionIndex = TANKS_APP->m_guRemoteTanksDirs[m_uTankID];
  198.             TANKS_APP->m_guRemoteTanksDirs[m_uTankID] = INVALID_DIRECTION;
  199.             bTryToRotate = TRUE;
  200.         } 
  201.         else 
  202.         {    
  203.             // Normal game play - Either a local tank, or a remote w/o absolute direction:
  204.             if (ManouverSet & CManouverSet::TURN_RIGHT_MASK)
  205.             {   // Rotate right
  206.                 ms.SetBit (CManouverSet::TURN_RIGHT);   
  207.                 if (dwCurTime - m_dwLastRotationTime >= TANK_ROTATION_DELAY) 
  208.                 {   // enough time has passed sine last rotation
  209.                     m_uDirectionIndex = (m_uDirectionIndex + 1) % MAX_DIRECTIONS;
  210.                     bTryToRotate = TRUE;
  211.                     ASSERT (m_uDirectionIndex < MAX_DIRECTIONS);
  212.                 } 
  213.             }
  214.             else if (ManouverSet & CManouverSet::TURN_LEFT_MASK)
  215.             {   // Rotate left
  216.                 ms.SetBit (CManouverSet::TURN_LEFT);
  217.                 if (dwCurTime - m_dwLastRotationTime >= TANK_ROTATION_DELAY)
  218.                 {   // enough time has passed sine last rotation
  219.                     m_uDirectionIndex = (m_uDirectionIndex == 0) ? 
  220.                         (MAX_DIRECTIONS - 1) : (m_uDirectionIndex - 1);
  221.                     bTryToRotate = TRUE;
  222.                     ASSERT (m_uDirectionIndex < MAX_DIRECTIONS);
  223.                 }
  224.             }
  225.         } // end of local tank or remote w/o absolute direction
  226.         if (bTryToRotate)
  227.         {   // There's an attmept to rotate - Rotate the image
  228.             m_ImageManager.RotateImage (m_hTankImage, m_uDirectionIndex);
  229.             // Check if we're out of the map now
  230.             if (GetMapPosition(m_Pos) != (X_FULL_IN_MAP | Y_FULL_IN_MAP)) 
  231.             {   // Out of map situation - undo rotation
  232.                 m_uDirectionIndex = uLastDir;
  233.                 m_hTankImage = himgLastImage;
  234.                 ms.UnsetBit (CManouverSet::TURN_RIGHT);   // Cancel roations
  235.                 ms.UnsetBit (CManouverSet::TURN_LEFT);
  236.             }
  237.             else
  238.             {   // Rotation o.k. - apply changes
  239.                 m_dwLastRotationTime = dwCurTime;
  240.                 m_bImageChanged = TRUE;
  241.             }
  242.         } // End of rotation attempt
  243.         //
  244.         // Calc new position: 
  245.         //
  246.         BOOL bTryToMove = FALSE;
  247.         if (m_bForceNewPos)
  248.         {   // We got a forced position and direction (due to checksum mismatch)
  249.             SetNewPos (m_iNewForcedXPos, m_iNewForcedYPos);
  250.             m_bForceNewPos = FALSE;       // Turn it off
  251.             m_bImageChanged = TRUE;
  252.         } 
  253.         else 
  254.         {   // Normal game play
  255.             if (ManouverSet & CManouverSet::FORWARD_MASK) 
  256.             {   // Move forward
  257.                 m_dVelocity = fabs(m_dVelocity);
  258.                 ms.SetBit (CManouverSet::FORWARD);
  259.                 bTryToMove = TRUE;
  260.             } else if (ManouverSet & CManouverSet::BACKWARD_MASK) 
  261.             {   // Move backwards
  262.                 m_dVelocity = -1.0 * fabs(m_dVelocity);
  263.                 ms.SetBit (CManouverSet::BACKWARD);
  264.                 bTryToMove = TRUE;
  265.             }
  266.             else
  267.                 StopMovement ();    // No movement chosen
  268.             if (bTryToMove)
  269.             {   // Tank tried to move
  270.                 if (CalcNewPos(dwCurTime) < 0)
  271.                 {   // 
  272.                     // Failed to move - out of map case
  273.                     //
  274.                     UndoMovement ();
  275.                     if (m_uDirectionIndex != uLastDir) 
  276.                     {   // If we moved AND rotated, cancel rotation as well.
  277.                         m_uDirectionIndex = uLastDir;
  278.                         m_hTankImage = himgLastImage;
  279.                     }
  280.                     ms.UnsetBit (CManouverSet::FORWARD);      // Cancel movements:
  281.                     ms.UnsetBit (CManouverSet::BACKWARD);
  282.                     ms.UnsetBit (CManouverSet::TURN_RIGHT);   // Cancel roations
  283.                     ms.UnsetBit (CManouverSet::TURN_LEFT);
  284.                     m_bImageChanged = FALSE;
  285.                 }
  286.                 else
  287.                 {   
  288.                     // Tries to move and successded (so far, collision not tested yet)
  289.                     m_bImageChanged = TRUE;
  290.                 }
  291.             }   // end of movement attempt
  292.         } // end of normal game play
  293.     }   // end of if (m_bUseManouverSet)
  294.     //
  295.     // handle reaction:
  296.     //
  297.     CReaction Reaction = m_ObjList.GetGameReaction (this);
  298.     if (m_CommManager.IsHost() && (BONUS_NONE != Reaction.GetBonusType()))
  299.         //
  300.         // If this the host machine and a tank just hit a bonus, tell all the other
  301.         // players (including this player) that the bonus has just been eaten.
  302.         // This will cause a ADD_BONUS message to arrive with the Tank ID in 
  303.         // the LifeSpan. The game manager, upon receipt of such a message will call 
  304.         // the EatBonus method for the correct tank object.
  305.         //
  306.         m_CommManager.NotifyBonusEaten (m_uTankID);
  307.     //
  308.     // update state (shield level, image)
  309.     //
  310.     UINT ExplosionReact = Reaction.GetExplosionIntensity();
  311.     if (ExplosionReact) 
  312.     {   // We are hit:
  313.         m_uShieldLevel -= ExplosionReact;    
  314.         if (LONG(m_uShieldLevel) < 0)
  315. {
  316.             // We are hit badly - that's the end:
  317. if (m_bLocal) // If it's a remote tank, we have to
  318. // wait for the judge decision...
  319. Kill();
  320. else
  321. m_uShieldLevel = 0;
  322. }
  323.         if (m_bLocal)
  324.             // If it's a local tank, update shield status.
  325.             m_pDlgWnd->SetShieldLevel (m_uShieldLevel);
  326.     }
  327.     //
  328.     // update position:
  329.     //
  330.     TerrainType TerrainReact = Reaction.GetTerrainDifficulty();
  331.     switch (TerrainReact)
  332.     {
  333.         case TERR_BLOCKED:
  334.         case HIT_TANK:
  335.             // we are blocked - rewind the new position:
  336.             UndoMovement ();
  337.             if (m_uDirectionIndex != uLastDir) {
  338.                 // There was also a rotation, undo it.
  339.                 m_uDirectionIndex = uLastDir;
  340.                 m_hTankImage = himgLastImage;
  341.             }
  342.             m_bImageChanged = FALSE;
  343.                 // Cancel all movements and rotations:
  344.             ms.UnsetBit (CManouverSet::TURN_RIGHT);
  345.             ms.UnsetBit (CManouverSet::TURN_LEFT);
  346.             ms.UnsetBit (CManouverSet::FORWARD);
  347.             ms.UnsetBit (CManouverSet::BACKWARD);
  348.             break;
  349.         case TERR_EMPTY:
  350.             m_dVelocity = TANK_MAX_VELOCITY;
  351.             break;
  352.         case TERR_75SPEED:
  353.             m_dVelocity = TANK_75_VELOCITY;
  354.             break;
  355.         case TERR_50SPEED:
  356.             m_dVelocity = TANK_50_VELOCITY;
  357.             break;
  358.         case TERR_25SPEED:
  359.             m_dVelocity = TANK_25_VELOCITY;
  360.             break;
  361.         default:
  362.             ASSERT (FALSE);
  363.             break;
  364.     }
  365.     if (m_bUseManouverSet)
  366.     {   // Can we use the manouver set to react to the user's whim
  367.         //
  368.         // check the fire controls, and spawn bullet/shell/mine if needed:
  369.         //
  370.         if ((ManouverSet & CManouverSet::FIRE_SHELL_MASK) && // Fire shell button is pressed and
  371.             m_uShells)                                       // we have shells 
  372.         {
  373.             if (ShellsEnabled(dwCurTime))    // we can fire a new shell
  374.                 FireShell(dwCurTime);
  375.             ms.SetBit (CManouverSet::FIRE_SHELL);
  376.         }
  377.         if ((ManouverSet & CManouverSet::FIRE_BULLET_MASK) &&   // Fire bullet button is pressed and
  378.             m_uBullets)                                         // we have bullets and
  379.         {
  380.             if (BulletsEnabled(dwCurTime))  // we can fire a new bullet
  381.                 FireBullet(dwCurTime);
  382.             ms.SetBit (CManouverSet::FIRE_BULLET);
  383.         }
  384.         if ((ManouverSet & CManouverSet::DROP_MINE_MASK) &&     // Drop mine button is pressed and
  385.             m_uMines)                                           // we have mines
  386.         {
  387.             if (MinesEnabled(dwCurTime)) // we can fire a new mine 
  388.                 // Try to drop a mine (may fail if there's no vacant room on the map)
  389.                 DropMine(dwCurTime);
  390.             
  391.             ms.SetBit (CManouverSet::DROP_MINE);
  392.         }
  393.         if ((ManouverSet & CManouverSet::AERIAL_SUPPORT_MASK) &&    // Bomber button is pressed and
  394.             !m_bBomberInSetup &&                                    // we don't already have a bomber in setup mode and    
  395.             m_bBomberAvail)                                         // we have the bomber icon on
  396.         {
  397.             LaunchBomber();
  398.             // Notice, we don't set the bomber bit in the ms. Only when the bomber starts to fly
  399.             // (after its setup mode) it sends ADD_BOMBER to all other players.
  400.         }
  401.     }
  402.     // Before we leave - update the last tick calculated:
  403.     // Check the fire rate bonus expiration time:
  404.     if (m_dwFireRateBonusLastTick && (m_dwFireRateBonusLastTick <= dwCurTime)) 
  405.     {   // turn off the fire rate bonus (timeout):
  406.         m_dwBulletFireDelay = BULLET_FIRE_DELAY;
  407.         m_dwShellFireDelay = SHELL_FIRE_DELAY;
  408.         m_dwFireRateBonusLastTick = 0;
  409.         if (m_bLocal)
  410.             // Turn of the icon
  411.             m_pDlgWnd->SetFastFireRate (FALSE);
  412.     }
  413.     SendManouverSet (ms);   // Notify other tanks on the network of our actions.
  414.     // Store newly calculated position in table:
  415.     PosEntry NewPos(dwCurTime, m_Pos.x, m_Pos.y);
  416.     m_PosTable.AddHead(NewPos);
  417.     return STATE_ALIVE; 
  418. }
  419. #pragma warning (default : 4701)
  420. inline BOOL
  421. CTankObj::BulletsEnabled(DWORD dwCurTime)
  422. {
  423.     return (dwCurTime - m_dwLastBulletFiredTick >= m_dwBulletFireDelay);
  424. }
  425.  
  426. inline BOOL
  427. CTankObj::MinesEnabled(DWORD dwCurTime)
  428. {
  429.     return (dwCurTime - m_dwLastMineDropTick >= MINE_FIRE_DELAY);
  430. }
  431. inline BOOL 
  432. CTankObj::ShellsEnabled(DWORD dwCurTime)
  433. {
  434.     return (dwCurTime - m_dwLastShellFiredTick >= m_dwShellFireDelay);
  435. }
  436. void
  437. CTankObj::FireShell(DWORD dwCurTime)
  438. {
  439.     // update flags:
  440.     m_uShells--;
  441.     m_dwLastShellFiredTick = dwCurTime;
  442.     // spawn shell and send message to game mananger:
  443.     CMessage::MessageData Params;
  444.     Params.ShellParam.wXPos = WORD(m_SpawnOffsets[m_uDirectionIndex].iShellXOffset + m_Pos.x);
  445.     Params.ShellParam.wYPos = WORD(m_SpawnOffsets[m_uDirectionIndex].iShellYOffset + m_Pos.y);
  446.     Params.ShellParam.bDirectionIndex = BYTE(m_uDirectionIndex);
  447.     Params.ShellParam.bParentTankID = BYTE(m_uTankID);
  448.     VERIFY (m_MsgQueue.Enqueue(CMessage::ADD_SHELL, Params));
  449.     if (m_bLocal)
  450.     {
  451.         // Update display
  452.         m_pDlgWnd->SetShellsCount(m_uShells);
  453.     }
  454.     // Play the sound
  455.     TANKS_APP->m_gSoundManager.Play(CSoundManager::FIRE_SHELL);
  456. }
  457. void
  458. CTankObj::FireBullet(DWORD dwCurTime)
  459. {
  460.     // update flags:
  461.     m_uBullets--;
  462.     m_dwLastBulletFiredTick = dwCurTime;
  463.     // spawn bullet and send message to game mananger:
  464.     CMessage::MessageData Params;
  465.     Params.BulletParam.wXPos = WORD(m_SpawnOffsets[m_uDirectionIndex].iBulletXOffset + m_Pos.x);
  466.     Params.BulletParam.wYPos = WORD(m_SpawnOffsets[m_uDirectionIndex].iBulletYOffset + m_Pos.y);
  467.     Params.BulletParam.bDirectionIndex = BYTE(m_uDirectionIndex);
  468.     Params.BulletParam.bParentTankID = BYTE(m_uTankID);
  469.     VERIFY (m_MsgQueue.Enqueue(CMessage::ADD_BULLET, Params));
  470.     if (m_bLocal)
  471.     {
  472.         // Update display
  473.         m_pDlgWnd->SetBulletsCount(m_uBullets);
  474.     }
  475.     // Play sound
  476.     TANKS_APP->m_gSoundManager.Play(CSoundManager::FIRE_BULLET);
  477. }
  478. BOOL
  479. CTankObj::DropMine(DWORD dwCurTime)
  480. {
  481.     CMine tmpMine (m_SpawnOffsets[m_uDirectionIndex].iMineXOffset + m_Pos.x,
  482.                    m_SpawnOffsets[m_uDirectionIndex].iMineYOffset + m_Pos.y);
  483.     CReaction Reaction = m_ObjList.GetGameReaction (&tmpMine);
  484.         // check that we can spawn:
  485.     if ((Reaction.GetTerrainDifficulty() == TERR_EMPTY) &&                   // board is empty
  486.         (tmpMine.GetMapPosition(tmpMine.GetPos()) == (X_FULL_IN_MAP | Y_FULL_IN_MAP))) // and fully in map
  487.     {
  488.         // update flags:
  489.         m_uMines--;
  490.         m_dwLastMineDropTick = dwCurTime;
  491.         // spawn mine and send message to game mananger:
  492.         CMessage::MessageData Params;
  493.         Params.MineParam.wXPos = WORD(tmpMine.GetPos().x);
  494.         Params.MineParam.wYPos = WORD(tmpMine.GetPos().y);
  495.         VERIFY (m_MsgQueue.Enqueue(CMessage::ADD_MINE, Params));
  496.         if (m_bLocal)
  497.         {
  498.             // Update display
  499.             m_pDlgWnd->SetMinesCount(m_uMines);
  500.         }
  501.         return TRUE;    // Mine dropped
  502.     }
  503.     return FALSE;   // Mine cannot be dropped
  504. }
  505. void
  506. CTankObj::LaunchBomber()
  507. {
  508.     ASSERT (m_bLocal);
  509.     ASSERT (m_bBomberAvail);
  510.     ASSERT (!m_bBomberInSetup);
  511.     CBomber *pBomber = new CBomber (this);
  512.     // spawn mine and send message to game mananger:
  513.     CMessage::MessageData Params;
  514.     Params.pGameObj = pBomber;
  515.     VERIFY (m_MsgQueue.Enqueue(CMessage::ADD_OBJECT, Params));
  516.     m_bBomberInSetup = TRUE;
  517.     m_bBomberAvail = FALSE;
  518.     m_pDlgWnd->SetAerialSupport (FALSE);
  519. }
  520. void
  521. CTankObj::RegainInput (BOOL bBomberLaunched)
  522. {
  523.     m_bUseManouverSet = TRUE;
  524.     m_bBomberInSetup = FALSE;
  525.     if (!bBomberLaunched)
  526.     {   // Bomber canceled - re-enable it
  527.         m_bBomberAvail = TRUE;
  528.         m_pDlgWnd->SetAerialSupport (TRUE);
  529.     }
  530. }
  531. void 
  532. CTankObj::SendManouverSet ( CManouverSet &ms )
  533. {
  534.     if (!m_bLocal)
  535.         return; // Don't send manouver set for remote tanks
  536.     if (ms.GetAll() == m_PrevManouverSet.GetAll())
  537.         // No change => Don't send
  538.         return;
  539.     CMessage::MessageData Params;
  540.         // Create manouver set notification message:
  541.     Params.ManouverSetParam.TankID = BYTE(m_uTankID);
  542.     Params.ManouverSetParam.ManouverSet = ms.GetAll();
  543.     Params.ManouverSetParam.Direction = BYTE(m_uDirectionIndex);
  544.         // Send it
  545.     TANKS_APP->m_gOutgoingMsgQueue.Enqueue (CMessage::MANOUVER_SET, Params);
  546.         // Update last manouver set sent over the network
  547.     m_PrevManouverSet = ms;
  548. }
  549. void
  550. CTankObj::EatBonus (BonusType t, DWORD dwEatTime)
  551. {
  552.     switch (t)
  553.     {
  554.         case BONUS_SHELLS:
  555.             m_uShells = min (MAX_STATUS_VALUE, m_uShells + TANK_BONUS_SHELLS);
  556.             if (m_bLocal)
  557.                 m_pDlgWnd->SetShellsCount(m_uShells);
  558.             break;
  559.         case BONUS_BULLETS:
  560.             m_uBullets = min (MAX_STATUS_VALUE, m_uBullets + TANK_BONUS_BULLETS);
  561.             if (m_bLocal)
  562.                 m_pDlgWnd->SetBulletsCount(m_uBullets);
  563.             break;
  564.         case BONUS_MINES:
  565.             m_uMines = min (MAX_STATUS_VALUE, TANK_BONUS_MINES + m_uMines);
  566.             if (m_bLocal)
  567.                 m_pDlgWnd->SetMinesCount(m_uMines);
  568.             break;
  569.         case BONUS_BOMBER:
  570.             if (m_bLocal)
  571.                 m_pDlgWnd->SetAerialSupport (TRUE);
  572.             m_bBomberAvail = TRUE;
  573.             break;
  574.         case BONUS_FIRE_RATE:
  575.             if (!m_dwFireRateBonusLastTick) {    // We don't already own the bonus
  576.                 m_dwBulletFireDelay = RAPID_BULLET_FIRE_DELAY;
  577.                 m_dwShellFireDelay = RAPID_SHELL_FIRE_DELAY;
  578.             }
  579.             m_dwFireRateBonusLastTick = dwEatTime + FIRE_RATE_BONUS_DURATION;
  580.             if (m_bLocal)
  581.                 m_pDlgWnd->SetFastFireRate (TRUE);
  582.             break;
  583.         case BONUS_SHIELD:
  584.             m_uShieldLevel = min (100, m_uShieldLevel + TANK_BONUS_SHIELD);
  585.             if (m_bLocal)
  586.                 m_pDlgWnd->SetShieldLevel (m_uShieldLevel);
  587.             break;
  588.         default:
  589.             ASSERT (FALSE); // Unsupported bonus type
  590.             break;
  591.     }
  592.     // Play sound
  593.     TANKS_APP->m_gSoundManager.Play(CSoundManager::PICK_BONUS);
  594. }
  595. WORD 
  596. CTankObj::PackBits (WORD wVal, WORD wNumBits)
  597. {
  598.      // If the value can be fully represented by wNumBits bits, return it:
  599.     if (wVal < (1 << wNumBits))
  600.         return wVal;
  601.     // First, find the position of the most significant bit :
  602.     int iPos=15;
  603.     WORD wValBackup = wVal;
  604.     while (!(wVal & 0x8000))
  605.     {   
  606.         wVal = WORD(wVal << 1);
  607.         iPos--;
  608.     }
  609.     // Now, simply shift right so that wNumBits MSBits fit into the result
  610.     return WORD(wValBackup >> (iPos - wNumBits + 1));
  611. }
  612. void
  613. CTankObj::GetStatus(CMessage::MessageData &MsgData)
  614. {
  615.     MsgData.TankStatusParam.bID =                   BYTE(m_uTankID);
  616.     MsgData.TankStatusParam.bShieldLevel =          (BYTE)m_uShieldLevel;
  617.     MsgData.TankStatusParam.dwShells =              m_uShells;
  618.     MsgData.TankStatusParam.dwBullets =             m_uBullets;
  619.     MsgData.TankStatusParam.dwMines =               m_uMines;
  620.     MsgData.TankStatusParam.bFireRateBonusSecsLeft = 
  621.                         BYTE(m_dwFireRateBonusLastTick - GetTickCount());
  622.     MsgData.TankStatusParam.bBomber =               BYTE(m_bBomberAvail);
  623.     MsgData.TankStatusParam.bFastFire =             (0 != m_dwFireRateBonusLastTick);
  624.     MsgData.TankStatusParam.bZombie =               BYTE(m_bZombie);
  625. }
  626. /*------------------------------------------------------------------------------
  627.   Function: SetPos
  628.   Purpose:  Calculates tanks position, and tries to enforce it, next time we calc
  629.             the tanks state. This function is used for REMOTE tanks only, when
  630.             receiving updates from the tank's "owner" (a player in this game session
  631.             that controls this tank).
  632.   Input:    dwTime: Local game time in which the tanks position was recorded.
  633.             XPos, YPos: Tank's position.
  634.   Output:   None.
  635.   Remarks:  Since this information arrived from the player that owns this tank,
  636.             the time when the info was recored is at the past in our local game
  637.             time. That means we can't just set the tanks position to the new one.
  638.             Instead we keep a record of the tanks last positions and time in a
  639.             table, and look through this table to find the closest available entry.
  640.             If no such entry is found - the update is abandoned, other wise, we
  641.             take the position offset between our record and the update we received, 
  642.             and use it to correct the current tank's position.
  643. ------------------------------------------------------------------------------*/
  644. void                
  645. CTankObj::SetPos (DWORD dwTime, int XPos, int YPos)
  646. {
  647.     // Force a new position and direction (will take effect only on next call to CalcState)
  648.     m_bForceNewPos = TRUE;
  649.     // First we need to search the history table for a position near the time given:
  650.     int ind = -1, min = INT_MAX;
  651.     for (int i = 0; i < MAX_POS_TABLE; i++) {
  652.         PosEntry posEntry = m_PosTable[i];
  653.         if (DWORD(-1) == posEntry.m_dwTime)
  654.             break;  // we covered all entries in use, and reached unused entry - just leave
  655.         int delta = abs(posEntry.m_dwTime - dwTime);
  656.         if (min > delta) {
  657.             min = delta;
  658.             ind = i;
  659.         }
  660.     }
  661.     // Get rid of extreme points:
  662.     // - Table is still empty (ind was never updated)
  663.     // - The best entry found is still not relevant to the time stamp reported:
  664.     if ((-1 == ind) ||
  665.         ((UINT)abs(m_PosTable[ind].m_dwTime - dwTime) > TANKS_APP->GetMaxRTT()))
  666.     {
  667.         if (-1 == ind)
  668.             NET_SYNC_TRACE(("TankObj::SetPos - empty tablen"))
  669.         if (-1 != ind)
  670.             NET_SYNC_TRACE(("TankObj::SetPos - time delta %dn", 
  671.                 m_PosTable[ind].m_dwTime - dwTime))
  672.         m_bForceNewPos = FALSE;
  673.         return;
  674.     }
  675.     // Now we calc the offset from our position to tanks real position
  676.     int dx = m_PosTable[ind].m_uXPos - XPos;
  677.     int dy = m_PosTable[ind].m_uYPos - YPos;
  678.     // And finally we apply the offset to tank's current position:
  679.     m_iNewForcedXPos = m_Pos.x - dx;
  680.     m_iNewForcedYPos = m_Pos.y - dy;
  681. }
  682. void
  683. CTankObj::SetStatus(CMessage::MessageData &MsgData)
  684. {
  685.     // Make sure the message arrived at the right tank:
  686.     ASSERT(MsgData.TankStatusParam.bID == m_uTankID);
  687.     m_uShieldLevel  = MsgData.TankStatusParam.bShieldLevel;
  688.     m_uShells       = MsgData.TankStatusParam.dwShells;
  689.     m_uBullets      = MsgData.TankStatusParam.dwBullets;
  690.     m_uMines        = MsgData.TankStatusParam.dwMines;
  691.     SetZombie (MsgData.TankStatusParam.bZombie);
  692.     m_dwFireRateBonusLastTick = MsgData.TankStatusParam.bFireRateBonusSecsLeft +
  693.         GetTickCount();
  694.     m_bBomberAvail  = MsgData.TankStatusParam.bBomber;
  695.     if (MsgData.TankStatusParam.bFastFire)
  696.     {   // Fast fire rate is set on:
  697.         m_dwFireRateBonusLastTick = FIRE_RATE_BONUS_DURATION + GetTickCount();
  698.     }
  699.     if (m_bLocal)
  700.     {   // Local tank - refresh display accordingly
  701.         m_pDlgWnd->SetFastFireRate  (MsgData.TankStatusParam.bFastFire);
  702.         m_pDlgWnd->SetAerialSupport (m_bBomberAvail);
  703.         m_pDlgWnd->SetShieldLevel   (m_uShieldLevel);
  704.         m_pDlgWnd->SetShellsCount   (m_uShells);
  705.         m_pDlgWnd->SetBulletsCount  (m_uBullets);
  706.         m_pDlgWnd->SetMinesCount    (m_uMines);
  707.     }
  708. }
  709. void
  710. CTankObj::GetStatusAndPos(CMessage::MessageData &MsgData)
  711. {
  712.     MsgData.TankParam.ID = m_uTankID;
  713.     MsgData.TankParam.XPos = m_Pos.x;
  714.     MsgData.TankParam.YPos = m_Pos.y;
  715.     MsgData.TankParam.ShieldLevel = m_uShieldLevel;
  716.     MsgData.TankParam.Shells = m_uShells;
  717.     MsgData.TankParam.Bullets = m_uBullets;
  718.     MsgData.TankParam.Mines = m_uMines;
  719.     MsgData.TankParam.FireRateBonusSecsLeft = BYTE(m_dwFireRateBonusLastTick - GetTickCount());
  720.     MsgData.TankParam.Bomber = m_bBomberAvail;
  721.     MsgData.TankParam.FastFire = (0 != m_dwFireRateBonusLastTick);
  722.     MsgData.TankParam.Direction = m_uDirectionIndex;
  723. }
  724. void
  725. CTankObj::Kill ()
  726. {
  727.     m_uShieldLevel = 0;
  728.     m_pCurrentImage = &m_hExplodedTankImage;
  729. if (m_bLocal)
  730. m_pDlgWnd->SetShieldLevel (m_uShieldLevel);
  731.     // Play sound
  732.     TANKS_APP->m_gSoundManager.Play(CSoundManager::TANK_EXPLODE);
  733. }
  734. CRect &
  735. CTankObj::GetUpdateRectangle()
  736. {
  737.     if (m_pCurrentImage != &m_hExplodedTankImage)
  738.         // Moving tank
  739.         return CMovingGameObject::GetUpdateRectangle();
  740.     else
  741.     {   // Exploding tank
  742.         CSize size;
  743.         CPoint offset;
  744.         m_GlobalImageManager.GetImageSize(*m_pCurrentImage, size);
  745.         m_GlobalImageManager.GetImageOffset(*m_pCurrentImage, offset);
  746.         m_UpdateRect = CRect (m_Pos + offset, size);
  747.     }
  748.     return m_UpdateRect;
  749. }