File indexing completed on 2024-12-01 06:50:34

0001 /*
0002     SPDX-FileCopyrightText: 2009 Mathias Kraus <k.hias@gmx.de>
0003     SPDX-FileCopyrightText: 2007-2008 Thomas Gallinari <tg8187@yahoo.fr>
0004     SPDX-FileCopyrightText: 2007-2008 Pierre-BenoƮt Besse <besse.pb@gmail.com>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "bomb.h"
0010 #include "arena.h"
0011 #include "granatier_random.h"
0012 
0013 #include <QTimer>
0014 
0015 #include <cstdlib>
0016 
0017 const int nMortarRampEnd = static_cast<int>(Granatier::FPS * 50 / 1000.0);
0018 const int nMortarPeak = static_cast<int>((Granatier::FPS * 800 / 1000.0) / 2 + nMortarRampEnd);
0019 const int nMortarGround = static_cast<int>((Granatier::FPS * 800 / 1000.0) + nMortarRampEnd);
0020 
0021 Bomb::Bomb(qreal fX, qreal fY, Arena* p_arena, int nBombID, int nDetonationCountdown) : Element(fX, fY, p_arena), m_xSpeed(0), m_ySpeed(0)
0022 {
0023     m_type = Granatier::Element::BOMB;
0024 
0025     m_xInit = fX;
0026     m_yInit = fY;
0027     m_x = fX;
0028     m_y = fY;
0029 
0030     m_bombPower = 1;
0031 
0032     int currentRow = m_arena->getRowFromY(m_y);
0033     int currentCol = m_arena->getColFromX(m_x);
0034     m_arena->setCellElement(currentRow, currentCol, this);
0035 
0036     m_detonated = false;
0037 
0038     m_bombID = nBombID;
0039     m_explosionID = nBombID;
0040 
0041     // Define the timer which sets the puls frequency
0042     m_detonationCountdownTimer = new QTimer(this);
0043     m_detonationCountdownTimer->setInterval(nDetonationCountdown);
0044     m_detonationCountdownTimer->setSingleShot(true);
0045     m_detonationCountdownTimer->start();
0046     connect(m_detonationCountdownTimer, &QTimer::timeout, this, &Bomb::detonate);
0047 
0048     m_mortarTimer = nullptr;
0049     m_mortarState = -1;
0050     m_thrown = false;
0051     m_stopOnCenter = false;
0052     m_falling = false;
0053 
0054     moveOnCenter();
0055 }
0056 
0057 Bomb::~Bomb()
0058 {
0059     delete m_detonationCountdownTimer;
0060     delete m_mortarTimer;
0061 }
0062 
0063 void Bomb::goUp()
0064 {
0065 }
0066 
0067 void Bomb::goDown()
0068 {
0069 }
0070 
0071 void Bomb::goRight()
0072 {
0073 }
0074 
0075 void Bomb::goLeft()
0076 {
0077 }
0078 
0079 void Bomb::updateMove()
0080 {
0081     if(m_detonated)
0082     {
0083         return;
0084     }
0085 
0086     int currentRow = m_arena->getRowFromY(m_y);
0087     int currentCol = m_arena->getColFromX(m_x);
0088     //check if the bomb is on an arrow, mortar or hole
0089     if(m_mortarState < 0 && m_xSpeed == 0 && m_ySpeed == 0)
0090     {
0091         switch (m_arena->getCell(currentRow, currentCol).getType())
0092         {
0093             case Granatier::Cell::ARROWUP:
0094                 setYSpeed(-5);
0095                 break;
0096             case Granatier::Cell::ARROWRIGHT:
0097                 setXSpeed(5);
0098                 break;
0099             case Granatier::Cell::ARROWDOWN:
0100                 setYSpeed(5);
0101                 break;
0102             case Granatier::Cell::ARROWLEFT:
0103                 setXSpeed(-5);
0104                 break;
0105             case Granatier::Cell::BOMBMORTAR:
0106                 m_mortarTimer = new QTimer;
0107                 m_mortarTimer->setSingleShot(true);
0108                 m_mortarTimer->setInterval(1500);
0109                 m_mortarTimer->start();
0110                 m_detonationCountdownTimer->stop();
0111                 m_mortarState = 0;
0112                 updateMortarState();
0113                 break;
0114             case Granatier::Cell::HOLE:
0115                 if(!m_falling)
0116                 {
0117                     m_falling = true;
0118                     m_type = Granatier::Element::NONE;
0119                     m_detonationCountdownTimer->stop();
0120                     Q_EMIT falling();
0121                     Q_EMIT releaseBombArmory();
0122                 }
0123                 break;
0124             default:
0125                 break;
0126         }
0127     }
0128 
0129     if(m_xSpeed != 0 || m_ySpeed != 0)
0130     {
0131         bool bOnCenter = false;
0132         int xDirection = 0;
0133         int xDeltaCenter = static_cast<int>(Granatier::CellSize/2 - (m_x - currentCol * Granatier::CellSize));
0134         bool bMoveAwayFromCenter = false;
0135         bool bIsHurdleCurrentCell = false;
0136         bool bIsHurdleNextCell = false;
0137 
0138         if (m_xSpeed > 0)
0139         {
0140             xDirection = 1;
0141         }
0142         else if (m_xSpeed < 0)
0143         {
0144             xDirection = -1;
0145         }
0146         int yDirection = 0;
0147         int yDeltaCenter = static_cast<int>(Granatier::CellSize/2 - (m_y - currentRow * Granatier::CellSize));
0148         if (m_ySpeed > 0)
0149         {
0150             yDirection = 1;
0151         }
0152         else if (m_ySpeed < 0)
0153         {
0154             yDirection = -1;
0155         }
0156 
0157         if((xDirection != 0 && xDeltaCenter == 0) || (yDirection != 0 && yDeltaCenter == 0))
0158         {
0159             bOnCenter = true;
0160         }
0161 
0162         int newRow = m_arena->getRowFromY(m_y + m_ySpeed);
0163         int newCol = m_arena->getColFromX(m_x + m_xSpeed);
0164         int nextRow;
0165         int nextCol;
0166         if(xDirection > 0 || yDirection > 0)
0167         {
0168             nextRow = m_arena->getRowFromY(m_y + yDirection * Granatier::CellSize/2); //this is needed because the bombs won't move if they are coming from the top, hit an up arrow and there is a hurdle below the arrow
0169             nextCol = m_arena->getColFromX(m_x + xDirection * Granatier::CellSize/2);
0170         }
0171         else
0172         {
0173             nextRow = m_arena->getRowFromY(m_y + m_ySpeed + yDirection * Granatier::CellSize/2);
0174             nextCol = m_arena->getColFromX(m_x + m_xSpeed + xDirection * Granatier::CellSize/2);
0175         }
0176 
0177         bIsHurdleCurrentCell = !(m_arena->getCell(currentRow, currentCol).isWalkable(this));
0178         bIsHurdleNextCell = !(m_arena->getCell(nextRow, nextCol).isWalkable(this));
0179 
0180         if(xDirection * xDeltaCenter <= 0 || yDirection * yDeltaCenter <= 0)
0181         {
0182             bMoveAwayFromCenter = true;
0183         }
0184 
0185         //at first, check if move over cell center or currently on cell center
0186         if((bOnCenter || (xDirection * xDeltaCenter < 0 && xDirection * (m_xSpeed + xDeltaCenter) >= 0) || (yDirection * yDeltaCenter < 0 && yDirection * (m_ySpeed + yDeltaCenter) >= 0)) && m_mortarState < 0)
0187         {
0188             bool bIsMortar = false;
0189             bool bIsNewDirection = false;
0190 
0191             switch (m_arena->getCell(currentRow, currentCol).getType())
0192             {
0193                 case Granatier::Cell::ARROWUP:
0194                     if(yDirection != -1)
0195                     {
0196                         bIsNewDirection = true;
0197                         bMoveAwayFromCenter = true;
0198                     }
0199                     break;
0200                 case Granatier::Cell::ARROWRIGHT:
0201                     if(xDirection != 1)
0202                     {
0203                         bIsNewDirection = true;
0204                         bMoveAwayFromCenter = true;
0205                     }
0206                     break;
0207                 case Granatier::Cell::ARROWDOWN:
0208                     if(yDirection != 1)
0209                     {
0210                         bIsNewDirection = true;
0211                         bMoveAwayFromCenter = true;
0212                     }
0213                     break;
0214                 case Granatier::Cell::ARROWLEFT:
0215                     if(xDirection != -1)
0216                     {
0217                         bIsNewDirection = true;
0218                         bMoveAwayFromCenter = true;
0219                     }
0220                     break;
0221                 case Granatier::Cell::BOMBMORTAR:
0222                     bIsMortar = true;
0223                     break;
0224                 case Granatier::Cell::HOLE:
0225                     m_stopOnCenter = true;
0226                     break;
0227                 default:
0228                     break;
0229             }
0230 
0231             //if two bombs move towards them, stop them and move them to the next cell center
0232             if(((bIsHurdleCurrentCell && !bMoveAwayFromCenter) || bIsHurdleNextCell) && !m_stopOnCenter)
0233             {
0234                 if(bOnCenter)
0235                 {
0236                     setXSpeed(0);
0237                     setYSpeed(0);
0238                 }
0239                 else
0240                 {
0241                     m_stopOnCenter = true;
0242                     setXSpeed(-m_xSpeed);
0243                     setYSpeed(-m_ySpeed);
0244                 }
0245             }
0246 
0247             //stop at cell center if direction change or bomb mortar in current cell
0248             else if(bIsMortar || bIsNewDirection || m_stopOnCenter)
0249             {
0250                 move((currentCol+0.5) * Granatier::CellSize, (currentRow+0.5) * Granatier::CellSize);
0251                 setXSpeed(0);
0252                 setYSpeed(0);
0253                 m_stopOnCenter = false;
0254             }
0255             else
0256             {
0257                 if((newRow != currentRow || newCol != currentCol) && m_mortarState < 0)
0258                 {
0259                     m_arena->removeCellElement(currentRow, currentCol, this);
0260                     m_arena->setCellElement(newRow, newCol, this);
0261                 }
0262                 move(m_x + m_xSpeed, m_y + m_ySpeed);
0263             }
0264         }
0265         else
0266         {
0267             //if two bombs move towards them, stop them and move them to the next cell center
0268             if(((bIsHurdleCurrentCell && !bMoveAwayFromCenter) || bIsHurdleNextCell) && !m_stopOnCenter && m_mortarState < 0)
0269             {
0270                 if(bOnCenter)
0271                 {
0272                     setXSpeed(0);
0273                     setYSpeed(0);
0274                 }
0275                 else
0276                 {
0277                     m_stopOnCenter = true;
0278                     setXSpeed(-m_xSpeed);
0279                     setYSpeed(-m_ySpeed);
0280                 }
0281             }
0282             else
0283             {
0284                 if((newRow != currentRow || newCol != currentCol) && m_mortarState < 0)
0285                 {
0286                     m_arena->removeCellElement(currentRow, currentCol, this);
0287                     m_arena->setCellElement(newRow, newCol, this);
0288                 }
0289                 move(m_x + m_xSpeed, m_y + m_ySpeed);
0290             }
0291         }
0292     }
0293 
0294     if(m_mortarState >= 0 && !(m_mortarTimer->isActive()))
0295     {
0296         updateMortarState();
0297     }
0298 }
0299 
0300 void Bomb::move(qreal x, qreal y)
0301 {
0302     // Move the Bomb
0303     m_x = x;
0304     m_y = y;
0305     Q_EMIT moved(m_x, m_y);
0306 }
0307 
0308 qreal Bomb::getXSpeed() const
0309 {
0310     return m_xSpeed;
0311 }
0312 
0313 qreal Bomb::getYSpeed() const
0314 {
0315     return m_ySpeed;
0316 }
0317 
0318 qreal Bomb::getSpeed()
0319 {
0320     return m_speed;
0321 }
0322 
0323 void Bomb::setXSpeed(qreal p_xSpeed)
0324 {
0325     m_xSpeed = p_xSpeed;
0326 }
0327 
0328 void Bomb::setYSpeed(qreal p_ySpeed)
0329 {
0330     m_ySpeed = p_ySpeed;
0331 }
0332 
0333 void Bomb::setThrown(int nDirection)
0334 {
0335     if(!m_mortarTimer)
0336     {
0337         m_mortarTimer = new QTimer;
0338         m_mortarTimer->setSingleShot(true);
0339         if(m_detonationCountdownTimer)
0340         {
0341             m_detonationCountdownTimer->stop();
0342         }
0343     }
0344     qreal fSpeed = 2 * Granatier::CellSize / (Granatier::FPS * 800 / 1000.0 + 1);
0345     switch(nDirection)
0346     {
0347         case Granatier::Direction::NORTH:
0348             setXSpeed(0);
0349             setYSpeed(-fSpeed);
0350             break;
0351         case Granatier::Direction::EAST:
0352             setXSpeed(fSpeed);
0353             setYSpeed(0);
0354             break;
0355         case Granatier::Direction::SOUTH:
0356             setXSpeed(0);
0357             setYSpeed(fSpeed);
0358             break;
0359         case Granatier::Direction::WEST:
0360             setXSpeed(-fSpeed);
0361             setYSpeed(0);
0362             break;
0363     }
0364 
0365     int curCellRow = m_arena->getRowFromY(m_y);
0366     int curCellCol = m_arena->getColFromX(m_x);
0367     m_arena->removeCellElement(curCellRow, curCellCol, this);
0368 
0369     m_type = Granatier::Element::NONE;
0370     m_thrown = true;
0371     m_mortarState = nMortarRampEnd;
0372 }
0373 
0374 void Bomb::setKicked(int nDirection)
0375 {
0376     switch(nDirection)
0377     {
0378         case Granatier::Direction::NORTH:
0379             setXSpeed(0);
0380             setYSpeed(-5);
0381             break;
0382         case Granatier::Direction::EAST:
0383             setXSpeed(5);
0384             setYSpeed(0);
0385             break;
0386         case Granatier::Direction::SOUTH:
0387             setXSpeed(0);
0388             setYSpeed(5);
0389             break;
0390         case Granatier::Direction::WEST:
0391             setXSpeed(-5);
0392             setYSpeed(0);
0393             break;
0394     }
0395 }
0396 
0397 bool Bomb::onCenter()
0398 {
0399     // Get the current cell center coordinates
0400     qreal centerX = (m_arena->getColFromX(m_x) + 0.5) * Granatier::CellSize;
0401     qreal centerY = (m_arena->getRowFromY(m_y) + 0.5) * Granatier::CellSize;
0402     bool willGoPast = false;
0403 
0404     // Will the character go past the center of the cell it's on ?
0405     // If goes right
0406     if (m_xSpeed > 0) {
0407         willGoPast = (m_x <= centerX && m_x + m_xSpeed >= centerX);
0408     }
0409     // If goes left
0410     else if (m_xSpeed < 0) {
0411         willGoPast = (m_x >= centerX && m_x + m_xSpeed <= centerX);
0412     }
0413     // If goes down
0414     else if (m_ySpeed > 0) {
0415         willGoPast = (m_y <= centerY && m_y + m_ySpeed >= centerY);
0416     }
0417     // If goes up
0418     else if (m_ySpeed < 0) {
0419         willGoPast = (m_y >= centerY && m_y + m_ySpeed <= centerY);
0420     }
0421     // If does not move
0422     else {
0423         willGoPast = (m_x == centerX && m_y == centerY);
0424     }
0425 
0426     return willGoPast;
0427 }
0428 
0429 void Bomb::moveOnCenter()
0430 {
0431     setX((m_arena->getColFromX(m_x) + 0.5) * Granatier::CellSize);
0432     setY((m_arena->getRowFromY(m_y) + 0.5) * Granatier::CellSize);
0433 }
0434 
0435 int Bomb::bombPower()
0436 {
0437     return m_bombPower;
0438 }
0439 
0440 void Bomb::setBombPower(int bombPower)
0441 {
0442     m_bombPower = bombPower;
0443 }
0444 
0445 void Bomb::pause()
0446 {
0447     if(m_detonationCountdownTimer)
0448     {
0449         m_detonationCountdownTimer->stop();
0450     }
0451 }
0452 
0453 void Bomb::resume()
0454 {
0455     if(m_detonationCountdownTimer)
0456     {
0457         m_detonationCountdownTimer->start();
0458     }
0459 }
0460 
0461 void Bomb::detonate()
0462 {
0463     if(!m_detonated)
0464     {
0465         m_detonated = true;
0466         delete m_detonationCountdownTimer;
0467         m_detonationCountdownTimer = nullptr;
0468         Q_EMIT bombDetonated(this);
0469         Q_EMIT releaseBombArmory();
0470     }
0471 }
0472 
0473 bool Bomb::isDetonated()
0474 {
0475     return m_detonated;
0476 }
0477 
0478 int Bomb::explosionID()
0479 {
0480     return m_explosionID;
0481 }
0482 
0483 void Bomb::initDetonation(int nBombID, int nDetonationTimeout)
0484 {
0485     if(m_bombID == m_explosionID)
0486     {
0487         m_explosionID = nBombID;
0488     }
0489 
0490     if((!m_detonationCountdownTimer))
0491     {
0492         return;
0493     }
0494 
0495     if(m_detonationCountdownTimer->interval() > nDetonationTimeout)
0496     {
0497         m_detonationCountdownTimer->setInterval(nDetonationTimeout);
0498     }
0499 
0500     if(!(m_detonationCountdownTimer->isActive()))
0501     {
0502         m_detonationCountdownTimer->start();
0503     }
0504 }
0505 
0506 void Bomb::slot_detonationCompleted()
0507 {
0508     //TODO: call from game
0509     m_arena->removeCellElement(m_arena->getRowFromY(m_y), m_arena->getColFromX(m_x), this);
0510 }
0511 
0512 void Bomb::updateMortarState()
0513 {
0514     Q_EMIT mortar(m_mortarState, nMortarRampEnd, nMortarPeak, nMortarGround);
0515 
0516     if(m_mortarState <= 0)
0517     {
0518         int curCellRow = m_arena->getRowFromY(m_y);
0519         int curCellCol = m_arena->getColFromX(m_x);
0520 
0521         setXSpeed(0);
0522         setYSpeed(0);
0523 
0524         m_thrown = false;
0525         m_type = Granatier::Element::NONE;
0526         m_arena->removeCellElement(curCellRow, curCellCol, this);
0527     }
0528     else if(m_mortarState == nMortarRampEnd)
0529     {
0530         int curCellRow = m_arena->getRowFromY(m_y);
0531         int curCellCol = m_arena->getColFromX(m_x);
0532 
0533         if(!m_thrown)
0534         {
0535             int nRow;
0536             int nCol;
0537             bool bFound = false;
0538 
0539             do
0540             {
0541                 nRow = granatier::RNG::fromRange(0, m_arena->getNbRows()-1);
0542                 nCol = granatier::RNG::fromRange(0, m_arena->getNbColumns()-1);
0543                 if(m_arena->getCell(nRow, nCol).getType() != Granatier::Cell::WALL)
0544                 {
0545                     bFound = true;
0546                 }
0547             }
0548             while (!bFound);
0549 
0550             setXSpeed((nCol - curCellCol) * Granatier::CellSize / (Granatier::FPS * 800 / 1000.0));
0551             setYSpeed((nRow - curCellRow) * Granatier::CellSize / (Granatier::FPS * 800 / 1000.0));
0552         }
0553 
0554         m_type = Granatier::Element::NONE;
0555         m_arena->removeCellElement(curCellRow, curCellCol, this);
0556     }
0557     else if(m_mortarState == nMortarGround)
0558     {
0559         setXSpeed(0);
0560         setYSpeed(0);
0561     }
0562     else if (m_mortarState > nMortarGround)
0563     {
0564         int curCellRow = m_arena->getRowFromY(m_y);
0565         int curCellCol = m_arena->getColFromX(m_x);
0566         m_type = Granatier::Element::BOMB;
0567         m_arena->setCellElement(curCellRow, curCellCol, this);
0568 
0569         m_mortarState = -1;
0570         if(m_detonationCountdownTimer)
0571         {
0572             if(!m_thrown)
0573             {
0574                 initDetonation(m_bombID, 40);
0575             }
0576             else
0577             {
0578                 m_detonationCountdownTimer->setInterval(2000);
0579                 m_detonationCountdownTimer->start();
0580             }
0581         }
0582     }
0583 
0584     if(m_mortarState >= 0)
0585     {
0586         m_mortarState++;
0587     }
0588 }
0589 
0590 #include "moc_bomb.cpp"