1 /*
2     SPDX-FileCopyrightText: 2007 Paolo Capriotti <p.capriotti@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "battlefieldview.h"
8 
9 #include <QMouseEvent>
10 #include <QSizePolicy>
11 
12 #include "kbsrenderer.h"
13 #include "sprite.h"
14 #include "animator.h"
15 #include "animation.h"
16 #include "welcomescreen.h"
17 #include "button.h"
18 #include "delegate.h"
19 
BattleFieldView(QWidget * parent,KBSRenderer * renderer,const QString & bgID,int gridSize)20 BattleFieldView::BattleFieldView(QWidget* parent, KBSRenderer* renderer, const QString& bgID, int gridSize)
21 : QGraphicsView(parent)
22 , m_renderer(renderer)
23 , m_factory(renderer)
24 , m_bgID(bgID)
25 , m_gridSize(gridSize)
26 , m_impact(nullptr)
27 , m_last_hit(nullptr)
28 , m_drawGrid(true)
29 , m_delegate(nullptr)
30 {
31     m_background_lower = new KGameRenderedItem(m_renderer, bgID + QLatin1String("-layer1"));
32     m_background_lower->setOpacity(0.98);
33 
34     m_background = new KGameRenderedItem(m_renderer, bgID + QLatin1String("-layer2"));
35     m_background->setOpacity(0.98);
36 
37     m_screen = new WelcomeScreen(font());
38 
39     QGraphicsScene *scene = new QGraphicsScene(this);
40     scene->addItem(m_background_lower);
41     scene->addItem(m_background);
42     scene->addItem(m_screen);
43 
44     for (Sprites::iterator i = m_sprites.begin();
45             i != m_sprites.end();
46             ++i) {
47         i.value() = nullptr;
48     }
49 
50     for (int i = 0; i < 11; i++) {
51         hlines[i] = new QGraphicsLineItem;
52         vlines[i] = new QGraphicsLineItem;
53 
54         scene->addItem(hlines[i]);
55         scene->addItem(vlines[i]);
56 
57         hlines[i]->stackBefore(m_background);
58         vlines[i]->stackBefore(m_background);
59     }
60 
61     setScene(scene);
62     setMouseTracking(true);
63     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
64     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
65 }
66 
toggleGrid(bool show)67 void BattleFieldView::toggleGrid(bool show)
68 {
69     if (m_drawGrid != show)
70     {
71         m_drawGrid = show;
72         drawGrid();
73     }
74 }
75 
drawGrid()76 void BattleFieldView::drawGrid()
77 {
78     if (m_drawGrid)
79     {
80         int spacing = m_renderer->size().width();
81         int width   = spacing * m_gridSize;
82         int height  = spacing * m_gridSize;
83 
84         for (int i = 0; i < 11; i++) {
85             hlines[i]->show();
86             hlines[i]->setLine(0, i * spacing, width, i * spacing);
87 
88             vlines[i]->show();
89             vlines[i]->setLine(i * spacing, 0, i * spacing, height);
90         }
91     }
92     else
93     {
94         for (int i = 0; i < 11; i++) {
95             hlines[i]->hide();
96             vlines[i]->hide();
97         }
98     }
99 }
100 
refresh()101 void BattleFieldView::refresh()
102 {
103     // Updates this widget.
104     int x = pos().x();
105     int y = pos().x();
106     int w = m_renderer->size().width() * m_gridSize;
107     int h = m_renderer->size().height() * m_gridSize;
108 
109     setSceneRect(0, 0, w, h);
110     // Due the rounded nature of the view, it's necessary to set its
111     // geometry to make sure it will show the entire grid.
112     int fs = frameWidth();
113     setGeometry(x, y, w + 2 * fs, h + 2 * fs);
114 
115     // update welcome screen
116     m_screen->setPos(0, 0);
117     m_screen->resize(m_renderer->size() * m_gridSize);
118 
119     // Updates the backgrounds.
120     m_background_lower->hide();
121     m_background_lower->setRenderSize(m_renderer->size() * m_gridSize);
122     m_background_lower->show();
123 
124     m_background->hide();
125     m_background->setRenderSize(m_renderer->size() * m_gridSize);
126     m_background->show();
127 
128     // Updates the grid.
129     drawGrid();
130 
131     // update preview
132     if (m_preview.sprite) {
133         m_preview.sprite->refresh(m_renderer);
134         m_preview.sprite->setPos(m_renderer->toReal(m_preview.pos));
135     }
136 
137     // update sprites
138     for (Sprites::const_iterator i = m_sprites.constBegin();
139             i != m_sprites.constEnd();
140             ++i) {
141         i.value()->refresh(m_renderer);
142         i.value()->setPos(m_renderer->toReal(i.key()));
143     }
144 }
145 
setPreview(const QPoint & pos)146 void BattleFieldView::setPreview(const QPoint & pos)
147 {
148     if (!m_delegate) {
149         return;
150     }
151     Ship * ship = m_delegate->nextShip();
152 
153     if (!ship) {
154         return;
155     }
156 
157     loadPreviewSprite(ship);
158     Coord coordinate = m_renderer->toLogical(pos);
159 
160     if (m_delegate->canAddShip(m_player, coordinate)) {
161         m_preview.sprite->turnGreen();
162     } else {
163         m_preview.sprite->turnRed();
164     }
165 
166     QPointF scenePos = mapToScene(pos);
167     m_preview.pos = m_renderer->toLogical(scenePos);
168     m_preview.sprite->setPos(m_renderer->toReal(m_preview.pos));
169 }
170 
loadPreviewSprite(Ship * ship)171 void BattleFieldView::loadPreviewSprite(Ship * ship)
172 {
173     if (m_preview.ship) {
174         return;
175     }
176 
177     m_preview.ship      = ship;
178     m_preview.sprite    = m_factory.createShip(ship);
179 
180     m_preview.sprite->setOpacity(PREVIEW_OPACITY);
181     scene()->addItem(m_preview.sprite);
182 }
cancelPreview()183 void BattleFieldView::cancelPreview()
184 {
185     delete m_preview.sprite;
186     m_preview.sprite = nullptr;
187     m_preview.ship = nullptr;
188 }
189 
addSprite(const Coord & c,Sprite * sprite)190 void BattleFieldView::addSprite(const Coord& c, Sprite* sprite)
191 {
192     m_sprites.insert(c, sprite);
193     sprite->setPos(m_renderer->toReal(c));
194     scene()->addItem(sprite);
195 }
196 
add(Ship * ship)197 void BattleFieldView::add(Ship* ship)
198 {
199     Sprite* sprite = m_factory.createShip(ship);
200     sprite->setZValue(BACKGROUND);
201     addSprite(ship->position(), sprite);
202 
203     // fading preview in
204     if (ship->alive()) {
205         Animation* a = new FadeAnimation(sprite, PREVIEW_OPACITY, 1, 1000);
206         Animator::instance()->add(a);
207     }
208 
209     if (ship == m_preview.ship) {
210         cancelPreview();
211     }
212     else if (!ship->alive()) {
213         sprite->setZValue(BACKGROUND);
214         Animation* a = new FadeAnimation(sprite, 0, 0.5, 1000);
215         Animator::instance()->add(a);
216     }
217 }
218 
sink(Ship * ship)219 void BattleFieldView::sink(Ship* ship)
220 {
221     m_last_hit = nullptr;
222 
223     Coord p = ship->position();
224     for (unsigned int i = 0;
225          i < ship->size();
226          i++, p += ship->increment()) {
227         const auto spritesOfPos = m_sprites.values(p);
228         for (Sprite* s : spritesOfPos) {
229             if (s->spriteKey().startsWith(QLatin1String("ship"))) {
230                 s->setZValue(BACKGROUND);
231                 s->setOpacity(0.5);
232             }
233             else if (s->spriteKey().startsWith(QLatin1String("hit"))) {
234                 s->setSpriteKey(QStringLiteral("hit-end"));
235             }
236         }
237     }
238 }
239 
hit(const Coord & c)240 void BattleFieldView::hit(const Coord& c)
241 {
242     removeImpact();
243     m_last_hit = m_factory.createHit();
244     m_last_hit->setZValue(FOREGROUND);
245     m_last_hit->setOpacity(1.0);
246     addSprite(c, m_last_hit);
247 }
248 
miss(const Coord & c)249 void BattleFieldView::miss(const Coord& c)
250 {
251     removeImpact();
252     m_impact = m_factory.createImpact();
253     m_impact->setZValue(FOREGROUND);
254     m_impact->setOpacity(1.0);
255     addSprite(c, m_impact);
256 }
257 
removeImpact()258 void BattleFieldView::removeImpact() {
259     if (m_impact) {
260         m_impact->setSpriteKey(QStringLiteral("water"));
261         m_impact->refresh(m_renderer);
262         m_impact = nullptr;
263     }
264     if (m_last_hit) {
265         m_last_hit->setSpriteKey(QStringLiteral("hit-after"));
266         m_last_hit->refresh(m_renderer);
267         m_last_hit = nullptr;
268     }
269 }
270 
clear()271 void BattleFieldView::clear()
272 {
273     // fixes a crash when the ships can not be placed.
274     Animator::instance()->stop();
275     delete m_preview.sprite;
276     m_preview.sprite = nullptr;
277     m_preview.ship = nullptr;
278 
279     m_impact = nullptr;
280     m_last_hit = nullptr;
281 
282     qDeleteAll(m_sprites);
283     m_sprites.clear();
284 }
285 
mousePressEvent(QMouseEvent * ev)286 void BattleFieldView::mousePressEvent(QMouseEvent *ev)
287 {
288     Button *button = dynamic_cast<Button *>(itemAt(ev->pos()));
289 
290     if (m_screen->isVisible() && button)
291     {
292         m_screen->onMousePress(button);
293     }
294     else if (ev->button() == Qt::LeftButton && m_delegate)
295     {
296         Coord c = m_renderer->toLogical(ev->pos());
297         m_delegate->action(m_player, c);
298     }
299     else if (ev->button() == Qt::RightButton && m_delegate)
300     {
301         m_delegate->changeDirection(m_player);
302         setPreview(ev->pos());
303     }
304 }
305 
mouseReleaseEvent(QMouseEvent * ev)306 void BattleFieldView::mouseReleaseEvent(QMouseEvent *ev)
307 {
308     Button *button = dynamic_cast<Button *>(itemAt(ev->pos()));
309 
310     if (m_screen->isVisible() && button && ev->button() == Qt::LeftButton) {
311         m_screen->onMouseRelease(button);
312     }
313 }
314 
mouseMoveEvent(QMouseEvent * ev)315 void BattleFieldView::mouseMoveEvent(QMouseEvent *ev)
316 {
317     Button *button = dynamic_cast<Button *>(itemAt(ev->pos()));
318 
319     if (m_screen->isVisible() && button)
320     {
321         m_screen->onMouseMove(button);
322     }
323     else if (m_screen->isVisible() && !button)
324     {
325         m_screen->onMouseLeave();
326     }
327     else
328     {
329         cancelPreview();
330         setPreview(ev->pos());
331     }
332 }
333 
leaveEvent(QEvent *)334 void BattleFieldView::leaveEvent(QEvent *)
335 {
336     if (m_screen->isVisible()) {
337         m_screen->onMouseLeave();
338     } else {
339         cancelPreview();
340     }
341 }
342 
screen() const343 WelcomeScreen* BattleFieldView::screen() const
344 {
345     return m_screen;
346 }
347 
setDelegate(Delegate * c)348 void BattleFieldView::setDelegate(Delegate *c)
349 {
350     m_delegate = c;
351 }
352 
setPlayer(Sea::Player player)353 void BattleFieldView::setPlayer(Sea::Player player)
354 {
355     m_player = player;
356 }
357 
358 const qreal BattleFieldView::PREVIEW_OPACITY = 0.7;
359