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