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