File indexing completed on 2024-02-18 04:04:52

0001 /*
0002     SPDX-FileCopyrightText: 2007 Paolo Capriotti <p.capriotti@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "battlefieldview.h"
0008 
0009 #include <QMouseEvent>
0010 #include <QSizePolicy>
0011 
0012 #include "kbsrenderer.h"
0013 #include "sprite.h"
0014 #include "animator.h"
0015 #include "animation.h"
0016 #include "welcomescreen.h"
0017 #include "button.h"
0018 #include "delegate.h"
0019 
0020 BattleFieldView::BattleFieldView(QWidget* parent, KBSRenderer* renderer, const QString& bgID, int gridSize)
0021 : QGraphicsView(parent)
0022 , m_renderer(renderer)
0023 , m_factory(renderer)
0024 , m_bgID(bgID)
0025 , m_gridSize(gridSize)
0026 , m_impact(nullptr)
0027 , m_last_hit(nullptr)
0028 , m_drawGrid(true)
0029 , m_delegate(nullptr)
0030 {
0031     m_background_lower = new KGameRenderedItem(m_renderer, bgID + QLatin1String("-layer1"));
0032     m_background_lower->setOpacity(0.98);
0033     
0034     m_background = new KGameRenderedItem(m_renderer, bgID + QLatin1String("-layer2"));
0035     m_background->setOpacity(0.98);
0036     
0037     m_screen = new WelcomeScreen(font());
0038 
0039     QGraphicsScene *scene = new QGraphicsScene(this);
0040     scene->addItem(m_background_lower);
0041     scene->addItem(m_background);
0042     scene->addItem(m_screen);
0043     
0044     for (Sprites::iterator i = m_sprites.begin();
0045             i != m_sprites.end();
0046             ++i) {
0047         i.value() = nullptr;
0048     }
0049 
0050     for (int i = 0; i < 11; i++) {
0051         hlines[i] = new QGraphicsLineItem;
0052         vlines[i] = new QGraphicsLineItem;
0053 
0054         scene->addItem(hlines[i]);
0055         scene->addItem(vlines[i]);
0056 
0057         hlines[i]->stackBefore(m_background);
0058         vlines[i]->stackBefore(m_background);
0059     }
0060 
0061     setScene(scene);
0062     setMouseTracking(true);
0063     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0064     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0065 }
0066 
0067 void BattleFieldView::toggleGrid(bool show)
0068 {
0069     if (m_drawGrid != show)
0070     {
0071         m_drawGrid = show;
0072         drawGrid();
0073     }
0074 }
0075 
0076 void BattleFieldView::drawGrid()
0077 {
0078     if (m_drawGrid)
0079     {
0080         int spacing = m_renderer->size().width();
0081         int width   = spacing * m_gridSize;
0082         int height  = spacing * m_gridSize;
0083 
0084         for (int i = 0; i < 11; i++) {
0085             hlines[i]->show();
0086             hlines[i]->setLine(0, i * spacing, width, i * spacing);
0087 
0088             vlines[i]->show();
0089             vlines[i]->setLine(i * spacing, 0, i * spacing, height);
0090         }
0091     }
0092     else
0093     {
0094         for (int i = 0; i < 11; i++) {
0095             hlines[i]->hide();
0096             vlines[i]->hide();
0097         }
0098     }
0099 }
0100 
0101 void BattleFieldView::refresh()
0102 {
0103     // Updates this widget.
0104     int x = pos().x();
0105     int y = pos().x();
0106     int w = m_renderer->size().width() * m_gridSize;
0107     int h = m_renderer->size().height() * m_gridSize;
0108 
0109     setSceneRect(0, 0, w, h);
0110     // Due the rounded nature of the view, it's necessary to set its
0111     // geometry to make sure it will show the entire grid.
0112     int fs = frameWidth();
0113     setGeometry(x, y, w + 2 * fs, h + 2 * fs); 
0114 
0115     // update welcome screen
0116     m_screen->setPos(0, 0);
0117     m_screen->resize(m_renderer->size() * m_gridSize);
0118 
0119     // Updates the backgrounds.
0120     m_background_lower->hide();
0121     m_background_lower->setRenderSize(m_renderer->size() * m_gridSize);
0122     m_background_lower->show();
0123 
0124     m_background->hide();
0125     m_background->setRenderSize(m_renderer->size() * m_gridSize);
0126     m_background->show();
0127 
0128     // Updates the grid.
0129     drawGrid();
0130  
0131     // update preview
0132     if (m_preview.sprite) {
0133         m_preview.sprite->refresh(m_renderer);
0134         m_preview.sprite->setPos(m_renderer->toReal(m_preview.pos));
0135     }
0136     
0137     // update sprites
0138     for (Sprites::const_iterator i = m_sprites.constBegin(); 
0139             i != m_sprites.constEnd();
0140             ++i) {
0141         i.value()->refresh(m_renderer);
0142         i.value()->setPos(m_renderer->toReal(i.key()));
0143     }
0144 }
0145 
0146 void BattleFieldView::setPreview(const QPoint & pos)
0147 {
0148     if (!m_delegate) {
0149         return;
0150     }
0151     Ship * ship = m_delegate->nextShip();
0152 
0153     if (!ship) {
0154         return;
0155     }
0156 
0157     loadPreviewSprite(ship);
0158     Coord coordinate = m_renderer->toLogical(pos);
0159 
0160     if (m_delegate->canAddShip(m_player, coordinate)) {
0161         m_preview.sprite->turnGreen();
0162     } else {
0163         m_preview.sprite->turnRed();
0164     }
0165 
0166     QPointF scenePos = mapToScene(pos);
0167     m_preview.pos = m_renderer->toLogical(scenePos);
0168     m_preview.sprite->setPos(m_renderer->toReal(m_preview.pos));
0169 }
0170 
0171 void BattleFieldView::loadPreviewSprite(Ship * ship)
0172 {
0173     if (m_preview.ship) {
0174         return;
0175     }
0176 
0177     m_preview.ship      = ship;
0178     m_preview.sprite    = m_factory.createShip(ship);
0179 
0180     m_preview.sprite->setOpacity(PREVIEW_OPACITY);
0181     scene()->addItem(m_preview.sprite);
0182 }
0183 void BattleFieldView::cancelPreview()
0184 {
0185     delete m_preview.sprite;
0186     m_preview.sprite = nullptr;
0187     m_preview.ship = nullptr;
0188 }
0189 
0190 void BattleFieldView::addSprite(const Coord& c, Sprite* sprite)
0191 {
0192     m_sprites.insert(c, sprite);
0193     sprite->setPos(m_renderer->toReal(c));
0194     scene()->addItem(sprite);
0195 }
0196 
0197 void BattleFieldView::add(Ship* ship)
0198 {
0199     Sprite* sprite = m_factory.createShip(ship);
0200     sprite->setZValue(BACKGROUND);
0201     addSprite(ship->position(), sprite);
0202     
0203     // fading preview in
0204     if (ship->alive()) {
0205         Animation* a = new FadeAnimation(sprite, PREVIEW_OPACITY, 1, 1000);
0206         Animator::instance()->add(a);
0207     }
0208 
0209     if (ship == m_preview.ship) {
0210         cancelPreview();
0211     }
0212     else if (!ship->alive()) {
0213         sprite->setZValue(BACKGROUND);
0214         Animation* a = new FadeAnimation(sprite, 0, 0.5, 1000);
0215         Animator::instance()->add(a);
0216     }
0217 }
0218 
0219 void BattleFieldView::sink(Ship* ship)
0220 {
0221     m_last_hit = nullptr;
0222     
0223     Coord p = ship->position();
0224     for (unsigned int i = 0; 
0225          i < ship->size(); 
0226          i++, p += ship->increment()) {
0227         const auto spritesOfPos = m_sprites.values(p);
0228         for (Sprite* s : spritesOfPos) {
0229             if (s->spriteKey().startsWith(QLatin1String("ship"))) {
0230                 s->setZValue(BACKGROUND);
0231                 s->setOpacity(0.5);
0232             }
0233             else if (s->spriteKey().startsWith(QLatin1String("hit"))) {
0234                 s->setSpriteKey(QStringLiteral("hit-end"));
0235             }
0236         }
0237     }
0238 }
0239 
0240 void BattleFieldView::hit(const Coord& c)
0241 {
0242     removeImpact();
0243     m_last_hit = m_factory.createHit();
0244     m_last_hit->setZValue(FOREGROUND);
0245     m_last_hit->setOpacity(1.0);
0246     addSprite(c, m_last_hit);
0247 }
0248 
0249 void BattleFieldView::miss(const Coord& c)
0250 {
0251     removeImpact();
0252     m_impact = m_factory.createImpact();
0253     m_impact->setZValue(FOREGROUND);
0254     m_impact->setOpacity(1.0);
0255     addSprite(c, m_impact);
0256 }
0257 
0258 void BattleFieldView::removeImpact() {
0259     if (m_impact) {
0260         m_impact->setSpriteKey(QStringLiteral("water"));
0261         m_impact->refresh(m_renderer);
0262         m_impact = nullptr;
0263     }
0264     if (m_last_hit) {
0265         m_last_hit->setSpriteKey(QStringLiteral("hit-after"));
0266         m_last_hit->refresh(m_renderer);
0267         m_last_hit = nullptr;
0268     }
0269 }
0270 
0271 void BattleFieldView::clear()
0272 {
0273     // fixes a crash when the ships can not be placed.
0274     Animator::instance()->stop();
0275     delete m_preview.sprite;
0276     m_preview.sprite = nullptr;
0277     m_preview.ship = nullptr;
0278     
0279     m_impact = nullptr;
0280     m_last_hit = nullptr;
0281 
0282     qDeleteAll(m_sprites);
0283     m_sprites.clear();
0284 }
0285 
0286 void BattleFieldView::mousePressEvent(QMouseEvent *ev)
0287 {
0288     Button *button = dynamic_cast<Button *>(itemAt(ev->pos()));
0289 
0290     if (m_screen->isVisible() && button)
0291     {
0292         m_screen->onMousePress(button);
0293     }
0294     else if (ev->button() == Qt::LeftButton && m_delegate)
0295     {
0296         Coord c = m_renderer->toLogical(ev->pos());
0297         m_delegate->action(m_player, c);
0298     }
0299     else if (ev->button() == Qt::RightButton && m_delegate)
0300     {
0301         m_delegate->changeDirection(m_player);
0302         setPreview(ev->pos());
0303     }
0304 }
0305 
0306 void BattleFieldView::mouseReleaseEvent(QMouseEvent *ev)
0307 {
0308     Button *button = dynamic_cast<Button *>(itemAt(ev->pos()));
0309 
0310     if (m_screen->isVisible() && button && ev->button() == Qt::LeftButton) {
0311         m_screen->onMouseRelease(button);
0312     }
0313 }
0314 
0315 void BattleFieldView::mouseMoveEvent(QMouseEvent *ev)
0316 {
0317     Button *button = dynamic_cast<Button *>(itemAt(ev->pos()));
0318 
0319     if (m_screen->isVisible() && button)
0320     {
0321         m_screen->onMouseMove(button);
0322     }
0323     else if (m_screen->isVisible() && !button)
0324     {
0325         m_screen->onMouseLeave();
0326     }
0327     else
0328     {
0329         cancelPreview();
0330         setPreview(ev->pos());
0331     }
0332 }
0333 
0334 void BattleFieldView::leaveEvent(QEvent *)
0335 {
0336     if (m_screen->isVisible()) {
0337         m_screen->onMouseLeave();
0338     } else {
0339         cancelPreview();
0340     }
0341 }
0342 
0343 WelcomeScreen* BattleFieldView::screen() const
0344 {
0345     return m_screen;
0346 }
0347 
0348 void BattleFieldView::setDelegate(Delegate *c)
0349 {
0350     m_delegate = c;
0351 }
0352 
0353 void BattleFieldView::setPlayer(Sea::Player player)
0354 {
0355     m_player = player;
0356 }
0357 
0358 const qreal BattleFieldView::PREVIEW_OPACITY = 0.7;