1 /*
2 SPDX-FileCopyrightText: 1998-2001 Andreas Zehender <az@azweb.de>
3 SPDX-FileCopyrightText: 2006-2007 Dirk Rathlev <dirkrathlev@gmx.de>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8 #include "mainview.h"
9 #include "ai.h"
10 #include "options.h"
11
12 #include <KActionCollection>
13 #include <KConfigGroup>
14 #include <KLocalizedString>
15 #include <KSharedConfig>
16 #include <KToggleAction>
17
18 #include <QAbstractEventDispatcher>
19 #include <QBrush>
20 #include <QFontDatabase>
21 #include <QGraphicsSimpleTextItem>
22 #include <QKeyEvent>
23 #include <QKeySequence>
24 #include <QResizeEvent>
25 #include <QSvgRenderer>
26 #include <QStandardPaths>
27
28 #include <math.h>
29
30 KToggleAction *MyMainView::pauseAction = nullptr;
31
32 static struct
33 {
34 int id;
35 const char *path;
36 }
37 kspd_animations [] =
38 {
39 { ID_EXPLOSION, "explos%1"},
40 { ID_MINE1, "mine_red%1"},
41 { ID_MINE2, "mine_blue%1"},
42 { ID_MINEEXPLO, "mineex%1"},
43 { 0, nullptr}
44 };
45
MyMainView(QWidget * parent)46 MyMainView::MyMainView(QWidget *parent)
47 :QWidget(parent),
48 field(this),//0,0,DEF_WIDTH,DEF_HEIGHT),
49 view(&field,this)
50 {
51 int i,p;
52 setMinimumSize(600,400);
53 random.seed(QRandomGenerator::global()->generate());
54 QPixmap backgr(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral(MV_BACKGROUND)));
55
56 field.setBackgroundBrush(QBrush(backgr));
57 view.setCacheMode(QGraphicsView::CacheBackground);
58
59 setFocusPolicy(Qt::StrongFocus);
60 // FIXME: is this needed anymore?
61 //view.setResizePolicy(Q3ScrollView::AutoOne);
62 view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
63 view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
64 view.setFrameStyle(QFrame::NoFrame);
65
66 for(p=0;p<2;++p)
67 {
68 for(i=0;i<PlayerKeyNum;++i)
69 playerKeyPressed[p][i]=false;
70 bulletShot[p]=false;
71 minePut[p]=false;
72 }
73
74 svgrender = new QSvgRenderer(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral(MV_SVG_FILE)));
75
76 sun=new SunSprite(svgrender,QStringLiteral( MV_SUN ));
77 field.addItem(sun);
78 sun->setPos(QPointF(width()/2-1-(sun->width()/2),
79 height()/2-1-(sun->height()/2)));
80
81 powerupelements[0] = QStringLiteral( MV_POWERMINE );
82 powerupelements[1] = QStringLiteral( MV_POWERBULLET );
83 powerupelements[2] = QStringLiteral( MV_POWERSHIELD );
84 powerupelements[3] = QStringLiteral( MV_POWERENERGY );
85
86 ship[0]=new ShipSprite(svgrender, QStringLiteral( MV_SHIP1 ), 0);
87 ship[1]=new ShipSprite(svgrender, QStringLiteral( MV_SHIP2 ), 1);
88 field.addItem(ship[0]);
89 field.addItem(ship[1]);
90
91 readSprites();
92
93 for(i=0;i<2;++i)
94 {
95 // ship[i]->setBoundsAction(QwRealMobileSprite::Wrap);
96 ship[i]->hide();
97 bullets[i]=new QList<BulletSprite*>;
98 mines[i]=new QList<MineSprite*>;
99
100 }
101
102 waitForStart=false;
103 textSprite = nullptr;
104 readConfig();
105 }
106
~MyMainView()107 MyMainView::~MyMainView()
108 {
109 int i;
110 QAbstractEventDispatcher::instance()->unregisterTimers(this);
111
112 for(i=0;i<2;i++)
113 {
114 delete ai[i];
115 qDeleteAll(*mines[i]);
116 delete mines[i];
117 qDeleteAll(*bullets[i]);
118 delete bullets[i];
119 }
120
121 qDeleteAll(powerups);
122 powerups.clear();
123
124 qDeleteAll(explosions);
125 explosions.clear();
126
127 writeConfig();
128 delete svgrender;
129 }
130
setActionCollection(KActionCollection * a)131 void MyMainView::setActionCollection(KActionCollection *a)
132 {
133 actionCollection = a;
134 }
135
136 /* Assumes that there are no gaps between animation frames. ie 1,2,3 will only have frames 1&2
137 recognized. It also assumes that there is at least one frame. */
138 // FIXME: Add Check for existence of first frame
139 // TODO: Add support for missing frames (ie 1,2,5)
readSprites()140 bool MyMainView::readSprites()
141 {
142 int i = 0;
143 while ( kspd_animations[i].id )
144 {
145 QList<QString> anim;
146 short frame = 0;
147 QString element =QLatin1String( kspd_animations[i].path );
148 QString elem = element.arg(frame, 2, 10, QLatin1Char('0'));
149 do
150 {
151 anim.append(elem);
152 frame++;
153 elem = element.arg(frame, 2, 10, QLatin1Char('0'));
154 } while ( svgrender->elementExists(elem) );
155 animation.insert( kspd_animations[i].id, anim );
156 i++;
157 }
158
159 // FIXME: Perform test!
160 return true;
161 }
162
readConfig()163 void MyMainView::readConfig()
164 {
165 KConfigGroup game(KSharedConfig::openConfig(), "Game");
166 int i;
167
168 customConfig.gamespeed=game.readEntry("gamespeed",
169 predefinedConfig[0].gamespeed);
170
171 customConfig.gravity=
172 game.readEntry("gravity",predefinedConfig[0].gravity);
173 customConfig.acc=
174 game.readEntry("acceleration",predefinedConfig[0].acc);
175 customConfig.bulletDamage=
176 game.readEntry("bulletDamage",predefinedConfig[0].bulletDamage);
177 customConfig.bulletLifeTime=
178 game.readEntry("bulletLifeTime",predefinedConfig[0].bulletLifeTime);
179 customConfig.bulletReloadTime=
180 game.readEntry("bulletReloadTime",predefinedConfig[0].bulletReloadTime);
181 customConfig.mineDamage=
182 game.readEntry("mineDamage",predefinedConfig[0].mineDamage);
183 customConfig.shipDamage=
184 game.readEntry("shipDamage",predefinedConfig[0].shipDamage);
185 customConfig.maxBullets=
186 game.readEntry("maxBullets",predefinedConfig[0].maxBullets);
187 customConfig.maxMines=
188 game.readEntry("maxMines",predefinedConfig[0].maxMines);
189 customConfig.mineReloadTime=
190 game.readEntry("mineReloadTime",predefinedConfig[0].mineReloadTime);
191 customConfig.rotationSpeed=
192 game.readEntry("rotationSpeed",predefinedConfig[0].rotationSpeed);
193 customConfig.shotSpeed=
194 game.readEntry("shotSpeed",predefinedConfig[0].shotSpeed);
195 customConfig.energyNeed=
196 game.readEntry("accEnergyNeed",predefinedConfig[0].energyNeed);
197 customConfig.rotationEnergyNeed=
198 game.readEntry("rotationEnergyNeed",predefinedConfig[0].rotationEnergyNeed);
199 customConfig.sunEnergy=
200 game.readEntry("sunEnergy",predefinedConfig[0].sunEnergy);
201 customConfig.mineActivateTime=
202 game.readEntry("mineActivateTime",predefinedConfig[0].mineActivateTime);
203 customConfig.mineFuel=
204 game.readEntry("mineFuel",predefinedConfig[0].mineFuel);
205 customConfig.shotEnergyNeed=
206 game.readEntry("shotEnergyNeed",predefinedConfig[0].shotEnergyNeed);
207 customConfig.mineEnergyNeed=
208 game.readEntry("mineEnergyNeed",predefinedConfig[0].mineEnergyNeed);
209 customConfig.startPosX=
210 game.readEntry("startPosX",predefinedConfig[0].startPosX);
211 customConfig.startPosY=
212 game.readEntry("startPosY",predefinedConfig[0].startPosY);
213 customConfig.startVelX=
214 game.readEntry("startVelX",predefinedConfig[0].startVelX);
215 customConfig.startVelY=
216 game.readEntry("startVelY",predefinedConfig[0].startVelY);
217 customConfig.powerupLifeTime=
218 game.readEntry("powerupLifeTime",predefinedConfig[0].powerupLifeTime);
219 customConfig.powerupRefreshTime=
220 game.readEntry("powerupRefreshTime",predefinedConfig[0].powerupRefreshTime);
221 customConfig.powerupShieldAmount=
222 game.readEntry("powerupShieldAmount",
223 predefinedConfig[0].powerupShieldAmount);
224 customConfig.powerupEnergyAmount=
225 game.readEntry("powerupEnergyAmount",
226 predefinedConfig[0].powerupEnergyAmount);
227
228 if(Options::lastConfig() < predefinedConfigNum)
229 config=modifyConfig(predefinedConfig[Options::lastConfig()]);
230 else
231 config=modifyConfig(customConfig);
232
233 for(i=0;i<2;++i)
234 ai[i]=new Ai(i,ship,bullets,mines,&config);
235 }
236
writeConfig()237 void MyMainView::writeConfig()
238 {
239 KConfigGroup game(KSharedConfig::openConfig(), "Game");
240
241 game.writeEntry("gravity",customConfig.gravity);
242 game.writeEntry("acceleration",customConfig.acc);
243 game.writeEntry("bulletDamage",customConfig.bulletDamage);
244 game.writeEntry("bulletLifeTime",customConfig.bulletLifeTime);
245 game.writeEntry("bulletReloadTime",customConfig.bulletReloadTime);
246 game.writeEntry("mineDamage",customConfig.mineDamage);
247 game.writeEntry("shipDamage",customConfig.shipDamage);
248 game.writeEntry("maxBullets",customConfig.maxBullets);
249 game.writeEntry("maxMines",customConfig.maxMines);
250 game.writeEntry("rotationSpeed",customConfig.rotationSpeed);
251 game.writeEntry("shotSpeed",customConfig.shotSpeed);
252 game.writeEntry("accEnergyNeed",customConfig.energyNeed);
253 game.writeEntry("rotationEnergyNeed",customConfig.rotationEnergyNeed);
254 game.writeEntry("sunEnergy",customConfig.sunEnergy);
255 game.writeEntry("mineActivateTime",customConfig.mineActivateTime);
256 game.writeEntry("mineReloadTime",customConfig.mineReloadTime);
257 game.writeEntry("mineFuel",customConfig.mineFuel);
258 game.writeEntry("shotEnergyNeed",customConfig.shotEnergyNeed);
259 game.writeEntry("mineEnergyNeed",customConfig.mineEnergyNeed);
260
261 game.writeEntry("startPosX",customConfig.startPosX);
262 game.writeEntry("startPosY",customConfig.startPosY);
263 game.writeEntry("startVelX",customConfig.startVelX);
264 game.writeEntry("startVelY",customConfig.startVelY);
265
266 game.writeEntry("powerupLifeTime",customConfig.powerupLifeTime);
267 game.writeEntry("powerupRefreshTime",customConfig.powerupRefreshTime);
268 game.writeEntry("powerupShieldAmount",customConfig.powerupShieldAmount);
269 game.writeEntry("powerupEnergyAmount",customConfig.powerupEnergyAmount);
270 }
271
modifyConfig(const SConfig & conf)272 SConfig MyMainView::modifyConfig(const SConfig &conf)
273 {
274 SConfig newConfig=conf;
275 newConfig.gamespeed*=Options::refreshTime()/33.0;
276 newConfig.acc*=newConfig.gamespeed;
277 newConfig.rotationSpeed*=newConfig.gamespeed*M_PI/ROTNUM*4;
278 newConfig.energyNeed*=newConfig.gamespeed;
279 newConfig.rotationEnergyNeed*=newConfig.gamespeed;
280 newConfig.mineActivateTime*=newConfig.gamespeed;
281
282 return newConfig;
283 }
284
keyPressEvent(QKeyEvent * ev)285 void MyMainView::keyPressEvent(QKeyEvent *ev)
286 {
287 // if-statement kept for historical reasons, maybe not needed anymore
288 if ( ((gameEnd>0.0) || (gameEnd<=-2.0)) && (!waitForStart) )
289 {
290 if(actionCollection->action(QStringLiteral( "P1KeyLeft" ))->shortcuts().contains(ev->key()))
291 playerKeyPressed[0][PlayerKeyLeft]=true;
292 else if(actionCollection->action(QStringLiteral( "P2KeyLeft" ))->shortcuts().contains(ev->key()))
293 playerKeyPressed[1][PlayerKeyLeft]=true;
294
295 else if(actionCollection->action(QStringLiteral( "P1KeyRight" ))->shortcuts().contains(ev->key()))
296 playerKeyPressed[0][PlayerKeyRight]=true;
297 else if(actionCollection->action(QStringLiteral( "P2KeyRight" ))->shortcuts().contains(ev->key()))
298 playerKeyPressed[1][PlayerKeyRight]=true;
299
300 else if(actionCollection->action(QStringLiteral( "P1KeyAcc" ))->shortcuts().contains(ev->key()))
301 playerKeyPressed[0][PlayerKeyAcc]=true;
302 else if(actionCollection->action(QStringLiteral( "P2KeyAcc" ))->shortcuts().contains(ev->key()))
303 playerKeyPressed[1][PlayerKeyAcc]=true;
304
305 else if(actionCollection->action(QStringLiteral( "P1Shot" ))->shortcuts().contains(ev->key()))
306 playerKeyPressed[0][PlayerKeyShot]=true;
307 else if(actionCollection->action(QStringLiteral( "P2Shot" ))->shortcuts().contains(ev->key()))
308 playerKeyPressed[1][PlayerKeyShot]=true;
309
310 else if(actionCollection->action(QStringLiteral( "P1Mine" ))->shortcuts().contains(ev->key()))
311 playerKeyPressed[0][PlayerKeyMine]=true;
312 else if(actionCollection->action(QStringLiteral( "P2Mine" ))->shortcuts().contains(ev->key()))
313 playerKeyPressed[1][PlayerKeyMine]=true;
314 else
315 ev->ignore();
316 }
317 }
318
keyReleaseEvent(QKeyEvent * ev)319 void MyMainView::keyReleaseEvent(QKeyEvent *ev)
320 {
321 if(actionCollection->action(QStringLiteral( "P1KeyLeft" ))->shortcuts().contains(ev->key()))
322 playerKeyPressed[0][PlayerKeyLeft]=false;
323 else if(actionCollection->action(QStringLiteral( "P2KeyLeft" ))->shortcuts().contains(ev->key()))
324 playerKeyPressed[1][PlayerKeyLeft]=false;
325
326 else if(actionCollection->action(QStringLiteral( "P1KeyRight" ))->shortcuts().contains(ev->key()))
327 playerKeyPressed[0][PlayerKeyRight]=false;
328 else if(actionCollection->action(QStringLiteral( "P2KeyRight" ))->shortcuts().contains(ev->key()))
329 playerKeyPressed[1][PlayerKeyRight]=false;
330
331 else if(actionCollection->action(QStringLiteral( "P1KeyAcc" ))->shortcuts().contains(ev->key()))
332 playerKeyPressed[0][PlayerKeyAcc]=false;
333 else if(actionCollection->action(QStringLiteral( "P2KeyAcc" ))->shortcuts().contains(ev->key()))
334 playerKeyPressed[1][PlayerKeyAcc]=false;
335
336 else if(actionCollection->action(QStringLiteral( "P1Shot" ))->shortcuts().contains(ev->key()))
337 playerKeyPressed[0][PlayerKeyShot]=false;
338 else if(actionCollection->action(QStringLiteral( "P2Shot" ))->shortcuts().contains(ev->key()))
339 playerKeyPressed[1][PlayerKeyShot]=false;
340
341 else if(actionCollection->action(QStringLiteral( "P1Mine" ))->shortcuts().contains(ev->key()))
342 playerKeyPressed[0][PlayerKeyMine]=false;
343 else if(actionCollection->action(QStringLiteral( "P2Mine" ))->shortcuts().contains(ev->key()))
344 playerKeyPressed[1][PlayerKeyMine]=false;
345 else
346 ev->ignore();
347 }
348
pause()349 void MyMainView::pause()
350 {
351 if( !waitForStart )
352 {
353 pauseAction->setChecked( true );
354
355 waitForStart=true;
356 QAbstractEventDispatcher::instance()->unregisterTimers(this);
357 Q_EMIT setStatusText(i18n(" paused "), IDS_PAUSE);
358 }
359 }
360
resume()361 void MyMainView::resume()
362 {
363 waitForStart=false;
364 timerID=startTimer(Options::refreshTime());
365 Q_EMIT setStatusText(QLatin1String( "" ),IDS_PAUSE);
366 Q_EMIT setStatusText(QLatin1String( "" ),IDS_MAIN);
367 }
368
start()369 void MyMainView::start( )
370 {
371 if( ( gameEnd <= 0.0 ) && ( gameEnd > -2.0 ) )
372 {
373 newRound( );
374 }
375 else if( waitForStart )
376 {
377 waitForStart = false;
378 timerID=startTimer(Options::refreshTime());
379 Q_EMIT setStatusText(QLatin1String( "" ),IDS_PAUSE);
380 Q_EMIT setStatusText(QLatin1String( "" ),IDS_MAIN);
381 pauseAction->setEnabled( true );
382 pauseAction->setChecked( false );
383 }
384 }
385
stop()386 void MyMainView::stop()
387 {
388 pauseAction->setEnabled( false );
389 pauseAction->setChecked( false );
390
391 QAbstractEventDispatcher::instance()->unregisterTimers(this);
392 waitForStart = true;
393 }
394
togglePause()395 void MyMainView::togglePause( )
396 {
397 if( waitForStart )
398 resume( );
399 else
400 pause( );
401 }
402
resizeEvent(QResizeEvent * event)403 void MyMainView::resizeEvent(QResizeEvent *event)
404 {
405 double mx,my;
406 int i,current;
407 int listsize; // used for caching QtList::size()
408
409 mx=(event->size().width()-event->oldSize().width())/2.0;
410 my=(event->size().height()-event->oldSize().height())/2.0;
411 QWidget::resizeEvent(event);
412 view.resize(width(),height());
413 field.setSceneRect(0, 0, width(),height());
414
415 // printf("%d %d\n",field.width(),field.height());
416
417 sun->setPos(QPointF(width()/2-1-(sun->width()/2),
418 height()/2-1-(sun->height()/2)));
419
420 for(i=0;i<2;++i)
421 {
422 // ship[i]->adoptSpritefieldBounds();
423 ship[i]->moveBy(mx,my);
424
425 listsize = mines[i]->size();
426 for (current=0; current<listsize; ++current)
427 {
428 // mine->adoptSpritefieldBounds();
429 mines[i]->value(current)->moveBy(mx,my);
430 }
431
432 listsize = bullets[i]->size();
433 for (current=0; current<listsize; ++current)
434 {
435 // bullet->adoptSpritefieldBounds();
436 bullets[i]->value(current)->moveBy(mx,my);
437 }
438 }
439 if(textSprite)
440 textSprite->moveBy((int)mx,(int)my);
441
442 listsize = powerups.size();
443 for (current=0; current<listsize; ++current)
444 {
445 powerups[current]->moveBy(mx,my);
446 }
447 }
448
newRound()449 void MyMainView::newRound()
450 {
451 double mx,my;
452 int i;
453
454 timeToNextPowerup=random.bounded(config.powerupRefreshTime);
455 qDeleteAll(powerups);
456 powerups.clear();
457
458 QAbstractEventDispatcher::instance()->unregisterTimers(this);
459 mx=width()/2.0;
460 my=height()/2.0;
461 ship[0]->setPos(QPointF(mx+config.startPosX-(ship[0]->width()/2),
462 my+config.startPosY-(ship[0]->height()/2)));
463 ship[0]->setRotation(0.0);
464
465 ship[1]->setPos(QPointF(mx-config.startPosX-(ship[1]->width()/2),
466 my-config.startPosY-(ship[1]->height()/2)));
467 ship[1]->setRotation(M_PI);
468
469 ship[0]->setVelocity(config.startVelX,config.startVelY);
470 ship[1]->setVelocity(-config.startVelX,-config.startVelY);
471 for(i=0;i<2;++i)
472 {
473 ship[i]->show();
474 ship[i]->setEnergy(MAX_ENERGY);
475 ship[i]->setHitPoints(Options::startHitPoints(i));
476 ship[i]->stop(false);
477 ship[i]->setExplosion(-1);
478 Q_EMIT energy(i,(int)ship[i]->getEnergy());
479 Q_EMIT hitPoints(i,ship[i]->getHitPoints());
480 bulletShot[i]=false;
481 qDeleteAll(*bullets[i]);
482 bullets[i]->clear();
483
484 qDeleteAll(*mines[i]);
485 mines[i]->clear();
486
487 ship[i]->mine(0.0);
488 ship[i]->bullet(0.0);
489 ship[i]->setBulletPowerups(0);
490 ship[i]->setMinePowerups(0);
491
492 ai[i]->newRound();
493 }
494 qDeleteAll(explosions);
495 explosions.clear();
496 gameEnd=-10.0;
497 for(i=0;i<PlayerKeyNum;++i)
498 {
499 playerKeyPressed[0][i]=false;
500 playerKeyPressed[1][i]=false;
501 }
502 if(textSprite)
503 {
504 textSprite->hide();
505 delete textSprite;
506 textSprite = nullptr;
507 }
508 //field.update();
509
510 QString str = i18n("Press %1 to start",
511 actionCollection->action(QStringLiteral("game_start"))->shortcut().toString(QKeySequence::NativeText));
512 Q_EMIT setStatusText(str,IDS_MAIN);
513 Q_EMIT setStatusText( QLatin1String( "" ), IDS_PAUSE );
514 stop( );
515 }
516
newGame()517 void MyMainView::newGame()
518 {
519 int i;
520 for(i=0;i<2;i++)
521 {
522 ship[i]->setWins(0);
523 Q_EMIT wins(i,0);
524 }
525 newRound();
526 }
527
timerEvent(QTimerEvent * event)528 void MyMainView::timerEvent(QTimerEvent *event)
529 {
530 unsigned w;
531 int i;
532 bool stopped = false;
533
534 if(event->timerId()==timerID)
535 {
536 QAbstractEventDispatcher::instance()->unregisterTimers(this);
537 if(gameEnd>0.0)
538 {
539 gameEnd-=1.0;
540 if(gameEnd<=0.0)
541 {
542 stopped = true;
543 if(textSprite)
544 {
545 textSprite->hide();
546 delete textSprite;
547 textSprite = nullptr;
548 }
549 textSprite = new QGraphicsSimpleTextItem(nullptr);
550 field.addItem(textSprite);
551 // FIXME
552 // textSprite->setTextFlags(Qt::AlignCenter);
553 textSprite->setBrush(QBrush(QColor(255,160,0)));
554 textSprite->setFont(QFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont).family(),14));
555 textSprite->show( );
556 if(ship[0]->getHitPoints()==0)
557 {
558 if(ship[1]->getHitPoints()==0)
559 textSprite->setText(i18n("draw round"));
560 else
561 {
562 textSprite->setText(i18n("blue player won the round"));
563 w=ship[1]->getWins()+1;
564 ship[1]->setWins(w);
565 Q_EMIT wins(1,w);
566 }
567 }
568 else
569 {
570 textSprite->setText(i18n("red player won the round"));
571 w=ship[0]->getWins()+1;
572 ship[0]->setWins(w);
573 Q_EMIT wins(0,w);
574 }
575 // must do this after setting text, because length is unknown until now
576 textSprite->setPos(QPointF((width()-textSprite->boundingRect().width()) / 2,height()/2-90));
577
578 QString str = i18n("Press %1 for new round",
579 actionCollection->action(QStringLiteral("game_start"))->shortcut().toString(QKeySequence::NativeText));
580 Q_EMIT setStatusText(str,IDS_MAIN);
581 stop( );
582 }
583 }
584
585 if( !stopped )
586 {
587 for(i=0;i<2;++i)
588 if(Options::playerIsAi(i)&&(ship[i]->getHitPoints()>0))
589 ai[i]->think();
590
591 moveMines();
592 moveBullets();
593 moveExplosions();
594 moveShips();
595 calculatePowerups();
596 collisions();
597 timerID=startTimer(Options::refreshTime());
598 }
599 //field.update();
600 }
601 }
602
moveShips()603 void MyMainView::moveShips()
604 {
605 int i,olde;
606 double nx,ny,en,nr;
607 BulletSprite *bullet;
608 MineSprite *mine;
609
610
611 for(i=0;i<2;++i)
612 {
613 bool playerIsAi = Options::playerIsAi(i);
614 olde=(int)ship[i]->getEnergy();
615 if(ship[i]->getHitPoints()==0)
616 {
617 ship[i]->forward(config.gamespeed);
618 ship[i]->calculateGravityAndEnergy(config.gravity,config.sunEnergy,
619 config.gamespeed);
620 }
621 else
622 {
623 ship[i]->calculateGravityAndEnergy(config.gravity,config.sunEnergy,
624 config.gamespeed);
625
626
627 if ((!playerIsAi && playerKeyPressed[i][PlayerKeyRight]) ||
628 (playerIsAi && ai[i]->rotateRight()))
629 ship[i]->rotateRight(config.rotationEnergyNeed,
630 config.rotationSpeed);
631
632 if ((!playerIsAi && playerKeyPressed[i][PlayerKeyLeft]) ||
633 (playerIsAi && ai[i]->rotateLeft()))
634 ship[i]->rotateLeft(config.rotationEnergyNeed,
635 config.rotationSpeed);
636
637 en=ship[i]->getEnergy();
638 nr=ship[i]->getRotation();
639
640 nx=cos(nr);
641 ny=sin(nr);
642 if (((!playerIsAi && playerKeyPressed[i][PlayerKeyAcc]) ||
643 (playerIsAi && ai[i]->accelerate()))
644 && (en>config.energyNeed) )
645 {
646 en-=config.energyNeed;
647 ship[i]->setVelocity(ship[i]->xVelocity()+nx*config.acc,
648 ship[i]->yVelocity()-ny*config.acc);
649
650 // limit speed to avoid "tunneling" through other objects
651 // FIXME: find a more elegant way
652 if (ship[i]->xVelocity()*ship[i]->xVelocity()+
653 ship[i]->yVelocity()*ship[i]->yVelocity() > MAX_VELOCITY*MAX_VELOCITY)
654 {
655 double alpha;
656 alpha = fabs(atan(ship[i]->yVelocity()/ship[i]->xVelocity()));
657 ship[i]->setVelocity(MAX_VELOCITY*cos(alpha)*fabs(ship[i]->xVelocity())/ship[i]->xVelocity(),
658 MAX_VELOCITY*sin(alpha)*fabs(ship[i]->yVelocity())/ship[i]->yVelocity());
659 }
660 }
661 if(en>MAX_ENERGY)
662 en=MAX_ENERGY;
663
664 ship[i]->forward(config.gamespeed);
665
666 //Bullets and Mines
667 if ((!playerIsAi && playerKeyPressed[i][PlayerKeyShot]) ||
668 (playerIsAi && ai[i]->shootBullet()))
669 {
670 if((en>config.shotEnergyNeed) && (!ship[i]->reloadsBullet()))
671 {
672 if(bullets[i]->count() <
673 (static_cast<int>(config.maxBullets) + ship[i]->getBulletPowerups()))
674 {
675 ship[i]->bullet(config.bulletReloadTime);
676 en-=config.shotEnergyNeed;
677 if(i)
678 bullet=new BulletSprite(svgrender, QStringLiteral( MV_BULLET2 ), i,
679 config.bulletLifeTime);
680 else
681 bullet=new BulletSprite(svgrender, QStringLiteral( MV_BULLET1 ), i,
682 config.bulletLifeTime);
683 field.addItem(bullet);
684 QPointF p;
685 p = ship[i]->mapToScene(ship[i]->center());
686 bullet->setPos(QPointF(p.x()+nx*SHOTDIST,p.y()-ny*SHOTDIST));
687 bullet->setVelocity(ship[i]->xVelocity()+nx*config.shotSpeed,
688 ship[i]->yVelocity()-ny*config.shotSpeed);
689 // bullet->setBoundsAction(QwRealMobileSprite::Wrap);
690 bullet->show();
691 bullets[i]->append(bullet);
692 }
693 }
694 }
695 if ((!Options::playerIsAi(i) && playerKeyPressed[i][PlayerKeyMine]) ||
696 (Options::playerIsAi(i) && ai[i]->layMine()))
697 {
698 if((en>config.mineEnergyNeed) && (!ship[i]->reloadsMine()))
699 {
700 if(mines[i]->count() <
701 (static_cast<int>(config.maxMines) + ship[i]->getMinePowerups()))
702 {
703 ship[i]->mine(config.mineReloadTime);
704 en-=config.mineEnergyNeed;
705 if (i==0)
706 mine=new MineSprite(svgrender,animation[ID_MINE1],animation[ID_MINEEXPLO],i,
707 config.mineActivateTime,config.mineFuel);
708 else
709 mine=new MineSprite(svgrender,animation[ID_MINE2],animation[ID_MINEEXPLO],i,
710 config.mineActivateTime,config.mineFuel);
711 field.addItem(mine);
712 mine->setPos(ship[i]->mapToScene(ship[i]->center()));
713 // move mine to center
714 mine->moveBy(-mine->center().x(),-mine->center().y());
715 mine->setVelocity(0,0);
716 //mine->setBoundsAction(QwRealMobileSprite::Wrap);
717 mine->show();
718 mines[i]->append(mine);
719 }
720 }
721 }
722 ship[i]->setEnergy(en);
723 if(olde!=(int)en)
724 Q_EMIT energy(i,(int)en);
725 }
726 }
727 }
728
moveMines()729 void MyMainView::moveMines()
730 {
731 int i;
732 MineSprite* mine;
733 int p;
734 int listsize; // used for caching QtList::size()
735
736 for(p=0;p<2;p++)
737 {
738 i=0;
739 listsize = mines[p]->size();
740 while (i<listsize)
741 {
742 mine = mines[p]->value(i);
743 mine->calculateGravity(config.gravity,config.gamespeed);
744 mine->forward(config.gamespeed);
745 if(mine->over())
746 {
747 mine->hide();
748 mines[p]->removeAt(i);
749 delete mine;
750 listsize--;
751 }
752 else
753 i++;
754 }
755 }
756 }
757
moveBullets()758 void MyMainView::moveBullets()
759 {
760 int i,j;
761 BulletSprite *sp;
762 int listsize; // used for caching QtList::size()
763
764 for(i=0;i<2;i++)
765 {
766 j=0;
767 listsize = bullets[i]->size();
768 while (j<listsize)
769 {
770 sp = bullets[i]->value(j);
771 sp->calculateGravity(config.gravity,config.gamespeed);
772 sp->forward(config.gamespeed);
773 if(sp->timeOut())
774 {
775 sp->hide();
776 bullets[i]->removeAll(sp);
777 listsize--;
778 }
779 else
780 j++;
781 }
782 }
783 }
784
moveExplosions()785 void MyMainView::moveExplosions()
786 {
787 int i=0;
788 ExplosionSprite *ex;
789 int listsize; // used for caching QtList::size()
790 listsize = explosions.size();
791 while (i<listsize)
792 {
793 ex = explosions[i];
794 ex->forward(config.gamespeed);
795 if(ex->isOver())
796 {
797 explosions.removeAt(i);
798 delete ex;
799 listsize--;
800 }
801 else
802 i++;
803 }
804 }
805
calculatePowerups()806 void MyMainView::calculatePowerups()
807 {
808 int i=0;
809 PowerupSprite *sp;
810 int listsize; // used for caching QtList::size()
811 listsize = powerups.size();
812 while (i<listsize)
813 {
814 sp = powerups[i];
815 sp->setLifetime(sp->getLifetime()-config.gamespeed);
816 if (sp->getLifetime()<0)
817 {
818 powerups.removeAt(i);
819 delete sp;
820 listsize--;
821 }
822 else
823 i++;
824 }
825 timeToNextPowerup-=config.gamespeed;
826 if(timeToNextPowerup<0)
827 {
828 int type,x,y;
829 timeToNextPowerup= random.bounded(config.powerupRefreshTime);
830 type= random.bounded(PowerupSprite::PowerupNum);
831 sp=new PowerupSprite(svgrender,powerupelements[type],type,
832 config.powerupLifeTime);
833 field.addItem(sp);
834 do
835 {
836 x = random.bounded(width()-40)+20;
837 y = random.bounded(height()-40)+20;
838 }
839 while(((x-width()/2)*(x-width()/2)+(y-height()/2)*(y-height()/2))<(50*50));
840 sp->setPos(QPointF(x,y));
841 powerups.append(sp);
842 sp->show();
843 }
844 }
845
collisions()846 void MyMainView::collisions()
847 {
848 int pl,hp,oldhp[2],ohp;
849 QList<QGraphicsItem *> unexact;
850 BulletSprite *bullet;
851 MineSprite *mine;
852 ExplosionSprite *expl;
853 ShipSprite *s;
854 PowerupSprite *power;
855 QList<QGraphicsItem *> hitlist;
856 double ndx[2],ndy[2];
857 double en;
858 int i;
859 int listsize; // used for caching QtList::size()
860
861 for(pl=0;pl<2;++pl)
862 {
863 if(!ship[pl]->isStopped())
864 {
865 unexact.clear();
866 unexact = ship[pl]->collidingItems(Qt::IntersectsItemBoundingRect);
867 oldhp[pl]=hp=ship[pl]->getHitPoints();
868 hitlist.clear();
869 for (QGraphicsItem *sprite : std::as_const(unexact)) {
870 if((sprite->type()!=S_EXPLOSION)
871 && !((sprite->type()!=S_SUN)&&(ship[pl]->getHitPoints()==0)))
872 if(ship[pl]->collidesWithItem(sprite,Qt::IntersectsItemShape))
873 if(!hitlist.contains(sprite))
874 hitlist.append(sprite);
875 }
876
877 for (QGraphicsItem *sprite : std::as_const(hitlist)) {
878 switch(sprite->type())
879 {
880 case S_SUN:
881 hp=0;
882 ship[pl]->stop();
883 break;
884 case S_BULLET:
885 bullet=(BulletSprite *)sprite;
886 bullet->hide();
887 //bullets[bullet->getPlayerNumber()]->removeRef(bullet);
888 bullets[bullet->getPlayerNumber()]->removeAll(bullet);
889 delete bullet;
890 hp-=config.bulletDamage;
891 break;
892 case S_SHIP:
893 s=(ShipSprite*)sprite;
894 ohp=s->getHitPoints();
895 if(ohp>0)
896 {
897 s->setHitPoints(ohp-hp-config.shipDamage);
898 Q_EMIT hitPoints(s->getPlayerNumber(),s->getHitPoints());
899 ndx[0]=((1-EPSILON)*ship[0]->xVelocity()+(1+EPSILON)*ship[1]->xVelocity())/2.0;
900 ndy[0]=((1-EPSILON)*ship[0]->yVelocity()+(1+EPSILON)*ship[1]->yVelocity())/2.0;
901 ndx[1]=((1-EPSILON)*ship[1]->xVelocity()+(1+EPSILON)*ship[0]->xVelocity())/2.0;
902 ndy[1]=((1-EPSILON)*ship[1]->yVelocity()+(1+EPSILON)*ship[0]->yVelocity())/2.0;
903 ship[0]->setVelocity(ndx[0],ndy[0]);
904 ship[1]->setVelocity(ndx[1],ndy[1]);
905 hp-=ohp+config.shipDamage;
906 }
907 break;
908 case S_MINE:
909 mine=(MineSprite *)sprite;
910 if(mine->isActive()&& !mine->explodes())
911 {
912 mine->explode();
913 ndx[0]=(ship[pl]->xVelocity()+0.3*mine->xVelocity())/1.3;
914 ndy[0]=(ship[pl]->yVelocity()+0.3*mine->yVelocity())/1.3;
915 ship[pl]->setVelocity(ndx[0],ndy[0]);
916 mine->setVelocity(ndx[0],ndy[0]);
917 hp-=config.mineDamage;
918 }
919 break;
920 case S_POWERUP:
921 power=(PowerupSprite *)sprite;
922 switch(power->getType())
923 {
924 case PowerupSprite::PowerupShield:
925 hp+=config.powerupShieldAmount;
926 break;
927 case PowerupSprite::PowerupEnergy:
928 en=ship[pl]->getEnergy()+config.powerupEnergyAmount;
929 if(en>MAX_ENERGY)
930 en=MAX_ENERGY;
931 ship[pl]->setEnergy(en);
932 break;
933 case PowerupSprite::PowerupMine:
934 ship[pl]->setMinePowerups(
935 ship[pl]->getMinePowerups()+1);
936 break;
937 case PowerupSprite::PowerupBullet:
938 ship[pl]->setBulletPowerups(
939 ship[pl]->getMinePowerups()+1);
940 break;
941 }
942 power->hide();
943 powerups.removeAll(power);
944 delete power;
945 break;
946 }
947 }
948 if(hp>MAX_HP)
949 hp=MAX_HP;
950 ship[pl]->setHitPoints(hp);
951 }
952
953 listsize = mines[pl]->size();
954 for (i=0; i<listsize; ++i)
955 {
956 mine = mines[pl]->value(i);
957 if(!mine->explodes())
958 {
959 unexact.clear();
960 unexact=mine->collidingItems(Qt::IntersectsItemBoundingRect);
961 hitlist.clear();
962 for (QGraphicsItem *sprite : std::as_const(unexact)) {
963 if(sprite->type()==S_BULLET)
964 if(mine->collidesWithItem(sprite))
965 if(!hitlist.contains(sprite))
966 hitlist.append(sprite);
967 }
968 if(!hitlist.isEmpty())
969 {
970 mine->explode();
971 for (QGraphicsItem *item : std::as_const(hitlist)) {
972 // FIXME: why does it crash with qgraphicsitem_cast?
973 bullet = static_cast<BulletSprite*>(item);// qgraphicsitem_cast<BulletSprite*>(item);
974 // bullets[bullet->getPlayerNumber()]->removeRef(bullet);
975 bullets[bullet->getPlayerNumber()]->removeAll(bullet);
976 delete bullet;
977 }
978 }
979 }
980 }
981 }
982
983 hitlist.clear();
984 unexact.clear();
985 unexact=sun->collidingItems(Qt::IntersectsItemBoundingRect);
986 for (QGraphicsItem *sprite : std::as_const(unexact)) {
987 switch(sprite->type())
988 {
989 case S_BULLET:
990 if(sun->collidesWithItem(sprite))
991 if(!hitlist.contains(sprite))
992 hitlist.append(sprite);
993 break;
994 case S_MINE:
995 if(!((MobileSprite*)sprite)->isStopped())
996 if(sun->collidesWithItem(sprite))
997 if(!hitlist.contains(sprite))
998 hitlist.append(sprite);
999 break;
1000 }
1001 }
1002
1003 for (QGraphicsItem *sprite : std::as_const(hitlist)) {
1004 switch(sprite->type())
1005 {
1006 case S_BULLET:
1007 bullet=(BulletSprite *)sprite;
1008 bullet->hide();
1009 bullets[bullet->getPlayerNumber()]->removeAll(bullet);
1010 delete bullet;
1011 break;
1012 case S_MINE:
1013 mine=(MineSprite*)sprite;
1014 mine->stop();
1015 if(!mine->explodes())
1016 mine->explode();
1017 break;
1018 }
1019 }
1020
1021
1022 for(pl=0;pl<2;++pl)
1023 {
1024 hp=ship[pl]->getHitPoints();
1025 if(hp!=oldhp[pl])
1026 Q_EMIT hitPoints(pl,hp);
1027 if((hp==0)&&(ship[pl]->getExplosion()<0))
1028 {
1029 ship[pl]->setExplosion((int)(EXPLOSION_TIME/config.gamespeed));
1030 expl = new ExplosionSprite(svgrender,animation[ID_EXPLOSION],ship[pl]);
1031 field.addItem(expl);
1032 expl->show();
1033 explosions.append(expl);
1034 gameEnd=Options::timeAfterKill()/config.gamespeed;
1035 }
1036 }
1037 }
1038
gameSetup()1039 void MyMainView::gameSetup()
1040 {
1041 if(!waitForStart)
1042 pause();
1043
1044 if (KConfigDialog::showDialog( QStringLiteral( "settings" )))
1045 return;
1046
1047 SettingsDialog *settings=new SettingsDialog(&customConfig,this,"settings");
1048 connect(settings, &SettingsDialog::settingsUpdated, this, &MyMainView::closeSettings);
1049 settings->show();
1050 }
1051
closeSettings()1052 void MyMainView::closeSettings(){
1053 if(Options::lastConfig()<predefinedConfigNum)
1054 config=modifyConfig(predefinedConfig[Options::lastConfig()]);
1055 else
1056 config=modifyConfig(customConfig);
1057 }
1058
focusOutEvent(QFocusEvent *)1059 void MyMainView::focusOutEvent (QFocusEvent * /*event*/)
1060 {
1061 /* FIXME: ugly hack, I think
1062 it's maybe better to declare something like QMyScene:public QGraphicsScene and process input there */
1063 setFocus(Qt::OtherFocusReason);
1064 }
1065
1066
1067