File indexing completed on 2024-04-28 04:01:40

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 Alexandre Galinier <alex.galinier@hotmail.com>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "gamescene.h"
0010 #include "cell.h"
0011 #include "bonus.h"
0012 #include "bonusitem.h"
0013 #include "settings.h"
0014 #include "game.h"
0015 #include "player.h"
0016 #include "block.h"
0017 #include "blockitem.h"
0018 #include "bomb.h"
0019 #include "arena.h"
0020 #include "arenaitem.h"
0021 #include "playeritem.h"
0022 #include "bombitem.h"
0023 #include "bombexplosionitem.h"
0024 #include "infooverlay.h"
0025 #include "infosidebar.h"
0026 #include "granatier_random.h"
0027 
0028 #include <KGameTheme>
0029 #include <KGameThemeProvider>
0030 #include <KLocalizedString>
0031 
0032 #include <QGraphicsView>
0033 #include <QTimer>
0034 
0035 #include <KGameRenderer>
0036 #include <KGameRenderedItem>
0037 
0038 #include <cmath>
0039 #include <QStandardPaths>
0040 
0041 GameScene::GameScene(Game* p_game, KGameThemeProvider* p_themeProvider) : m_game(p_game), m_themeProvider(p_themeProvider)
0042 {
0043     connect(p_game, &Game::gameStarted, this, &GameScene::start);
0044     connect(p_game, &Game::pauseChanged, this, &GameScene::setPaused);
0045     connect(p_game, &Game::bombCreated, this, &GameScene::createBombItem);
0046     connect(p_game, &Game::infoChanged, this, &GameScene::updateInfo);
0047 
0048     m_SvgScaleFactor = 1;
0049 
0050     m_backgroundResizeTimer = new QTimer();
0051     m_backgroundResizeTimer->setSingleShot(true);
0052     connect(m_backgroundResizeTimer, &QTimer::timeout, this, &GameScene::resizeBackground);
0053 
0054     // Create the PlayerItems and the points labels
0055     QList <Player*> players = p_game->getPlayers();
0056 
0057     PlayerItem* playerItem;
0058     for(auto & player : players)
0059     {
0060         const QString desktopPath = player->getDesktopFilePath();
0061         auto* theme = new KGameTheme(desktopPath.toUtf8());
0062         theme->readFromDesktopFile(desktopPath);
0063         auto  playerRenderer = new KGameRenderer(theme);
0064         m_mapRendererPlayerItems.insert(player, playerRenderer);
0065         playerItem = new PlayerItem(player, playerRenderer);
0066         // Corrects the position of the player
0067         playerItem->update(player->getX(), player->getY());
0068         // Stops the player animation
0069         playerItem->stopAnim();
0070 
0071         m_playerItems.append(playerItem);
0072 
0073         connect(this, &GameScene::resizeGraphics, playerItem, &PlayerItem::updateGraphics);
0074         connect(playerItem, &PlayerItem::bonusItemTaken, this, &GameScene::removeBonusItem);
0075     }
0076 
0077     // The remaining time
0078     m_remainingTimeLabel = new QGraphicsTextItem(i18n("0:00"));
0079     m_remainingTimeLabel->setFont(QFont(QStringLiteral("Helvetica"), static_cast<int>(Granatier::CellSize * 0.35), QFont::Bold, false));
0080     m_remainingTimeLabel->setDefaultTextColor(QColor("#FFFF00"));
0081     m_remainingTimeLabel->setZValue(1000);
0082 
0083     m_arenaNameLabel = new QGraphicsTextItem(i18n("Arena Name"));
0084     m_arenaNameLabel->setFont(QFont(QStringLiteral("Helvetica"), static_cast<int>(Granatier::CellSize * 0.35), QFont::Bold, false));
0085     m_arenaNameLabel->setDefaultTextColor(QColor("#FFFF00"));
0086     m_arenaNameLabel->setZValue(1000);
0087 
0088     // setup the theme renderer
0089     m_rendererSelectedTheme = new KGameRenderer(m_themeProvider);
0090     m_rendererDefaultTheme = nullptr;
0091     setupThemeRenderer();
0092 
0093     connect(m_themeProvider, &KGameThemeProvider::currentThemeChanged, this, &GameScene::themeChanged);
0094 
0095     // create the info overlay
0096     m_infoOverlay = new InfoOverlay(m_game, this);
0097     connect(this, &GameScene::resizeGraphics, m_infoOverlay, &InfoOverlay::updateGraphics);
0098 
0099     init();
0100 }
0101 
0102 void GameScene::setupThemeRenderer()
0103 {
0104     bool selectedThemeIsDefault = true;
0105 
0106     if(m_themeProvider->currentTheme() != m_themeProvider->defaultTheme())
0107     {
0108         // Load the default SVG file as fallback
0109         selectedThemeIsDefault = false;
0110         if(m_rendererDefaultTheme == nullptr)
0111         {
0112             auto* theme = new KGameTheme(m_themeProvider->defaultTheme()->identifier());
0113             theme->setGraphicsPath(m_themeProvider->defaultTheme()->graphicsPath());
0114             m_rendererDefaultTheme = new KGameRenderer(theme);
0115         }
0116     }
0117 
0118     if(selectedThemeIsDefault || m_rendererSelectedTheme->spriteExists(QStringLiteral("background")))
0119     {
0120         m_rendererBackground = m_rendererSelectedTheme;
0121     }
0122     else
0123     {
0124         m_rendererBackground = m_rendererDefaultTheme;
0125     }
0126 
0127     // set the renderer for the arena items TODO: add all the arena items
0128     if(selectedThemeIsDefault || (m_rendererSelectedTheme->spriteExists(QStringLiteral("arena_ground")) &&
0129         m_rendererSelectedTheme->spriteExists(QStringLiteral("arena_wall")) &&
0130         m_rendererSelectedTheme->spriteExists(QStringLiteral("arena_block")) &&
0131         m_rendererSelectedTheme->spriteExists(QStringLiteral("arena_block_highlight")) &&
0132         m_rendererSelectedTheme->spriteExists(QStringLiteral("arena_ice")) &&
0133         m_rendererSelectedTheme->spriteExists(QStringLiteral("arena_bomb_mortar")) &&
0134         m_rendererSelectedTheme->spriteExists(QStringLiteral("arena_arrow_up")) &&
0135         m_rendererSelectedTheme->spriteExists(QStringLiteral("arena_arrow_right")) &&
0136         m_rendererSelectedTheme->spriteExists(QStringLiteral("arena_arrow_down")) &&
0137         m_rendererSelectedTheme->spriteExists(QStringLiteral("arena_arrow_left"))))
0138     {
0139         m_rendererArenaItems = m_rendererSelectedTheme;
0140     }
0141     else
0142     {
0143         m_rendererArenaItems = m_rendererDefaultTheme;
0144     }
0145 
0146     // set the renderer for the bonus items TODO: add all the bonus items
0147     if(selectedThemeIsDefault || (m_rendererSelectedTheme->spriteExists(QStringLiteral("bonus_speed")) &&
0148         m_rendererSelectedTheme->spriteExists(QStringLiteral("bonus_bomb")) &&
0149         m_rendererSelectedTheme->spriteExists(QStringLiteral("bonus_power")) &&
0150         m_rendererSelectedTheme->spriteExists(QStringLiteral("bonus_shield")) &&
0151         m_rendererSelectedTheme->spriteExists(QStringLiteral("bonus_throw")) &&
0152         m_rendererSelectedTheme->spriteExists(QStringLiteral("bonus_kick")) &&
0153         m_rendererSelectedTheme->spriteExists(QStringLiteral("bonus_bad_slow")) &&
0154         m_rendererSelectedTheme->spriteExists(QStringLiteral("bonus_bad_hyperactive")) &&
0155         m_rendererSelectedTheme->spriteExists(QStringLiteral("bonus_bad_mirror")) &&
0156         m_rendererSelectedTheme->spriteExists(QStringLiteral("bonus_bad_scatty")) &&
0157         m_rendererSelectedTheme->spriteExists(QStringLiteral("bonus_bad_restrain")) &&
0158         m_rendererSelectedTheme->spriteExists(QStringLiteral("bonus_neutral_pandora")) &&
0159         m_rendererSelectedTheme->spriteExists(QStringLiteral("bonus_neutral_resurrect"))))
0160     {
0161         m_rendererBonusItems = m_rendererSelectedTheme;
0162     }
0163     else
0164     {
0165         m_rendererBonusItems = m_rendererDefaultTheme;
0166     }
0167 
0168     // set the renderer for the bomb items
0169     if(selectedThemeIsDefault || (m_rendererSelectedTheme->spriteExists(QStringLiteral("bomb")) &&
0170         m_rendererSelectedTheme->spriteExists(QStringLiteral("bomb_blast_core_0")) &&
0171         m_rendererSelectedTheme->spriteExists(QStringLiteral("bomb_blast_north_0")) &&
0172         m_rendererSelectedTheme->spriteExists(QStringLiteral("bomb_blast_east_0")) &&
0173         m_rendererSelectedTheme->spriteExists(QStringLiteral("bomb_blast_south_0")) &&
0174         m_rendererSelectedTheme->spriteExists(QStringLiteral("bomb_blast_west_0"))))
0175     {
0176         m_rendererBombItems = m_rendererSelectedTheme;
0177     }
0178     else
0179     {
0180         m_rendererBombItems = m_rendererDefaultTheme;
0181     }
0182 
0183     // set the renderer for the score items
0184     if(selectedThemeIsDefault || (m_rendererSelectedTheme->spriteExists(QStringLiteral("score_star_enabled")) &&
0185         m_rendererSelectedTheme->spriteExists(QStringLiteral("score_star_disabled"))))
0186     {
0187         m_rendererScoreItems = m_rendererSelectedTheme;
0188     }
0189     else
0190     {
0191         m_rendererScoreItems = m_rendererDefaultTheme;
0192     }
0193 }
0194 
0195 void GameScene::init()
0196 {
0197     initItemsWithGraphicsFromTheme();
0198 
0199     // Display each PlayerItem
0200     for (auto & playerItem : m_playerItems)
0201     {
0202         if(!items().contains(playerItem))
0203         {
0204             addItem(playerItem);
0205         }
0206         playerItem->resurrect();
0207     }
0208 
0209     if (!items().contains(m_remainingTimeLabel))
0210     {
0211         addItem(m_remainingTimeLabel);
0212     }
0213     m_remainingTimeLabel->setDefaultTextColor(QColor("#FFFF00"));
0214     int nTime = m_game->getRemainingTime();
0215     m_remainingTimeLabel->setPlainText(QStringLiteral("%1:%2").arg(nTime/60).arg(nTime%60, 2, 10, QLatin1Char('0')));
0216     m_remainingTimeLabel->setPos(Granatier::CellSize * m_game->getArena()->getNbColumns() - m_remainingTimeLabel->boundingRect().width(), - m_remainingTimeLabel->boundingRect().height());
0217 
0218     if (!items().contains(m_arenaNameLabel))
0219     {
0220         addItem(m_arenaNameLabel);
0221     }
0222     m_arenaNameLabel->setPlainText(m_game->getArena()->getName());
0223     m_arenaNameLabel->setPos(0, - m_arenaNameLabel->boundingRect().height());
0224 
0225     //this is needed for info sidebar
0226     setSceneRect(0, -m_remainingTimeLabel->boundingRect().height(),
0227                  m_game->getArena()->getNbColumns()*Granatier::CellSize,
0228                  m_game->getArena()->getNbRows()*Granatier::CellSize + m_remainingTimeLabel->boundingRect().height());
0229 
0230     // create the info sidebar
0231     m_infoSidebar = new InfoSidebar(m_game, this);
0232     connect(this, &GameScene::resizeGraphics, m_infoSidebar, &InfoSidebar::updateGraphics);
0233 
0234     //update the sceneRect
0235     QRectF oldSceneRect = sceneRect();
0236     QRectF sidebarRect = m_infoSidebar->rect();
0237     QRectF newSceneRect;
0238     newSceneRect.setLeft(oldSceneRect.left() < sidebarRect.left() ? oldSceneRect.left() : sidebarRect.left());
0239     newSceneRect.setRight(oldSceneRect.right() > sidebarRect.right() ? oldSceneRect.right() : sidebarRect.right());
0240     newSceneRect.setTop(oldSceneRect.top() < sidebarRect.top() ? oldSceneRect.top() : sidebarRect.top());
0241     newSceneRect.setBottom(oldSceneRect.bottom() > sidebarRect.bottom() ? oldSceneRect.bottom() : sidebarRect.bottom());
0242     newSceneRect.adjust(-5, -5, 5, 5);
0243     setSceneRect(newSceneRect);
0244     m_infoSidebar->reset();
0245 
0246     m_infoOverlay->showGetReady();
0247 
0248     //set the minimum size for the scene
0249     m_minSize = sceneRect();
0250     int minWidth = static_cast<int>((m_minSize.width() / Granatier::CellSize + 1.1) * Granatier::CellSize);
0251     int minHeight = static_cast<int>((m_minSize.height() / Granatier::CellSize + 1.1) * Granatier::CellSize);
0252     m_minSize.setX(m_minSize.x() + (m_minSize.width() - minWidth) / 10);
0253     m_minSize.setY(m_minSize.y() + (m_minSize.height() - minHeight) / 4);
0254     m_minSize.setWidth(minWidth);
0255     m_minSize.setHeight(minHeight);
0256     setSceneRect(m_minSize);
0257 
0258     resizeSprites();
0259 }
0260 
0261 void GameScene::initItemsWithGraphicsFromTheme()
0262 {
0263     // Create the ArenaItems
0264     m_arenaItem = new ArenaItem**[m_game->getArena()->getNbRows()];
0265 
0266     for (int i = 0; i < m_game->getArena()->getNbRows(); ++i)
0267     {
0268         m_arenaItem[i] = new ArenaItem*[m_game->getArena()->getNbColumns()];
0269         for (int j = 0; j < m_game->getArena()->getNbColumns(); ++j)
0270         {
0271             // Create the ArenaItem and set the image
0272             auto* arenaItem = new ArenaItem(j * Granatier::CellSize, i * Granatier::CellSize, m_rendererArenaItems, QStringLiteral(""));
0273             connect(this, &GameScene::resizeGraphics, arenaItem, &ArenaItem::updateGraphics);
0274 
0275             //TODO: use this function call
0276             //arenaItem->setElementId(m_game->getArena()->getCell(i,j).getElement()->getImageId());
0277             switch(m_game->getArena()->getCell(i,j).getType())
0278             {
0279                 case Granatier::Cell::WALL:
0280                     arenaItem->setSpriteKey(QStringLiteral("arena_wall"));
0281                     arenaItem->setZValue(-2);
0282                     break;
0283                 case Granatier::Cell::HOLE:
0284                     delete arenaItem;
0285                     arenaItem = nullptr;
0286                     break;
0287                 case Granatier::Cell::ICE:
0288                     arenaItem->setSpriteKey(QStringLiteral("arena_ice"));
0289                     arenaItem->setZValue(0);
0290                     break;
0291                 case Granatier::Cell::BOMBMORTAR:
0292                     arenaItem->setSpriteKey(QStringLiteral("arena_bomb_mortar"));
0293                     arenaItem->setZValue(0);
0294                     break;
0295                 case Granatier::Cell::ARROWUP:
0296                     arenaItem->setSpriteKey(QStringLiteral("arena_arrow_up"));
0297                     arenaItem->setZValue(0);
0298                     break;
0299                 case Granatier::Cell::ARROWRIGHT:
0300                     arenaItem->setSpriteKey(QStringLiteral("arena_arrow_right"));
0301                     arenaItem->setZValue(0);
0302                     break;
0303                 case Granatier::Cell::ARROWDOWN:
0304                     arenaItem->setSpriteKey(QStringLiteral("arena_arrow_down"));
0305                     arenaItem->setZValue(0);
0306                     break;
0307                 case Granatier::Cell::ARROWLEFT:
0308                     arenaItem->setSpriteKey(QStringLiteral("arena_arrow_left"));
0309                     arenaItem->setZValue(0);
0310                     break;
0311                 case Granatier::Cell::GROUND:
0312                 case Granatier::Cell::BLOCK:
0313                 default:
0314                     arenaItem->setSpriteKey(QStringLiteral("arena_ground"));
0315                     arenaItem->setZValue(-1);
0316             }
0317             m_arenaItem[i][j] = arenaItem;
0318         }
0319     }
0320 
0321     // Create the Block and Bonus items
0322     m_blockItems = new BlockItem**[m_game->getArena()->getNbRows()];
0323     m_bonusItems = new BonusItem**[m_game->getArena()->getNbRows()];
0324     QList<Element*> blockElements;
0325     for (int i = 0; i < m_game->getArena()->getNbRows(); ++i)
0326     {
0327         m_blockItems[i] = new BlockItem*[m_game->getArena()->getNbColumns()];
0328         m_bonusItems[i] = new BonusItem*[m_game->getArena()->getNbColumns()];
0329         for (int j = 0; j < m_game->getArena()->getNbColumns(); ++j)
0330         {
0331             blockElements = m_game->getArena()->getCell(i, j).getElements(Granatier::Element::BLOCK);
0332             if (!blockElements.isEmpty())
0333             {
0334                 // Create the element item and set the image
0335                 for(const auto& element: std::as_const(blockElements))
0336                 {
0337                     auto* block = dynamic_cast <Block*> (element);
0338                     auto* blockItem = new BlockItem(block, m_rendererArenaItems);
0339                     connect(this, &GameScene::resizeGraphics, blockItem, &BlockItem::updateGraphics);
0340                     blockItem->setSpriteKey(block->getImageId());
0341                     blockItem->update(block->getX(), block->getY());
0342                     blockItem->setZValue(200);
0343                     if(Settings::self()->showAllTiles() == 1)
0344                     {
0345                         blockItem->setZValue(99);
0346                     }
0347                     connect(this, &GameScene::resizeGraphics, blockItem, &BlockItem::updateGraphics);
0348                     connect(blockItem, &BlockItem::blockItemDestroyed, this, &GameScene::removeBlockItem);
0349                     m_blockItems[i][j] = blockItem;
0350                     // if the block contains a hidden bonus, create the bonus item
0351                     Bonus* bonus = block->getBonus();
0352                     if(bonus)
0353                     {
0354                         auto* bonusItem = new BonusItem(bonus, m_rendererBonusItems);
0355                         switch(bonus->getBonusType())
0356                         {
0357                             case Granatier::Bonus::SPEED:
0358                                 bonusItem->setSpriteKey(QStringLiteral("bonus_speed"));
0359                                 break;
0360                             case Granatier::Bonus::BOMB:
0361                                 bonusItem->setSpriteKey(QStringLiteral("bonus_bomb"));
0362                                 break;
0363                             case Granatier::Bonus::POWER:
0364                                 bonusItem->setSpriteKey(QStringLiteral("bonus_power"));
0365                                 break;
0366                             case Granatier::Bonus::SHIELD:
0367                                 bonusItem->setSpriteKey(QStringLiteral("bonus_shield"));
0368                                 break;
0369                             case Granatier::Bonus::THROW:
0370                                 bonusItem->setSpriteKey(QStringLiteral("bonus_throw"));
0371                                 break;
0372                             case Granatier::Bonus::KICK:
0373                                 bonusItem->setSpriteKey(QStringLiteral("bonus_kick"));
0374                                 break;
0375                             case Granatier::Bonus::HYPERACTIVE:
0376                                 bonusItem->setSpriteKey(QStringLiteral("bonus_bad_hyperactive"));
0377                                 break;
0378                             case Granatier::Bonus::SLOW:
0379                                 bonusItem->setSpriteKey(QStringLiteral("bonus_bad_slow"));
0380                                 break;
0381                             case Granatier::Bonus::MIRROR:
0382                                 bonusItem->setSpriteKey(QStringLiteral("bonus_bad_mirror"));
0383                                 break;
0384                             case Granatier::Bonus::SCATTY:
0385                                 bonusItem->setSpriteKey(QStringLiteral("bonus_bad_scatty"));
0386                                 break;
0387                             case Granatier::Bonus::RESTRAIN:
0388                                 bonusItem->setSpriteKey(QStringLiteral("bonus_bad_restrain"));
0389                                 break;
0390                             case Granatier::Bonus::RESURRECT:
0391                                 bonusItem->setSpriteKey(QStringLiteral("bonus_neutral_resurrect"));
0392                                 break;
0393                             default:
0394                                 bonusItem->setSpriteKey(QStringLiteral("bonus_neutral_pandora"));
0395                         }
0396 
0397                         if(granatier::RNG::fromRange(0, 10) > 9 && bonusItem->spriteKey() != QStringLiteral("bonus_neutral_resurrect"))
0398                         {
0399                             bonusItem->setSpriteKey(QStringLiteral("bonus_neutral_pandora"));
0400                         }
0401 
0402                         bonusItem->update(bonus->getX(), bonus->getY());
0403                         bonusItem->setZValue(100);
0404                         m_bonusItems[i][j] = bonusItem;
0405 
0406                         connect(this, &GameScene::resizeGraphics, bonusItem, &BonusItem::updateGraphics);
0407                         connect(bonusItem, &BonusItem::bonusItemDestroyed, this, &GameScene::removeBonusItem);
0408 
0409                         addItem(bonusItem);
0410                         if(Settings::self()->showAllTiles() == 0)
0411                         {
0412                             bonusItem->hide();
0413                         }
0414                     }
0415                     else
0416                     {
0417                         m_bonusItems[i][j] = nullptr;
0418                     }
0419                 }
0420             }
0421             else
0422             {
0423                 m_blockItems[i][j] = nullptr;
0424                 m_bonusItems[i][j] = nullptr;
0425             }
0426         }
0427     }
0428 
0429     // Create the Bomb items
0430     for(auto& bomb: m_tempBombList)
0431     {
0432         createBombItem(bomb);
0433     }
0434     if(!m_tempBombList.isEmpty())
0435     {
0436         QHash<BombItem*, QList<BombExplosionItem*> >::iterator i = m_bombItems.begin();
0437         while (i != m_bombItems.end())
0438         {
0439             i.key()->pauseAnim();
0440             i++;
0441         }
0442     }
0443 
0444     // Display the ArenaItem
0445     for (int i = 0; i < m_game->getArena()->getNbRows();++i)
0446     {
0447         for (int j = 0; j < m_game->getArena()->getNbColumns(); ++j)
0448         {
0449             if(m_arenaItem[i][j] != nullptr)
0450             {
0451                 addItem(m_arenaItem[i][j]);
0452             }
0453         }
0454     }
0455 
0456     // Display the Block Items
0457     for (int i = 0; i < m_game->getArena()->getNbRows(); ++i)
0458     {
0459         for (int j = 0; j < m_game->getArena()->getNbColumns(); ++j)
0460         {
0461             if (m_blockItems[i][j] != nullptr)
0462             {
0463                 if (!items().contains(m_blockItems[i][j]))
0464                 {
0465                     addItem(m_blockItems[i][j]);
0466                 }
0467             }
0468         }
0469     }
0470 
0471     //create the background
0472     m_arenaBackground = new KGameRenderedItem(m_rendererBackground, QStringLiteral("background"));
0473     m_arenaBackground->setZValue(-5);
0474     m_arenaBackground->setPos(0, 0);
0475     m_arenaBackground->setCacheMode(QGraphicsItem::NoCache);  // if cache is set, there are some artifacts; pay attention, that the KGameRenderer cache is used nevertheless
0476     m_arenaBackground->setRenderSize(QSize(100, 100)); //just to get something in the background, until the right size is rendered
0477     addItem(m_arenaBackground);
0478 
0479     resizeSprites();
0480 }
0481 
0482 GameScene::~GameScene()
0483 {
0484     delete m_backgroundResizeTimer;
0485 
0486     cleanUp();
0487 
0488     delete m_infoOverlay;
0489 
0490     for (auto & playerItem : m_playerItems)
0491     {
0492         if(items().contains(playerItem))
0493         {
0494             removeItem(playerItem);
0495         }
0496         playerItem->stopAnim();
0497         delete playerItem;
0498     }
0499 
0500     QMap <Player*, KGameRenderer*>::iterator iteratorRendererPlayer = m_mapRendererPlayerItems.begin();
0501     while (iteratorRendererPlayer != m_mapRendererPlayerItems.end())
0502     {
0503         delete iteratorRendererPlayer.value();
0504         iteratorRendererPlayer++;
0505     }
0506 
0507     delete m_rendererSelectedTheme;
0508     delete m_rendererDefaultTheme;
0509 }
0510 
0511 void GameScene::cleanUp()
0512 {
0513     cleanUpItemsWithGraphicsFromTheme();
0514 
0515     delete m_infoSidebar;
0516 
0517     if(items().contains(m_remainingTimeLabel))
0518     {
0519         removeItem(m_remainingTimeLabel);
0520     }
0521 
0522     if(items().contains(m_arenaNameLabel))
0523     {
0524         removeItem(m_arenaNameLabel);
0525     }
0526 }
0527 
0528 void GameScene::cleanUpItemsWithGraphicsFromTheme()
0529 {
0530     // remove the arena items
0531     for (int i = 0; i < m_game->getArena()->getNbRows();++i)
0532     {
0533         for (int j = 0; j < m_game->getArena()->getNbColumns(); ++j)
0534         {
0535             if (m_arenaItem[i][j] != nullptr)
0536             {
0537                 if (items().contains(m_arenaItem[i][j]))
0538                 {
0539                     removeItem(m_arenaItem[i][j]);
0540                 }
0541                 delete m_arenaItem[i][j];
0542             }
0543         }
0544         delete[] m_arenaItem[i];
0545     }
0546     delete[] m_arenaItem;
0547 
0548     // Find the BombItem and remove it
0549     BombExplosionItem* bombExplosionItem;
0550     QHash<BombItem*, QList<BombExplosionItem*> >::iterator i = m_bombItems.begin();
0551     while (i != m_bombItems.end())
0552     {
0553         while(!i.value().isEmpty())
0554         {
0555             bombExplosionItem = i.value().takeFirst();
0556             if(items().contains(bombExplosionItem))
0557             {
0558                 removeItem(bombExplosionItem);
0559             }
0560             delete bombExplosionItem;
0561         }
0562         if(items().contains(i.key()))
0563         {
0564             removeItem(i.key());
0565         }
0566         if(dynamic_cast <Bomb*> (i.key()->getModel())->isDetonated())
0567         {
0568             dynamic_cast <Bomb*> (i.key()->getModel())->slot_detonationCompleted();
0569         }
0570         delete i.key();
0571         i = m_bombItems.erase(i);
0572     }
0573 
0574     // Find the BlockItems and BonusItems and remove it
0575     for (int i = 0; i < m_game->getArena()->getNbRows();++i)
0576     {
0577         for (int j = 0; j < m_game->getArena()->getNbColumns(); ++j)
0578         {
0579             if (m_blockItems[i][j] != nullptr)
0580             {
0581                 if (items().contains(m_blockItems[i][j]))
0582                 {
0583                     removeItem(m_blockItems[i][j]);
0584                 }
0585                 delete m_blockItems[i][j];
0586             }
0587             if (m_bonusItems[i][j] != nullptr)
0588             {
0589                 if (items().contains(m_bonusItems[i][j]))
0590                 {
0591                     removeItem(m_bonusItems[i][j]);
0592                 }
0593                 delete m_bonusItems[i][j];
0594             }
0595         }
0596         delete[] m_blockItems[i];
0597         delete[] m_bonusItems[i];
0598     }
0599     delete[] m_blockItems;
0600     delete[] m_bonusItems;
0601 
0602     removeItem(m_arenaBackground);
0603     delete m_arenaBackground;
0604 }
0605 
0606 void GameScene::themeChanged()
0607 {
0608     m_tempBombList.clear();
0609     QHash<BombItem*, QList<BombExplosionItem*> >::iterator i = m_bombItems.begin();
0610     while (i != m_bombItems.end())
0611     {
0612         if(!(dynamic_cast <Bomb*> (i.key()->getModel())->isDetonated()))
0613         {
0614             m_tempBombList.append(dynamic_cast <Bomb*> (i.key()->getModel()));
0615         }
0616         i++;
0617     }
0618 
0619     cleanUpItemsWithGraphicsFromTheme();
0620     setupThemeRenderer();
0621     initItemsWithGraphicsFromTheme();
0622     m_infoSidebar->themeChanged();
0623     m_infoOverlay->themeChanged();
0624 
0625     m_tempBombList.clear();
0626 }
0627 
0628 KGameRenderer* GameScene::renderer(Granatier::Element::Type type, Player* player)
0629 {
0630     switch(type)
0631     {
0632         case Granatier::Element::BLOCK:
0633             return m_rendererArenaItems;
0634         case Granatier::Element::BONUS:
0635             return m_rendererBonusItems;
0636         case Granatier::Element::BOMB:
0637             return m_rendererBombItems;
0638         case Granatier::Element::PLAYER:
0639             return m_mapRendererPlayerItems.value(player);
0640         case Granatier::Element::SCORE:
0641             return m_rendererScoreItems;
0642         default:
0643             return nullptr;
0644     }
0645 }
0646 
0647 void GameScene::showScore()
0648 {
0649     m_infoOverlay->showScore();
0650 }
0651 
0652 void GameScene::resizeSprites(int delayForBackground)
0653 {
0654     if(views().isEmpty())
0655     {
0656         return;
0657     }
0658 
0659     //calculate the scaling factor for the SVGs
0660     int horizontalPixelsPerCell = static_cast<int>(views().constFirst()->size().width() / (m_minSize.width()/Granatier::CellSize));
0661     int verticalPixelsPerCell = static_cast<int>(views().constFirst()->size().height() / (m_minSize.height()/Granatier::CellSize));
0662     if(horizontalPixelsPerCell < verticalPixelsPerCell)
0663     {
0664         m_SvgScaleFactor = Granatier::CellSize / horizontalPixelsPerCell;
0665     }
0666     else
0667     {
0668         m_SvgScaleFactor = Granatier::CellSize / verticalPixelsPerCell;
0669     }
0670     QTransform transform;
0671     transform.scale(1/m_SvgScaleFactor, 1/m_SvgScaleFactor);
0672     views().constFirst()->setTransform(transform);
0673     views().constFirst()->centerOn(sceneRect().center());
0674     views().constFirst()->updateSceneRect(m_minSize);;
0675 
0676     //update pixmaps
0677     Q_EMIT resizeGraphics(m_SvgScaleFactor);
0678 
0679     //update overlay
0680     QRect viewRect = views().constFirst()->rect();
0681     QRectF viewRectToScene = QRectF(views().constFirst()->mapToScene(viewRect.topLeft()), views().constFirst()->mapToScene(viewRect.bottomRight()));
0682     m_infoOverlay->resizeDimmOverlay(viewRectToScene.x(), viewRectToScene.y(), viewRectToScene.width(), viewRectToScene.height());
0683 
0684     //update background pixmap
0685     m_arenaBackground->setPos(views().constFirst()->mapToScene(viewRect.topLeft()));
0686     m_arenaBackground->setScale(m_SvgScaleFactor);
0687 
0688     m_arenaBackground->setPixmap(m_arenaBackground->pixmap().scaled(viewRect.size()));
0689 
0690     m_backgroundResizeTimer->stop();
0691     m_backgroundResizeTimer->start(delayForBackground);
0692 }
0693 
0694 void GameScene::resizeBackground()
0695 {
0696     if(views().isEmpty())
0697     {
0698         return;
0699     }
0700     QRect viewRect = views().constFirst()->rect();
0701     m_arenaBackground->setRenderSize(viewRect.size());
0702 }
0703 
0704 Game* GameScene::getGame() const
0705 {
0706     return m_game;
0707 }
0708 
0709 void GameScene::start()
0710 {
0711     // hide the info items
0712     m_infoOverlay->hideItems();
0713 }
0714 
0715 void GameScene::setPaused(const bool p_pause, const bool p_fromUser)
0716 {
0717     // If the game has paused
0718     if (p_pause)
0719     {
0720         // If the pause is due to an action from the user
0721         if (p_fromUser)
0722         {
0723             m_infoOverlay->showPause();
0724         }
0725 
0726         // Stop player animation
0727         for (auto & playerItem : m_playerItems)
0728         {
0729             playerItem->pauseAnim();
0730         }
0731         // Stop bomb animation
0732         QHash<BombItem*, QList<BombExplosionItem*> >::iterator i = m_bombItems.begin();
0733         while (i != m_bombItems.end())
0734         {
0735             i.key()->pauseAnim();
0736             ++i;
0737         }
0738     }
0739     else
0740     {   // If the game has resumed, hide the info items
0741         m_infoOverlay->hideItems();
0742 
0743         // Resume player animation
0744         for (auto & playerItem : m_playerItems)
0745         {
0746             playerItem->resumeAnim();
0747         }
0748         // Resume bomb animation
0749         QHash<BombItem*, QList<BombExplosionItem*> >::iterator i = m_bombItems.begin();
0750         while (i != m_bombItems.end())
0751         {
0752             i.key()->resumeAnim();
0753             ++i;
0754         }
0755     }
0756 }
0757 
0758 void GameScene::removeBlockItem(BlockItem* blockItem)
0759 {
0760     // remove the Bonus Items
0761     for (int i = 0; i < m_game->getArena()->getNbRows(); ++i)
0762     {
0763         for (int j = 0; j < m_game->getArena()->getNbColumns(); ++j)
0764         {
0765             if (m_blockItems[i][j] != nullptr && m_blockItems[i][j] == blockItem)
0766             {
0767                 if (items().contains(m_blockItems[i][j]))
0768                 {
0769                     removeItem(m_blockItems[i][j]);
0770                     m_blockItems[i][j] = nullptr;
0771                     m_game->blockDestroyed(i, j, dynamic_cast <Block*> (blockItem->getModel()));
0772                     delete blockItem;
0773                 }
0774             }
0775         }
0776     }
0777 }
0778 
0779 void GameScene::removeBonusItem(BonusItem* bonusItem)
0780 {
0781     // remove the Bonus Items
0782     for (int i = 0; i < m_game->getArena()->getNbRows(); ++i)
0783     {
0784         for (int j = 0; j < m_game->getArena()->getNbColumns(); ++j)
0785         {
0786             if (m_bonusItems[i][j] != nullptr && m_bonusItems[i][j] == bonusItem)
0787             {
0788                 if (items().contains(m_bonusItems[i][j]))
0789                 {
0790                     removeItem(m_bonusItems[i][j]);
0791                     m_bonusItems[i][j] = nullptr;
0792                     m_game->removeBonus(dynamic_cast <Bonus*> (bonusItem->getModel()));
0793                     delete bonusItem;
0794                 }
0795             }
0796         }
0797     }
0798 }
0799 
0800 void GameScene::updateInfo(const Granatier::Info::Type p_info)
0801 {
0802     if(p_info == Granatier::Info::TimeInfo)
0803     {
0804         int nTime = m_game->getRemainingTime();
0805         if(nTime > 0)
0806         {
0807             m_remainingTimeLabel->setPlainText(QStringLiteral("%1:%2").arg(nTime/60).arg(nTime%60, 2, 10, QLatin1Char('0')));
0808         }
0809         else
0810         {
0811             m_remainingTimeLabel->setPlainText(i18n("Sudden Death"));
0812             m_remainingTimeLabel->setDefaultTextColor(QColor("#FF0000"));
0813             m_remainingTimeLabel->setPos(Granatier::CellSize * m_game->getArena()->getNbColumns() - m_remainingTimeLabel->boundingRect().width(), - m_remainingTimeLabel->boundingRect().height());
0814         }
0815     }
0816 }
0817 
0818 void GameScene::createBombItem(Bomb* bomb)
0819 {
0820     // Create the Bombs
0821     auto* bombItem = new BombItem(bomb, m_rendererBombItems);
0822     // Corrects the position of the BombItem
0823     bombItem->update(bomb->getX(), bomb->getY());
0824     addItem(bombItem);
0825     m_bombItems[bombItem].append(nullptr);
0826 
0827     bombItem->updateGraphics(m_SvgScaleFactor); //TODO: use a Renderer class and get the scale factor from a static function during initialization
0828 
0829     connect(this, &GameScene::resizeGraphics, bombItem, &BombItem::updateGraphics);
0830     connect(bomb, &Bomb::mortar, bombItem, &BombItem::updateMortar);
0831     connect(bomb, &Bomb::bombDetonated, this, &GameScene::bombDetonated);
0832     connect(bombItem, &BombItem::bombItemFinished, this, &GameScene::removeBombItem);
0833     connect(bombItem, &BombItem::animationFrameChanged, this, &GameScene::updateBombExplosionItemAnimation);
0834 }
0835 
0836 void GameScene::removeBombItem(BombItem* bombItem)
0837 {
0838     m_game->removeBomb(dynamic_cast <Bomb*> (bombItem->getModel()));
0839     // Find the BombItem and remove it
0840     BombExplosionItem* bombExplosionItem;
0841     QHash<BombItem*, QList<BombExplosionItem*> >::iterator i = m_bombItems.begin();
0842     while (i != m_bombItems.end())
0843     {
0844         if(i.key() == bombItem)
0845         {
0846             while(!i.value().isEmpty())
0847             {
0848                 bombExplosionItem = i.value().takeFirst();
0849                 if(items().contains(bombExplosionItem))
0850                 {
0851                     removeItem(bombExplosionItem);
0852                 }
0853                 delete bombExplosionItem;
0854             }
0855             if(items().contains(i.key()))
0856             {
0857                 removeItem(i.key());
0858             }
0859             delete i.key();
0860             i = m_bombItems.erase(i);
0861             break;
0862         }
0863         else
0864         {
0865             ++i;
0866         }
0867     }
0868 }
0869 
0870 void GameScene::bombDetonated(Bomb* bomb)
0871 {
0872     BombItem* bombItem = nullptr;
0873     BombExplosionItem* bombExplosionItem = nullptr;
0874     QList<Element*> blockElements;
0875     QList<Element*> bombElements;
0876     int nBombPower = bomb->bombPower();
0877     int nNumberOfColums = m_game->getArena()->getNbColumns();
0878     int nNumberOfRows = m_game->getArena()->getNbRows();
0879     int nColumn;
0880     int nRow;
0881     qreal xPos = 0;
0882     qreal yPos = 0;
0883     bool abDirectionsDone[] = {false, false, false, false};
0884     int nDetonationCountdown = 75;
0885     int anDirectionDetonationCountdown[] = {nDetonationCountdown, nDetonationCountdown, nDetonationCountdown, nDetonationCountdown};
0886 
0887     // Find the BombItem from the Bomb
0888     QHash<BombItem*, QList<BombExplosionItem*> >::iterator i = m_bombItems.begin();
0889     while (i != m_bombItems.end())
0890     {
0891         if(i.key()->getModel() == bomb)
0892         {
0893             bombItem = i.key();
0894             break;
0895         }
0896         ++i;
0897     }
0898 
0899     if(!bombItem)
0900     {
0901         return;
0902     }
0903 
0904     //check if there is a bomb or block at the position where the bomb exploded (possible when thrown)
0905     nColumn = m_game->getArena()->getColFromX(bomb->getX());
0906     nRow = m_game->getArena()->getRowFromY(bomb->getY());
0907     if(nColumn >= 0 && nColumn < nNumberOfColums && nRow >= 0 && nRow < nNumberOfRows)
0908     {
0909         bombElements = m_game->getArena()->getCell(nRow, nColumn).getElements(Granatier::Element::BOMB);
0910         int tempBombDetonationCountdown = nDetonationCountdown;
0911         for(const auto& element: std::as_const(bombElements))
0912         {
0913             if(dynamic_cast <Bomb*> (element) != bomb && !(dynamic_cast <Bomb*> (element)->isDetonated()))
0914             {
0915                 dynamic_cast <Bomb*> (element)->initDetonation(bomb->explosionID(), tempBombDetonationCountdown);
0916                 tempBombDetonationCountdown += 10;
0917             }
0918         }
0919 
0920         blockElements = m_game->getArena()->getCell(nRow, nColumn).getElements(Granatier::Element::BLOCK);
0921         if(!blockElements.isEmpty())
0922         {
0923             for(const auto& element: std::as_const(blockElements))
0924             {
0925                 dynamic_cast <Block*> (element)->startDestruction();
0926                 if (m_blockItems[nRow][nColumn] != nullptr)
0927                 {
0928                     //display bonus if available
0929                     if (m_bonusItems[nRow][nColumn] != nullptr)
0930                     {
0931                         m_bonusItems[nRow][nColumn]->setUndestroyable(bomb->explosionID());
0932                         m_bonusItems[nRow][nColumn]->show();
0933                     }
0934                 }
0935             }
0936         }
0937         else if(m_bonusItems[nRow][nColumn] != nullptr)
0938         {
0939             m_bonusItems[nRow][nColumn]->initDestruction(bomb->explosionID());
0940         }
0941     }
0942 
0943     for(int i = 0; i < nBombPower; i++)
0944     {
0945         Granatier::Direction::Type direction = Granatier::Direction::NORTH;
0946         do
0947         {
0948             switch(direction)
0949             {
0950                 case Granatier::Direction::NORTH:
0951                     xPos = bomb->getX();
0952                     yPos = bomb->getY() - (i+1)*Granatier::CellSize;
0953                     break;
0954                 case Granatier::Direction::EAST:
0955                     xPos = bomb->getX() + (i+1)*Granatier::CellSize;
0956                     yPos = bomb->getY();
0957                     break;
0958                 case Granatier::Direction::SOUTH:
0959                     xPos = bomb->getX();
0960                     yPos = bomb->getY() + (i+1)*Granatier::CellSize;
0961                     break;
0962                 case Granatier::Direction::WEST:
0963                     xPos = bomb->getX() - (i+1)*Granatier::CellSize;
0964                     yPos = bomb->getY();
0965                     break;
0966             }
0967             nColumn = m_game->getArena()->getColFromX(xPos);
0968             nRow = m_game->getArena()->getRowFromY(yPos);
0969 
0970             if(!abDirectionsDone[direction] && nColumn >= 0 && nColumn < nNumberOfColums && nRow >= 0 && nRow < nNumberOfRows)
0971             {
0972                 bombElements = m_game->getArena()->getCell(nRow, nColumn).getElements(Granatier::Element::BOMB);
0973                 blockElements = m_game->getArena()->getCell(nRow, nColumn).getElements(Granatier::Element::BLOCK);
0974                 if(m_game->getArena()->getCell(nRow, nColumn).getType() != Granatier::Cell::WALL)
0975                 {
0976                     int tempBombDetonationCountdown = anDirectionDetonationCountdown[direction];
0977                     bool increaseDetonationTimeout = false;
0978                     for(const auto& element: std::as_const(bombElements))
0979                     {
0980                         if(dynamic_cast <Bomb*> (element) != bomb && !(dynamic_cast <Bomb*> (element)->isDetonated()))
0981                         {
0982                             dynamic_cast <Bomb*> (element)->initDetonation(bomb->explosionID(), tempBombDetonationCountdown);
0983                             tempBombDetonationCountdown += 10;
0984                             increaseDetonationTimeout = true;
0985                         }
0986                     }
0987                     if(increaseDetonationTimeout)
0988                     {
0989                         anDirectionDetonationCountdown[direction] += nDetonationCountdown;
0990                     }
0991 
0992                     if(!blockElements.isEmpty())
0993                     {
0994                         abDirectionsDone[direction] = true;
0995                         for(const auto& element: std::as_const(blockElements))
0996                         {
0997                             dynamic_cast <Block*> (element)->startDestruction();
0998                             if (m_blockItems[nRow][nColumn] != nullptr)
0999                             {
1000                                 //display bonus if available
1001                                 if (m_bonusItems[nRow][nColumn] != nullptr)
1002                                 {
1003                                     m_bonusItems[nRow][nColumn]->setUndestroyable(bomb->explosionID());
1004                                     m_bonusItems[nRow][nColumn]->show();
1005                                 }
1006                             }
1007                         }
1008                     }
1009                     else if(m_bonusItems[nRow][nColumn] != nullptr)
1010                     {
1011                         m_bonusItems[nRow][nColumn]->initDestruction(bomb->explosionID());
1012                     }
1013 
1014                     bombExplosionItem = new BombExplosionItem (bomb, direction, nBombPower - i, m_rendererBombItems, m_SvgScaleFactor);
1015                     bombExplosionItem->setPosition(xPos, yPos);
1016                     connect(this, &GameScene::resizeGraphics, bombExplosionItem, &BombExplosionItem::updateGraphics);
1017                     addItem(bombExplosionItem);
1018                     m_bombItems[bombItem].append(bombExplosionItem);
1019                 }
1020                 else
1021                 {
1022                     abDirectionsDone[direction] = true;
1023                 }
1024             }
1025             else
1026             {
1027                 abDirectionsDone[direction] = true;
1028             }
1029 
1030             switch(direction)
1031             {
1032                 case Granatier::Direction::NORTH:
1033                     direction = Granatier::Direction::EAST;
1034                     break;
1035                 case Granatier::Direction::EAST:
1036                     direction = Granatier::Direction::SOUTH;
1037                     break;
1038                 case Granatier::Direction::SOUTH:
1039                     direction = Granatier::Direction::WEST;
1040                     break;
1041                 case Granatier::Direction::WEST:
1042                     direction = Granatier::Direction::NORTH;
1043                     break;
1044             }
1045         }
1046         while(direction != Granatier::Direction::NORTH);
1047     }
1048 }
1049 
1050 void GameScene::updateBombExplosionItemAnimation(BombItem* bombItem, int nFrame)
1051 {
1052     // Find the BombItems and update the frame
1053     BombExplosionItem* bombExplosionItem;
1054     QHash<BombItem*, QList<BombExplosionItem*> >::iterator i = m_bombItems.begin();
1055     while (i != m_bombItems.end())
1056     {
1057         if(i.key() == bombItem)
1058         {
1059             for(int nIndex = 0; nIndex < i.value().count(); nIndex++)
1060             {
1061                 bombExplosionItem = i.value().at(nIndex);
1062                 if(bombExplosionItem)
1063                 {
1064                     bombExplosionItem->updateAnimationn(nFrame);
1065                 }
1066             }
1067             break;
1068         }
1069         else
1070         {
1071             ++i;
1072         }
1073     }
1074 }
1075 
1076 #include "moc_gamescene.cpp"