1/*
2 Copyright (C) 2002-2005, Jason Katz-Brown <jasonkb@mit.edu>
3 Copyright 2010 Stefan Majewsky <majewsky@gmx.net>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18*/
19
20#include "game.h"
21#include "itemfactory.h"
22#include "kcomboboxdialog.h"
23#include "obstacles.h"
24#include "shape.h"
25
26#include "tagaro/board.h"
27
28#include <QApplication>
29#include <QBoxLayout>
30#include <QCheckBox>
31#include <QLabel>
32#include <QMouseEvent>
33#include <QTimer>
34#include <KFileDialog>
35#include <KGameRenderer>
36#include <KgTheme>
37#include <KLineEdit>
38#include <KMessageBox>
39#include <KNumInput>
40#include <KRandom>
41#include <KStandardDirs>
42#include <Box2D/Dynamics/b2Body.h>
43#include <Box2D/Dynamics/Contacts/b2Contact.h>
44#include <Box2D/Dynamics/b2Fixture.h>
45#include <Box2D/Dynamics/b2World.h>
46#include <Box2D/Dynamics/b2WorldCallbacks.h>
47
48inline QString makeGroup(int id, int hole, const QString &name, int x, int y)
49{
50 return QString("%1-%2@%3,%4|%5").arg(hole).arg(name).arg(x).arg(y).arg(id);
51}
52
53inline QString makeStateGroup(int id, const QString &name)
54{
55 return QString("%1|%2").arg(name).arg(id);
56}
57
58class KolfContactListener : public b2ContactListener
59{
60 public:
61 virtual void PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
62 {
63 Q_UNUSED(oldManifold)
64 CanvasItem* citemA = static_cast<CanvasItem*>(contact->GetFixtureA()->GetBody()->GetUserData());
65 CanvasItem* citemB = static_cast<CanvasItem*>(contact->GetFixtureB()->GetBody()->GetUserData());
66 if (!CanvasItem::mayCollide(citemA, citemB))
67 contact->SetEnabled(false);
68 }
69};
70
71class KolfWorld : public b2World
72{
73 public:
74 KolfWorld()
75 : b2World(b2Vec2(0, 0), true) //parameters: no gravity, objects are allowed to sleep
76 {
77 SetContactListener(&m_listener);
78 }
79 private:
80 KolfContactListener m_listener;
81};
82
83class KolfTheme : public KgTheme
84{
85 public:
86 KolfTheme() : KgTheme("pics/default_theme.desktop")
87 {
88 setGraphicsPath(KStandardDirs::locate("appdata", "pics/default_theme.svgz"));
89 }
90};
91
92class KolfRenderer : public KGameRenderer
93{
94 public:
95 KolfRenderer() : KGameRenderer(new KolfTheme)
96 {
97 setStrategyEnabled(KGameRenderer::UseDiskCache, false);
98 setStrategyEnabled(KGameRenderer::UseRenderingThreads, false);
99 }
100};
101
102K_GLOBAL_STATIC(KolfRenderer, g_renderer)
103K_GLOBAL_STATIC(KolfWorld, g_world)
104
105KGameRenderer* Kolf::renderer()
106{
107 return g_renderer;
108}
109
110Tagaro::Board* Kolf::findBoard(QGraphicsItem* item_)
111{
112 //This returns the toplevel board instance in which the given parent resides.
113 return item_ ? dynamic_cast<Tagaro::Board*>(item_->topLevelItem()) : 0;
114}
115
116b2World* Kolf::world()
117{
118 return g_world;
119}
120
121/////////////////////////
122
123Putter::Putter(QGraphicsItem* parent, b2World* world)
124: QGraphicsLineItem(parent)
125, CanvasItem(world)
126{
127 setData(0, Rtti_Putter);
128 setZBehavior(CanvasItem::FixedZValue, 10001);
129 m_showGuideLine = true;
130 oneDegree = M_PI / 180;
131 guideLineLength = 9;
132 putterWidth = 11;
133 angle = 0;
134
135 guideLine = new QGraphicsLineItem(this);
136 guideLine->setPen(QPen(Qt::white));
137 guideLine->setZValue(998.8);
138
139 setPen(QPen(Qt::black, 4));
140 maxAngle = 2 * M_PI;
141
142 hideInfo();
143
144 // this also sets Z
145 resetAngles();
146}
147
148void Putter::showInfo()
149{
150 guideLine->setVisible(isVisible());
151}
152
153void Putter::hideInfo()
154{
155 guideLine->setVisible(m_showGuideLine? isVisible() : false);
156}
157
158void Putter::moveBy(double dx, double dy)
159{
160 QGraphicsLineItem::moveBy(dx, dy);
161 guideLine->setPos(x(), y());
162 CanvasItem::moveBy(dx, dy);
163}
164
165void Putter::setShowGuideLine(bool yes)
166{
167 m_showGuideLine = yes;
168 setVisible(isVisible());
169}
170
171void Putter::setVisible(bool yes)
172{
173 QGraphicsLineItem::setVisible(yes);
174 guideLine->setVisible(m_showGuideLine? yes : false);
175}
176
177void Putter::setOrigin(double _x, double _y)
178{
179 setVisible(true);
180 setPos(_x, _y);
181 guideLineLength = 9; //reset to default
182 finishMe();
183}
184
185void Putter::setAngle(Ball *ball)
186{
187 angle = angleMap.contains(ball)? angleMap[ball] : 0;
188 finishMe();
189}
190
191void Putter::go(Direction d, Amount amount)
192{
193 double addition = (amount == Amount_More? 6 * oneDegree : amount == Amount_Less? .5 * oneDegree : 2 * oneDegree);
194
195 switch (d)
196 {
197 case Forwards:
198 guideLineLength -= 1;
199 guideLine->setVisible(false);
200 break;
201 case Backwards:
202 guideLineLength += 1;
203 guideLine->setVisible(false);
204 break;
205 case D_Left:
206 angle += addition;
207 if (angle > maxAngle)
208 angle -= maxAngle;
209 break;
210 case D_Right:
211 angle -= addition;
212 if (angle < 0)
213 angle = maxAngle - fabs(angle);
214 break;
215 }
216
217 finishMe();
218}
219
220void Putter::finishMe()
221{
222 midPoint.setX(cos(angle) * guideLineLength);
223 midPoint.setY(-sin(angle) * guideLineLength);
224
225 QPointF start;
226 QPointF end;
227
228 if (midPoint.y() || !midPoint.x())
229 {
230 start.setX(midPoint.x() - putterWidth * sin(angle));
231 start.setY(midPoint.y() - putterWidth * cos(angle));
232 end.setX(midPoint.x() + putterWidth * sin(angle));
233 end.setY(midPoint.y() + putterWidth * cos(angle));
234 }
235 else
236 {
237 start.setX(midPoint.x());
238 start.setY(midPoint.y() + putterWidth);
239 end.setY(midPoint.y() - putterWidth);
240 end.setX(midPoint.x());
241 }
242
243 guideLine->setLine(midPoint.x(), midPoint.y(), -cos(angle) * guideLineLength * 4, sin(angle) * guideLineLength * 4);
244
245 setLine(start.x(), start.y(), end.x(), end.y());
246}
247
248/////////////////////////
249
250HoleConfig::HoleConfig(HoleInfo *holeInfo, QWidget *parent)
251: Config(parent)
252{
253 this->holeInfo = holeInfo;
254
255 QVBoxLayout *layout = new QVBoxLayout(this);
256 layout->setMargin( marginHint() );
257 layout->setSpacing( spacingHint() );
258
259 QHBoxLayout *hlayout = new QHBoxLayout;
260 hlayout->setSpacing( spacingHint() );
261 layout->addLayout( hlayout );
262 hlayout->addWidget(new QLabel(i18n("Course name: "), this));
263 KLineEdit *nameEdit = new KLineEdit(holeInfo->untranslatedName(), this);
264 hlayout->addWidget(nameEdit);
265 connect(nameEdit, SIGNAL(textChanged(QString)), this, SLOT(nameChanged(QString)));
266
267 hlayout = new QHBoxLayout;
268 hlayout->setSpacing( spacingHint() );
269 layout->addLayout( hlayout );
270 hlayout->addWidget(new QLabel(i18n("Course author: "), this));
271 KLineEdit *authorEdit = new KLineEdit(holeInfo->author(), this);
272 hlayout->addWidget(authorEdit);
273 connect(authorEdit, SIGNAL(textChanged(QString)), this, SLOT(authorChanged(QString)));
274
275 layout->addStretch();
276
277 hlayout = new QHBoxLayout;
278 hlayout->setSpacing( spacingHint() );
279 layout->addLayout( hlayout );
280 hlayout->addWidget(new QLabel(i18n("Par:"), this));
281 KIntSpinBox *par = new KIntSpinBox(this);
282 par->setRange( 1, 15 );
283 par->setSingleStep( 1 );
284 par->setValue(holeInfo->par());
285 hlayout->addWidget(par);
286 connect(par, SIGNAL(valueChanged(int)), this, SLOT(parChanged(int)));
287 hlayout->addStretch();
288
289 hlayout->addWidget(new QLabel(i18n("Maximum:"), this));
290 KIntSpinBox *maxstrokes = new KIntSpinBox(this);
291 maxstrokes->setRange( holeInfo->lowestMaxStrokes(), 30 );
292 maxstrokes->setSingleStep( 1 );
293 maxstrokes->setWhatsThis( i18n("Maximum number of strokes player can take on this hole."));
294 maxstrokes->setToolTip( i18n("Maximum number of strokes"));
295 maxstrokes->setSpecialValueText(i18n("Unlimited"));
296 maxstrokes->setValue(holeInfo->maxStrokes());
297 hlayout->addWidget(maxstrokes);
298 connect(maxstrokes, SIGNAL(valueChanged(int)), this, SLOT(maxStrokesChanged(int)));
299
300 QCheckBox *check = new QCheckBox(i18n("Show border walls"), this);
301 check->setChecked(holeInfo->borderWalls());
302 layout->addWidget(check);
303 connect(check, SIGNAL(toggled(bool)), this, SLOT(borderWallsChanged(bool)));
304}
305
306void HoleConfig::authorChanged(const QString &newauthor)
307{
308 holeInfo->setAuthor(newauthor);
309 changed();
310}
311
312void HoleConfig::nameChanged(const QString &newname)
313{
314 holeInfo->setName(newname);
315 holeInfo->setUntranslatedName(newname);
316 changed();
317}
318
319void HoleConfig::parChanged(int newpar)
320{
321 holeInfo->setPar(newpar);
322 changed();
323}
324
325void HoleConfig::maxStrokesChanged(int newms)
326{
327 holeInfo->setMaxStrokes(newms);
328 changed();
329}
330
331void HoleConfig::borderWallsChanged(bool yes)
332{
333 holeInfo->borderWallsChanged(yes);
334 changed();
335}
336
337/////////////////////////
338
339StrokeCircle::StrokeCircle(QGraphicsItem *parent)
340: QGraphicsItem(parent)
341{
342 dvalue = 0;
343 dmax = 360;
344 iwidth = 100;
345 iheight = 100;
346 ithickness = 8;
347 setZValue(10000);
348
349 setSize(QSizeF(80, 80));
350 setThickness(8);
351}
352
353void StrokeCircle::setValue(double v)
354{
355 dvalue = v;
356 if (dvalue > dmax)
357 dvalue = dmax;
358
359 update();
360}
361
362double StrokeCircle::value()
363{
364 return dvalue;
365}
366
367bool StrokeCircle::collidesWithItem(const QGraphicsItem*, Qt::ItemSelectionMode) const { return false; }
368
369QRectF StrokeCircle::boundingRect() const { return QRectF(x(), y(), iwidth, iheight); }
370
371void StrokeCircle::setMaxValue(double m)
372{
373 dmax = m;
374 if (dvalue > dmax)
375 dvalue = dmax;
376}
377void StrokeCircle::setSize(const QSizeF& size)
378{
379 if (size.width() > 0)
380 iwidth = size.width();
381 if (size.height() > 0)
382 iheight = size.height();
383}
384void StrokeCircle::setThickness(double t)
385{
386 if (t > 0)
387 ithickness = t;
388}
389
390double StrokeCircle::thickness() const
391{
392 return ithickness;
393}
394
395double StrokeCircle::width() const
396{
397 return iwidth;
398}
399
400double StrokeCircle::height() const
401{
402 return iheight;
403}
404
405void StrokeCircle::paint (QPainter *p, const QStyleOptionGraphicsItem *, QWidget * )
406{
407 int al = (int)((dvalue * 360 * 16) / dmax);
408 int length, deg;
409 if (al < 0)
410 {
411 deg = 270 * 16;
412 length = -al;
413 }
414 else if (al <= (270 * 16))
415 {
416 deg = 270 * 16 - al;
417 length = al;
418 }
419 else
420 {
421 deg = (360 * 16) - (al - (270 * 16));
422 length = al;
423 }
424
425 p->setBrush(QBrush(Qt::black, Qt::NoBrush));
426 p->setPen(QPen(Qt::white, ithickness / 2));
427 p->drawEllipse(QRectF(x() + ithickness / 2, y() + ithickness / 2, iwidth - ithickness, iheight - ithickness));
428
429 if(dvalue>=0)
430 p->setPen(QPen(QColor((int)((0xff * dvalue) / dmax), 0, (int)(0xff - (0xff * dvalue) / dmax)), ithickness));
431 else
432 p->setPen(QPen(QColor("black"), ithickness));
433
434 p->drawArc(QRectF(x() + ithickness / 2, y() + ithickness / 2, iwidth - ithickness, iheight - ithickness), deg, length);
435
436 p->setPen(QPen(Qt::white, 1));
437 p->drawEllipse(QRectF(x(), y(), iwidth, iheight));
438 p->drawEllipse(QRectF(x() + ithickness, y() + ithickness, iwidth - ithickness * 2, iheight - ithickness * 2));
439 p->setPen(QPen(Qt::white, 3));
440 p->drawLine(QPointF(x() + iwidth / 2, y() + iheight - ithickness * 1.5), QPointF(x() + iwidth / 2, y() + iheight));
441 p->drawLine(QPointF(x() + iwidth / 4 - iwidth / 20, y() + iheight - iheight / 4 + iheight / 20), QPointF(x() + iwidth / 4 + iwidth / 20, y() + iheight - iheight / 4 - iheight / 20));
442 p->drawLine(QPointF(x() + iwidth - iwidth / 4 + iwidth / 20, y() + iheight - iheight / 4 + iheight / 20), QPointF(x() + iwidth - iwidth / 4 - iwidth / 20, y() + iheight - iheight / 4 - iheight / 20));
443}
444/////////////////////////////////////////
445
446KolfGame::KolfGame(const Kolf::ItemFactory& factory, PlayerList *players, const QString &filename, QWidget *parent)
447: QGraphicsView(parent)
448, m_factory(factory)
449, holeInfo(g_world)
450{
451 setRenderHint(QPainter::Antialiasing);
452 // for mouse control
453 setMouseTracking(true);
454 viewport()->setMouseTracking(true);
455 setFrameShape(NoFrame);
456
457 regAdv = false;
458 curHole = 0; // will get ++'d
459 cfg = 0;
460 setFilename(filename);
461 this->players = players;
462 curPlayer = players->end();
463 curPlayer--; // will get ++'d to end and sent back
464 // to beginning
465 paused = false;
466 modified = false;
467 inPlay = false;
468 putting = false;
469 stroking = false;
470 editing = false;
471 strict = false;
472 lastDelId = -1;
473 m_showInfo = false;
474 ballStateList.canUndo = false;
475 soundDir = KStandardDirs::locate("appdata", "sounds/");
476 dontAddStroke = false;
477 addingNewHole = false;
478 scoreboardHoles = 0;
479 infoShown = false;
480 m_useMouse = true;
481 m_useAdvancedPutting = true;
482 m_sound = true;
483 m_ignoreEvents = false;
484 highestHole = 0;
485 recalcHighestHole = false;
486 banner = 0;
487
488#ifdef SOUND
489 m_player = Phonon::createPlayer(Phonon::GameCategory);
490#endif
491
492 holeInfo.setGame(this);
493 holeInfo.setAuthor(i18n("Course Author"));
494 holeInfo.setName(i18n("Course Name"));
495 holeInfo.setUntranslatedName(i18n("Course Name"));
496 holeInfo.setMaxStrokes(10);
497 holeInfo.borderWallsChanged(true);
498
499 // width and height are the width and height of the scene
500 // in easy storage
501 width = 400;
502 height = 400;
503
504 margin = 10;
505
506 setFocusPolicy(Qt::StrongFocus);
507 setMinimumSize(width, height);
508 QSizePolicy sizePolicy = QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
509 setSizePolicy(sizePolicy);
510
511 setContentsMargins(margin, margin, margin, margin);
512
513 course = new Tagaro::Scene(Kolf::renderer(), "grass");
514 course->setMainView(this); //this does this->setScene(course)
515 courseBoard = new Tagaro::Board;
516 courseBoard->setLogicalSize(QSizeF(400, 400));
517 course->addItem(courseBoard);
518
519 if( filename.contains( "intro" ) )
520 {
521 banner = new Tagaro::SpriteObjectItem(Kolf::renderer(), "intro_foreground", courseBoard);
522 banner->setSize(400, 132);
523 banner->setPos(0, 32);
524 banner->setZValue(3); //on the height of a puddle (above slopes and sands, below any objects)
525 }
526
527 adjustSize();
528
529 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
530 {
531 Ball* ball = (*it).ball();
532 ball->setParentItem(courseBoard);
533 m_topLevelQItems << ball;
534 m_moveableQItems << ball;
535 }
536
537 QFont font = QApplication::font();
538 font.setPixelSize(12);
539
540 // create the advanced putting indicator
541 strokeCircle = new StrokeCircle(courseBoard);
542 strokeCircle->setPos(width - 90, height - 90);
543 strokeCircle->setVisible(false);
544 strokeCircle->setValue(0);
545 strokeCircle->setMaxValue(360);
546
547 // whiteBall marks the spot of the whole whilst editing
548 whiteBall = new Ball(courseBoard, g_world);
549 whiteBall->setGame(this);
550 whiteBall->setColor(Qt::white);
551 whiteBall->setVisible(false);
552 whiteBall->setDoDetect(false);
553 m_topLevelQItems << whiteBall;
554 m_moveableQItems << whiteBall;
555
556 int highestLog = 0;
557
558 // if players have scores from loaded game, move to last hole
559 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
560 {
561 if ((int)(*it).scores().count() > highestLog)
562 highestLog = (*it).scores().count();
563
564 (*it).ball()->setGame(this);
565 }
566
567 // here only for saved games
568 if (highestLog)
569 curHole = highestLog;
570
571 putter = new Putter(courseBoard, g_world);
572
573 // border walls:
574
575 // horiz
576 addBorderWall(QPoint(margin, margin), QPoint(width - margin, margin));
577 addBorderWall(QPoint(margin, height - margin - 1), QPoint(width - margin, height - margin - 1));
578
579 // vert
580 addBorderWall(QPoint(margin, margin), QPoint(margin, height - margin));
581 addBorderWall(QPoint(width - margin - 1, margin), QPoint(width - margin - 1, height - margin));
582
583 timer = new QTimer(this);
584 connect(timer, SIGNAL(timeout()), this, SLOT(timeout()));
585 timerMsec = 300;
586
587 fastTimer = new QTimer(this);
588 connect(fastTimer, SIGNAL(timeout()), this, SLOT(fastTimeout()));
589 fastTimerMsec = 11;
590
591 autoSaveTimer = new QTimer(this);
592 connect(autoSaveTimer, SIGNAL(timeout()), this, SLOT(autoSaveTimeout()));
593 autoSaveMsec = 5 * 1000 * 60; // 5 min autosave
594
595 // setUseAdvancedPutting() sets maxStrength!
596 setUseAdvancedPutting(false);
597
598 putting = false;
599 putterTimer = new QTimer(this);
600 connect(putterTimer, SIGNAL(timeout()), this, SLOT(putterTimeout()));
601 putterTimerMsec = 20;
602}
603
604void KolfGame::startFirstHole(int hole)
605{
606 if (curHole > 0) // if there was saved game, sync scoreboard
607 // with number of holes
608 {
609 for (; scoreboardHoles < curHole; ++scoreboardHoles)
610 {
611 cfgGroup = KConfigGroup(cfg->group(QString("%1-hole@-50,-50|0").arg(scoreboardHoles + 1)));
612 emit newHole(cfgGroup.readEntry("par", 3));
613 }
614
615 // lets load all of the scores from saved game if there are any
616 for (int hole = 1; hole <= curHole; ++hole)
617 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
618 emit scoreChanged((*it).id(), hole, (*it).score(hole));
619 }
620
621 curHole = hole - 1;
622
623 // this increments curHole, etc
624 recalcHighestHole = true;
625 startNextHole();
626 paused = true;
627 unPause();
628}
629
630void KolfGame::setFilename(const QString &filename)
631{
632 this->filename = filename;
633 delete cfg;
634 cfg = new KConfig(filename, KConfig::NoGlobals);
635}
636
637KolfGame::~KolfGame()
638{
639 QList<QGraphicsItem*> itemsCopy(m_topLevelQItems); //this list will be modified soon, so take a copy
640 foreach (QGraphicsItem* item, itemsCopy)
641 {
642 CanvasItem* citem = dynamic_cast<CanvasItem*>(item);
643 delete citem;
644 }
645
646 delete cfg;
647#ifdef SOUND
648 delete m_player;
649#endif
650}
651
652void KolfGame::setModified(bool mod)
653{
654 modified = mod;
655 emit modifiedChanged(mod);
656}
657
658void KolfGame::pause()
659{
660 if (paused)
661 {
662 // play along with people who call pause() again, instead of unPause()
663 unPause();
664 return;
665 }
666
667 paused = true;
668 timer->stop();
669 fastTimer->stop();
670 putterTimer->stop();
671}
672
673void KolfGame::unPause()
674{
675 if (!paused)
676 return;
677
678 paused = false;
679
680 timer->start(timerMsec);
681 fastTimer->start(fastTimerMsec);
682
683 if (putting || stroking)
684 putterTimer->start(putterTimerMsec);
685}
686
687void KolfGame::addBorderWall(const QPoint &start, const QPoint &end)
688{
689 Kolf::Wall *wall = new Kolf::Wall(courseBoard, g_world);
690 wall->setLine(QLineF(start, end));
691 wall->setVisible(true);
692 wall->setGame(this);
693 //change Z value to something very high so that border walls
694 //really keep the balls inside the course
695 wall->setZBehavior(CanvasItem::FixedZValue, 10000);
696 borderWalls.append(wall);
697}
698
699void KolfGame::handleMouseDoubleClickEvent(QMouseEvent *e)
700{
701 // allow two fast single clicks
702 handleMousePressEvent(e);
703}
704
705void KolfGame::handleMousePressEvent(QMouseEvent *e)
706{
707 if (m_ignoreEvents)
708 return;
709
710 if (editing)
711 {
712 //at this point, QGV::mousePressEvent and thus the interaction
713 //with overlays has already been done; we therefore know that
714 //the user has clicked into free space
715 setSelectedItem(0);
716 return;
717 }
718 else
719 {
720 if (m_useMouse)
721 {
722 if (!inPlay && e->button() == Qt::LeftButton)
723 puttPress();
724 else if (e->button() == Qt::RightButton)
725 toggleShowInfo();
726 }
727 }
728
729 setFocus();
730}
731
732QPoint KolfGame::viewportToViewport(const QPoint &p)
733{
734 //convert viewport coordinates to board coordinates
735 return courseBoard->deviceTransform(viewportTransform()).inverted().map(p);
736}
737
738// the following four functions are needed to handle both
739// border presses and regular in-course presses
740
741void KolfGame::mouseReleaseEvent(QMouseEvent * e)
742{
743 e->setAccepted(false);
744 QGraphicsView::mouseReleaseEvent(e);
745 if (e->isAccepted())
746 return;
747
748 QMouseEvent fixedEvent (QEvent::MouseButtonRelease, viewportToViewport(e->pos()), e->button(), e->buttons(), e->modifiers());
749 handleMouseReleaseEvent(&fixedEvent);
750 e->accept();
751}
752
753void KolfGame::mousePressEvent(QMouseEvent * e)
754{
755 e->setAccepted(false);
756 QGraphicsView::mousePressEvent(e);
757 if (e->isAccepted())
758 return;
759
760 QMouseEvent fixedEvent (QEvent::MouseButtonPress, viewportToViewport(e->pos()), e->button(), e->buttons(), e->modifiers());
761 handleMousePressEvent(&fixedEvent);
762 e->accept();
763}
764
765void KolfGame::mouseDoubleClickEvent(QMouseEvent * e)
766{
767 e->setAccepted(false);
768 QGraphicsView::mouseDoubleClickEvent(e);
769 if (e->isAccepted())
770 return;
771
772 QMouseEvent fixedEvent (QEvent::MouseButtonDblClick, viewportToViewport(e->pos()), e->button(), e->buttons(), e->modifiers());
773 handleMouseDoubleClickEvent(&fixedEvent);
774 e->accept();
775}
776
777void KolfGame::mouseMoveEvent(QMouseEvent * e)
778{
779 e->setAccepted(false);
780 QGraphicsView::mouseMoveEvent(e);
781 if (e->isAccepted())
782 return;
783
784 QMouseEvent fixedEvent (QEvent::MouseMove, viewportToViewport(e->pos()), e->button(), e->buttons(), e->modifiers());
785 handleMouseMoveEvent(&fixedEvent);
786 e->accept();
787}
788
789void KolfGame::handleMouseMoveEvent(QMouseEvent *e)
790{
791 if (!editing && !inPlay && putter && !m_ignoreEvents)
792 {
793 // mouse moving of putter
794 updateMouse();
795 e->accept();
796 }
797}
798
799void KolfGame::updateMouse()
800{
801 // don't move putter if in advanced putting sequence
802 if (!m_useMouse || ((stroking || putting) && m_useAdvancedPutting))
803 return;
804
805 const QPointF cursor = viewportToViewport(mapFromGlobal(QCursor::pos()));
806 const QPointF ball((*curPlayer).ball()->x(), (*curPlayer).ball()->y());
807 putter->setAngle(-Vector(cursor - ball).direction());
808}
809
810void KolfGame::handleMouseReleaseEvent(QMouseEvent *e)
811{
812 setCursor(Qt::ArrowCursor);
813
814 if (editing)
815 {
816 emit newStatusText(QString());
817 }
818
819 if (m_ignoreEvents)
820 return;
821
822 if (!editing && m_useMouse)
823 {
824 if (!inPlay && e->button() == Qt::LeftButton)
825 puttRelease();
826 else if (e->button() == Qt::RightButton)
827 toggleShowInfo();
828 }
829
830 setFocus();
831}
832
833void KolfGame::keyPressEvent(QKeyEvent *e)
834{
835 if (inPlay || editing || m_ignoreEvents)
836 return;
837
838 switch (e->key())
839 {
840 case Qt::Key_Up:
841 if (!e->isAutoRepeat())
842 toggleShowInfo();
843 break;
844
845 case Qt::Key_Escape:
846 putting = false;
847 stroking = false;
848 finishStroking = false;
849 strokeCircle->setVisible(false);
850 putterTimer->stop();
851 putter->setOrigin((*curPlayer).ball()->x(), (*curPlayer).ball()->y());
852 break;
853
854 case Qt::Key_Left:
855 case Qt::Key_Right:
856 // don't move putter if in advanced putting sequence
857 if ((!stroking && !putting) || !m_useAdvancedPutting)
858 putter->go(e->key() == Qt::Key_Left? D_Left : D_Right, e->modifiers() & Qt::ShiftModifier? Amount_More : e->modifiers() & Qt::ControlModifier? Amount_Less : Amount_Normal);
859 break;
860
861 case Qt::Key_Space: case Qt::Key_Down:
862 puttPress();
863 break;
864
865 default:
866 break;
867 }
868}
869
870void KolfGame::toggleShowInfo()
871{
872 setShowInfo(!m_showInfo);
873}
874
875void KolfGame::updateShowInfo()
876{
877 setShowInfo(m_showInfo);
878}
879
880void KolfGame::setShowInfo(bool yes)
881{
882 m_showInfo = yes;
883 QList<QGraphicsItem*> infoItems;
884 foreach (QGraphicsItem* qitem, m_topLevelQItems)
885 {
886 CanvasItem *citem = dynamic_cast<CanvasItem *>(qitem);
887 if (citem)
888 infoItems << citem->infoItems();
889 }
890 foreach (QGraphicsItem* qitem, infoItems)
891 qitem->setVisible(m_showInfo);
892}
893
894void KolfGame::puttPress()
895{
896 // Advanced putting: 1st click start putting sequence, 2nd determine strength, 3rd determine precision
897
898 if (!putting && !stroking && !inPlay)
899 {
900 puttCount = 0;
901 puttReverse = false;
902 putting = true;
903 stroking = false;
904 strength = 0;
905 if (m_useAdvancedPutting)
906 {
907 strokeCircle->setValue(0);
908 int pw = (int)(putter->line().x2() - putter->line().x1());
909 if (pw < 0) pw = -pw;
910 int px = (int)putter->x() + pw / 2;
911 int py = (int)putter->y();
912 if (px > width / 2 && py < height / 2)
913 strokeCircle->setPos(px/2 - pw / 2 - 5 - strokeCircle->width()/2, py/2 + 5);
914 else if (px > width / 2)
915 strokeCircle->setPos(px/2 - pw / 2 - 5 - strokeCircle->width()/2, py/2 - 5 - strokeCircle->height()/2);
916 else if (py < height / 2)
917 strokeCircle->setPos(px/2 + pw / 2 + 5, py/2 + 5);
918 else
919 strokeCircle->setPos(px/2 + pw / 2 + 5, py/2 - 5 - strokeCircle->height()/2);
920 strokeCircle->setVisible(true);
921 }
922 putterTimer->start(putterTimerMsec);
923 }
924 else if (m_useAdvancedPutting && putting && !editing)
925 {
926 putting = false;
927 stroking = true;
928 puttReverse = false;
929 finishStroking = false;
930 }
931 else if (m_useAdvancedPutting && stroking)
932 {
933 finishStroking = true;
934 putterTimeout();
935 }
936}
937
938void KolfGame::keyReleaseEvent(QKeyEvent *e)
939{
940 if (e->isAutoRepeat() || m_ignoreEvents)
941 return;
942
943 if (e->key() == Qt::Key_Space || e->key() == Qt::Key_Down)
944 puttRelease();
945 else if ((e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete) && !(e->modifiers() & Qt::ControlModifier))
946 {
947 if (editing && selectedItem)
948 {
949 CanvasItem *citem = dynamic_cast<CanvasItem *>(selectedItem);
950 if (!citem)
951 return;
952 QGraphicsItem *item = dynamic_cast<QGraphicsItem *>(citem);
953 if (citem && !dynamic_cast<Ball*>(item))
954 {
955 lastDelId = citem->curId();
956
957 m_topLevelQItems.removeAll(item);
958 m_moveableQItems.removeAll(item);
959 delete citem;
960 setSelectedItem(0);
961
962 setModified(true);
963 }
964 }
965 }
966 else if (e->key() == Qt::Key_I || e->key() == Qt::Key_Up)
967 toggleShowInfo();
968}
969
970void KolfGame::resizeEvent( QResizeEvent* ev )
971{
972 int newW = ev->size().width();
973 int newH = ev->size().height();
974 int oldW = ev->oldSize().width();
975 int oldH = ev->oldSize().height();
976
977 if(oldW<=0 || oldH<=0) //this is the first draw so no point wasting resources resizing yet
978 return;
979 else if( (oldW==newW) && (oldH==newH) )
980 return;
981
982 int setSize = qMin(newW, newH);
983 QGraphicsView::resize(setSize, setSize); //make sure new size is square
984}
985
986void KolfGame::puttRelease()
987{
988 if (!m_useAdvancedPutting && putting && !editing)
989 {
990 putting = false;
991 stroking = true;
992 }
993}
994
995void KolfGame::stoppedBall()
996{
997 if (!inPlay)
998 {
999 inPlay = true;
1000 dontAddStroke = true;
1001 }
1002}
1003
1004void KolfGame::timeout()
1005{
1006 Ball *curBall = (*curPlayer).ball();
1007
1008 // test if the ball is gone
1009 // in this case we want to stop the ball and
1010 // later undo the shot
1011 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
1012 {
1013 //QGV handles management of dirtied rects for us
1014 //course->update();
1015
1016 if (!QRectF(QPointF(), courseBoard->logicalSize()).contains((*it).ball()->pos()))
1017 {
1018 (*it).ball()->setState(Stopped);
1019
1020 // don't do it if he's past maxStrokes
1021 if ((*it).score(curHole) < holeInfo.maxStrokes() - 1 || !holeInfo.hasMaxStrokes())
1022 {
1023 loadStateList();
1024 }
1025 shotDone();
1026
1027 return;
1028 }
1029 }
1030
1031 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
1032 if ((*it).ball()->forceStillGoing() || ((*it).ball()->curState() == Rolling && Vector((*it).ball()->velocity()).magnitude() > 0 && (*it).ball()->isVisible()))
1033 return;
1034
1035 int curState = curBall->curState();
1036 if (curState == Stopped && inPlay)
1037 {
1038 inPlay = false;
1039 QTimer::singleShot(0, this, SLOT(shotDone()));
1040 }
1041
1042 if (curState == Holed && inPlay)
1043 {
1044 emit inPlayEnd();
1045
1046 int curScore = (*curPlayer).score(curHole);
1047 if (!dontAddStroke)
1048 curScore++;
1049
1050 if (curScore == 1)
1051 {
1052 playSound("holeinone");
1053 }
1054 else if (curScore <= holeInfo.par())
1055 {
1056 // I don't have a sound!!
1057 // *sob*
1058 // playSound("woohoo");
1059 }
1060
1061 (*curPlayer).ball()->setZValue((*curPlayer).ball()->zValue() + .1 - (.1)/(curScore));
1062
1063 if (allPlayersDone())
1064 {
1065 inPlay = false;
1066
1067 if (curHole > 0 && !dontAddStroke)
1068 {
1069 (*curPlayer).addStrokeToHole(curHole);
1070 emit scoreChanged((*curPlayer).id(), curHole, (*curPlayer).score(curHole));
1071 }
1072 QTimer::singleShot(600, this, SLOT(holeDone()));
1073 }
1074 else
1075 {
1076 inPlay = false;
1077 QTimer::singleShot(0, this, SLOT(shotDone()));
1078 }
1079 }
1080}
1081
1082void KolfGame::fastTimeout()
1083{
1084 // do regular advance every other time
1085 if (regAdv)
1086 course->advance();
1087 regAdv = !regAdv;
1088
1089 if (editing)
1090 return;
1091
1092 // do Box2D advance
1093 //Because there are so much CanvasItems out there, there is currently no
1094 //easy and/or systematic approach to iterate over all of them, except for
1095 //using the b2Bodies available on the world.
1096
1097 //prepare simulation
1098 for (b2Body* body = g_world->GetBodyList(); body; body = body->GetNext())
1099 {
1100 CanvasItem* citem = static_cast<CanvasItem*>(body->GetUserData());
1101 if (citem)
1102 {
1103 citem->startSimulation();
1104 //HACK: the following should not be necessary at this point
1105 QGraphicsItem* qitem = dynamic_cast<QGraphicsItem*>(citem);
1106 if (qitem)
1107 citem->updateZ(qitem);
1108 }
1109 }
1110 //step world
1111 //NOTE: I previously set timeStep to 1.0 so that CItem's velocity()
1112 //corresponds to the position change per step. In this case, the
1113 //velocity would be scaled by Kolf::Box2DScaleFactor, which would result in
1114 //very small velocities (below Box2D's internal cutoff thresholds!) for
1115 //usual movements. Therefore, we apply the scaling to the timestep instead.
1116 const double timeStep = 1.0 * Kolf::Box2DScaleFactor;
1117 g_world->Step(timeStep, 10, 10); //parameters 2/3 = iteration counts (TODO: optimize)
1118 //conclude simulation
1119 for (b2Body* body = g_world->GetBodyList(); body; body = body->GetNext())
1120 {
1121 CanvasItem* citem = static_cast<CanvasItem*>(body->GetUserData());
1122 if (citem)
1123 {
1124 citem->endSimulation();
1125 }
1126 }
1127}
1128
1129void KolfGame::ballMoved()
1130{
1131 if (putter->isVisible())
1132 {
1133 putter->setPos((*curPlayer).ball()->x(), (*curPlayer).ball()->y());
1134 updateMouse();
1135 }
1136}
1137
1138void KolfGame::putterTimeout()
1139{
1140 if (inPlay || editing)
1141 return;
1142
1143 if (m_useAdvancedPutting)
1144 {
1145 if (putting)
1146 {
1147 const qreal base = 2.0;
1148
1149 if (puttReverse && strength <= 0)
1150 {
1151 // aborted
1152 putting = false;
1153 strokeCircle->setVisible(false);
1154 }
1155 else if (strength > maxStrength || puttReverse)
1156 {
1157 // decreasing strength as we've reached the top
1158 puttReverse = true;
1159 strength -= pow(base, qreal(strength / maxStrength)) - 1.8;
1160 if ((int)strength < puttCount * 2)
1161 {
1162 puttCount--;
1163 if (puttCount >= 0)
1164 putter->go(Forwards);
1165 }
1166 }
1167 else
1168 {
1169 // make the increase at high strength faster
1170 strength += pow(base, strength / maxStrength) - .3;
1171 if ((int)strength > puttCount * 2)
1172 {
1173 putter->go(Backwards);
1174 puttCount++;
1175 }
1176 }
1177 // make the visible steps at high strength smaller
1178 strokeCircle->setValue(pow(strength / maxStrength, 0.8) * 360);
1179 }
1180 else if (stroking)
1181 {
1182 double al = strokeCircle->value();
1183 if (al >= 45)
1184 al -= 0.2 + strength / 50 + al / 100;
1185 else
1186 al -= 0.2 + strength / 50;
1187
1188 if (puttReverse)
1189 {
1190 // show the stroke
1191 puttCount--;
1192 if (puttCount >= 0)
1193 putter->go(Forwards);
1194 else
1195 {
1196 strokeCircle->setVisible(false);
1197 finishStroking = false;
1198 putterTimer->stop();
1199 putting = false;
1200 stroking = false;
1201 shotStart();
1202 }
1203 }
1204 else if (al < -45 || finishStroking)
1205 {
1206 strokeCircle->setValue(al);
1207 int deg;
1208 // if > 45 or < -45 then bad stroke
1209 if (al > 45)
1210 {
1211 deg = putter->curDeg() - 45 + rand() % 90;
1212 strength -= rand() % (int)strength;
1213 }
1214 else if (!finishStroking)
1215 {
1216 deg = putter->curDeg() - 45 + rand() % 90;
1217 strength -= rand() % (int)strength;
1218 }
1219 else
1220 deg = putter->curDeg() + (int)(strokeCircle->value() / 3);
1221
1222 if (deg < 0)
1223 deg += 360;
1224 else if (deg > 360)
1225 deg -= 360;
1226
1227 putter->setDeg(deg);
1228 puttReverse = true;
1229 }
1230 else
1231 {
1232 strokeCircle->setValue(al);
1233 putterTimer->start(putterTimerMsec/10);
1234 }
1235 }
1236 }
1237 else
1238 {
1239 if (putting)
1240 {
1241 putter->go(Backwards);
1242 puttCount++;
1243 strength += 1.5;
1244 if (strength > maxStrength)
1245 {
1246 putting = false;
1247 stroking = true;
1248 }
1249 }
1250 else if (stroking)
1251 {
1252 if (putter->curLen() < (*curPlayer).ball()->height() + 2)
1253 {
1254 stroking = false;
1255 putterTimer->stop();
1256 putting = false;
1257 stroking = false;
1258 shotStart();
1259 }
1260
1261 putter->go(Forwards);
1262 putterTimer->start(putterTimerMsec/10);
1263 }
1264 }
1265}
1266
1267void KolfGame::autoSaveTimeout()
1268{
1269 // this should be a config option
1270 // until it is i'll disable it
1271 if (editing)
1272 {
1273 //save();
1274 }
1275}
1276
1277void KolfGame::recreateStateList()
1278{
1279 savedState.clear();
1280 foreach (QGraphicsItem* item, m_topLevelQItems)
1281 {
1282 if (dynamic_cast<Ball*>(item)) continue; //see below
1283 CanvasItem* citem = dynamic_cast<CanvasItem*>(item);
1284 if (citem)
1285 {
1286 const QString key = makeStateGroup(citem->curId(), citem->name());
1287 savedState.insert(key, item->pos());
1288 }
1289 }
1290
1291 ballStateList.clear();
1292 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
1293 ballStateList.append((*it).stateInfo(curHole));
1294
1295 ballStateList.canUndo = true;
1296}
1297
1298void KolfGame::undoShot()
1299{
1300 if (ballStateList.canUndo)
1301 loadStateList();
1302}
1303
1304void KolfGame::loadStateList()
1305{
1306 foreach (QGraphicsItem* item, m_topLevelQItems)
1307 {
1308 if (dynamic_cast<Ball*>(item)) continue; //see below
1309 CanvasItem* citem = dynamic_cast<CanvasItem*>(item);
1310 if (citem)
1311 {
1312 const QString key = makeStateGroup(citem->curId(), citem->name());
1313 const QPointF currentPos = item->pos();
1314 const QPointF posDiff = savedState.value(key, currentPos) - currentPos;
1315 citem->moveBy(posDiff.x(), posDiff.y());
1316 }
1317 }
1318
1319 for (BallStateList::Iterator it = ballStateList.begin(); it != ballStateList.end(); ++it)
1320 {
1321 BallStateInfo info = (*it);
1322 Player &player = (*(players->begin() + (info.id - 1) ));
1323 player.ball()->setPos(info.spot.x(), info.spot.y());
1324 player.ball()->setBeginningOfHole(info.beginningOfHole);
1325 if ((*curPlayer).id() == info.id)
1326 ballMoved();
1327 else
1328 player.ball()->setVisible(!info.beginningOfHole);
1329 player.setScoreForHole(info.score, curHole);
1330 player.ball()->setState(info.state);
1331 emit scoreChanged(info.id, curHole, info.score);
1332 }
1333}
1334
1335void KolfGame::shotDone()
1336{
1337 inPlay = false;
1338 emit inPlayEnd();
1339 setFocus();
1340
1341 Ball *ball = (*curPlayer).ball();
1342
1343 if (!dontAddStroke && (*curPlayer).numHoles())
1344 (*curPlayer).addStrokeToHole(curHole);
1345
1346 dontAddStroke = false;
1347
1348 // do hack stuff, shouldn't be done here
1349
1350 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
1351 {
1352 if ((*it).ball()->addStroke())
1353 {
1354 for (int i = 1; i <= (*it).ball()->addStroke(); ++i)
1355 (*it).addStrokeToHole(curHole);
1356
1357 // emit that we have a new stroke count
1358 emit scoreChanged((*it).id(), curHole, (*it).score(curHole));
1359 }
1360 (*it).ball()->setAddStroke(0);
1361 }
1362
1363 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
1364 {
1365 Ball *ball = (*it).ball();
1366
1367 if (ball->curState() == Holed)
1368 continue;
1369
1370 Vector oldVelocity;
1371 if (ball->placeOnGround(oldVelocity))
1372 {
1373 ball->setPlaceOnGround(false);
1374
1375 QStringList options;
1376 const QString placeOutside = i18n("Drop Outside of Hazard");
1377 const QString rehit = i18n("Rehit From Last Location");
1378 options << placeOutside << rehit;
1379 const QString choice = KComboBoxDialog::getItem(i18n("What would you like to do for your next shot?"), i18n("%1 is in a Hazard", (*it).name()), options, placeOutside, "hazardOptions");
1380
1381 if (choice == placeOutside)
1382 {
1383 (*it).ball()->setDoDetect(false);
1384
1385 QPointF pos = ball->pos();
1386 //normalize old velocity
1387 const QPointF v = oldVelocity / oldVelocity.magnitude();
1388
1389 while (1)
1390 {
1391 QList<QGraphicsItem *> list = ball->collidingItems();
1392 bool keepMoving = false;
1393 while (!list.isEmpty())
1394 {
1395 QGraphicsItem *item = list.takeFirst();
1396 if (item->data(0) == Rtti_DontPlaceOn)
1397 keepMoving = true;
1398 }
1399 if (!keepMoving)
1400 break;
1401
1402 const qreal movePixel = 3.0;
1403 pos -= v * movePixel;
1404 ball->setPos(pos);
1405 }
1406 }
1407 else if (choice == rehit)
1408 {
1409 for (BallStateList::Iterator it = ballStateList.begin(); it != ballStateList.end(); ++it)
1410 {
1411 if ((*it).id == (*curPlayer).id())
1412 {
1413 if ((*it).beginningOfHole)
1414 ball->setPos(whiteBall->x(), whiteBall->y());
1415 else
1416 ball->setPos((*it).spot.x(), (*it).spot.y());
1417
1418 break;
1419 }
1420 }
1421 }
1422
1423 ball->setVisible(true);
1424 ball->setState(Stopped);
1425
1426 (*it).ball()->setDoDetect(true);
1427 ball->collisionDetect();
1428 }
1429 }
1430
1431 // emit again
1432 emit scoreChanged((*curPlayer).id(), curHole, (*curPlayer).score(curHole));
1433
1434 if(ball->curState() == Rolling) {
1435 inPlay = true;
1436 return;
1437 }
1438
1439 ball->setVelocity(Vector());
1440
1441 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
1442 {
1443 Ball *ball = (*it).ball();
1444
1445 int curStrokes = (*it).score(curHole);
1446 if (curStrokes >= holeInfo.maxStrokes() && holeInfo.hasMaxStrokes())
1447 {
1448 ball->setState(Holed);
1449 ball->setVisible(false);
1450
1451 // move to center in case he/she hit out
1452 ball->setPos(width / 2, height / 2);
1453 playerWhoMaxed = (*it).name();
1454
1455 if (allPlayersDone())
1456 {
1457 startNextHole();
1458 QTimer::singleShot(100, this, SLOT(emitMax()));
1459 return;
1460 }
1461
1462 QTimer::singleShot(100, this, SLOT(emitMax()));
1463 }
1464 }
1465
1466 // change player to next player
1467 // skip player if he's Holed
1468 do
1469 {
1470 curPlayer++;
1471 if (curPlayer == players->end())
1472 curPlayer = players->begin();
1473 }
1474 while ((*curPlayer).ball()->curState() == Holed);
1475
1476 emit newPlayersTurn(&(*curPlayer));
1477
1478 (*curPlayer).ball()->setVisible(true);
1479
1480 inPlay = false;
1481 (*curPlayer).ball()->collisionDetect();
1482
1483 putter->setAngle((*curPlayer).ball());
1484 putter->setOrigin((*curPlayer).ball()->x(), (*curPlayer).ball()->y());
1485 updateMouse();
1486}
1487
1488void KolfGame::emitMax()
1489{
1490 emit maxStrokesReached(playerWhoMaxed);
1491}
1492
1493void KolfGame::startBall(const Vector &velocity)
1494{
1495 playSound("hit");
1496
1497 emit inPlayStart();
1498 putter->setVisible(false);
1499
1500 (*curPlayer).ball()->setState(Rolling);
1501 (*curPlayer).ball()->setVelocity(velocity);
1502 (*curPlayer).ball()->shotStarted();
1503
1504 foreach (QGraphicsItem* qitem, m_topLevelQItems)
1505 {
1506 CanvasItem *citem = dynamic_cast<CanvasItem *>(qitem);
1507 if (citem)
1508 citem->shotStarted();
1509 }
1510
1511 inPlay = true;
1512}
1513
1514void KolfGame::shotStart()
1515{
1516 // ensure we never hit the ball back into the hole which
1517 // can cause hole skippage
1518 if ((*curPlayer).ball()->curState() == Holed)
1519 return;
1520
1521 // save state
1522 recreateStateList();
1523
1524 putter->saveAngle((*curPlayer).ball());
1525 strength /= 8;
1526 if (!strength)
1527 strength = 1;
1528
1529 //kDebug(12007) << "Start started. BallX:" << (*curPlayer).ball()->x() << ", BallY:" << (*curPlayer).ball()->y() << ", Putter Angle:" << putter->curAngle() << ", Vector Strength: " << strength;
1530
1531 (*curPlayer).ball()->collisionDetect();
1532
1533 startBall(Vector::fromMagnitudeDirection(strength, -(putter->curAngle() + M_PI)));
1534
1535 addHoleInfo(ballStateList);
1536}
1537
1538void KolfGame::addHoleInfo(BallStateList &list)
1539{
1540 list.player = (*curPlayer).id();
1541 list.vector = (*curPlayer).ball()->velocity();
1542 list.hole = curHole;
1543}
1544
1545void KolfGame::sayWhosGoing()
1546{
1547 if (players->count() >= 2)
1548 {
1549 KMessageBox::information(this, i18n("%1 will start off.", (*curPlayer).name()), i18n("New Hole"), "newHole");
1550 }
1551}
1552
1553void KolfGame::holeDone()
1554{
1555 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
1556 (*it).ball()->setVisible(false);
1557 startNextHole();
1558 sayWhosGoing();
1559}
1560
1561// this function is WAY too smart for it's own good
1562// ie, bad design :-(
1563void KolfGame::startNextHole()
1564{
1565 setFocus();
1566
1567 bool reset = true;
1568 if (askSave(true))
1569 {
1570 if (allPlayersDone())
1571 {
1572 // we'll reload this hole, but not reset
1573 curHole--;
1574 reset = false;
1575 }
1576 else
1577 return;
1578 }
1579 else
1580 setModified(false);
1581
1582 pause();
1583
1584 dontAddStroke = false;
1585
1586 inPlay = false;
1587 timer->stop();
1588 putter->resetAngles();
1589
1590 int oldCurHole = curHole;
1591 curHole++;
1592 emit currentHole(curHole);
1593
1594 if (reset)
1595 {
1596 whiteBall->setPos(width/2, height/2);
1597 holeInfo.borderWallsChanged(true);
1598 }
1599
1600 int leastScore = INT_MAX;
1601
1602 // to get the first player to go first on every hole,
1603 // don't do the score stuff below
1604 curPlayer = players->begin();
1605
1606 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
1607 {
1608 if (curHole > 1)
1609 {
1610 bool ahead = false;
1611 if ((*it).lastScore() != 0)
1612 {
1613 if ((*it).lastScore() < leastScore)
1614 ahead = true;
1615 else if ((*it).lastScore() == leastScore)
1616 {
1617 for (int i = curHole - 1; i > 0; --i)
1618 {
1619 while(i > (*it).scores().size())
1620 i--;
1621
1622 const int thisScore = (*it).score(i);
1623 const int thatScore = (*curPlayer).score(i);
1624 if (thisScore < thatScore)
1625 {
1626 ahead = true;
1627 break;
1628 }
1629 else if (thisScore > thatScore)
1630 break;
1631 }
1632 }
1633 }
1634
1635 if (ahead)
1636 {
1637 curPlayer = it;
1638 leastScore = (*it).lastScore();
1639 }
1640 }
1641
1642 if (reset)
1643 (*it).ball()->setPos(width / 2, height / 2);
1644 else
1645 (*it).ball()->setPos(whiteBall->x(), whiteBall->y());
1646
1647 (*it).ball()->setState(Stopped);
1648
1649 // this gets set to false when the ball starts
1650 // to move by the Mr. Ball himself.
1651 (*it).ball()->setBeginningOfHole(true);
1652 if ((int)(*it).scores().count() < curHole)
1653 (*it).addHole();
1654 (*it).ball()->setVelocity(Vector());
1655 (*it).ball()->setVisible(false);
1656 }
1657
1658 emit newPlayersTurn(&(*curPlayer));
1659
1660 if (reset)
1661 openFile();
1662
1663 inPlay = false;
1664 timer->start(timerMsec);
1665
1666 if(size().width()!=400 || size().height()!=400) { //not default size, so resizing needed
1667 int setSize = qMin(size().width(), size().height());
1668 //resize needs to be called for setSize+1 first because otherwise it doesn't seem to get called (not sure why)
1669 QGraphicsView::resize(setSize+1, setSize+1);
1670 QGraphicsView::resize(setSize, setSize);
1671 }
1672
1673 // if (false) { we're done with the round! }
1674 if (oldCurHole != curHole)
1675 {
1676 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) {
1677 (*it).ball()->setPlaceOnGround(false);
1678 while( (*it).numHoles() < (unsigned)curHole)
1679 (*it).addHole();
1680 }
1681
1682 // here we have to make sure the scoreboard shows
1683 // all of the holes up until now;
1684
1685 for (; scoreboardHoles < curHole; ++scoreboardHoles)
1686 {
1687 cfgGroup = KConfigGroup(cfg->group(QString("%1-hole@-50,-50|0").arg(scoreboardHoles + 1)));
1688 emit newHole(cfgGroup.readEntry("par", 3));
1689 }
1690
1691 resetHoleScores();
1692 updateShowInfo();
1693
1694 // this is from shotDone()
1695 (*curPlayer).ball()->setVisible(true);
1696 putter->setOrigin((*curPlayer).ball()->x(), (*curPlayer).ball()->y());
1697 updateMouse();
1698
1699 ballStateList.canUndo = false;
1700
1701 (*curPlayer).ball()->collisionDetect();
1702 }
1703
1704 unPause();
1705}
1706
1707void KolfGame::showInfoDlg(bool addDontShowAgain)
1708{
1709 KMessageBox::information(parentWidget(),
1710 i18n("Course name: %1", holeInfo.name()) + QString("\n")
1711 + i18n("Created by %1", holeInfo.author()) + QString("\n")
1712 + i18n("%1 holes", highestHole),
1713 i18n("Course Information"),
1714 addDontShowAgain? holeInfo.name() + QString(" ") + holeInfo.author() : QString());
1715}
1716
1717void KolfGame::openFile()
1718{
1719 QList<QGraphicsItem*> newTopLevelQItems;
1720 foreach (QGraphicsItem* qitem, m_topLevelQItems)
1721 {
1722 if (dynamic_cast<Ball*>(qitem))
1723 {
1724 //do not delete balls
1725 newTopLevelQItems << qitem;
1726 continue;
1727 }
1728 CanvasItem *citem = dynamic_cast<CanvasItem *>(qitem);
1729 if (citem)
1730 {
1731 delete citem;
1732 }
1733 }
1734
1735 m_moveableQItems = m_topLevelQItems = newTopLevelQItems;
1736 selectedItem = 0;
1737
1738 // will tell basic course info
1739 // we do this here for the hell of it.
1740 // there is no fake id, by the way,
1741 // because it's old and when i added ids i forgot to change it.
1742 cfgGroup = KConfigGroup(cfg->group(QString("0-course@-50,-50")));
1743 holeInfo.setAuthor(cfgGroup.readEntry("author", holeInfo.author()));
1744 holeInfo.setName(cfgGroup.readEntry("Name", holeInfo.name()));
1745 holeInfo.setUntranslatedName(cfgGroup.readEntryUntranslated("Name", holeInfo.untranslatedName()));
1746 emit titleChanged(holeInfo.name());
1747
1748 cfgGroup = KConfigGroup(KSharedConfig::openConfig(filename), QString("%1-hole@-50,-50|0").arg(curHole));
1749 curPar = cfgGroup.readEntry("par", 3);
1750 holeInfo.setPar(curPar);
1751 holeInfo.borderWallsChanged(cfgGroup.readEntry("borderWalls", holeInfo.borderWalls()));
1752 holeInfo.setMaxStrokes(cfgGroup.readEntry("maxstrokes", 10));
1753
1754 QStringList missingPlugins;
1755
1756 // The "for" loop depends on the list of groups being in sorted order.
1757 QStringList groups = cfg->groupList();
1758 groups.sort();
1759
1760 int numItems = 0;
1761 int _highestHole = 0;
1762
1763 for (QStringList::const_iterator it = groups.constBegin(); it != groups.constEnd(); ++it)
1764 {
1765 // Format of group name is [<holeNum>-<name>@<x>,<y>|<id>]
1766 cfgGroup = KConfigGroup(cfg->group(*it));
1767
1768 const int len = (*it).length();
1769 const int dashIndex = (*it).indexOf("-");
1770 const int holeNum = (*it).left(dashIndex).toInt();
1771 if (holeNum > _highestHole)
1772 _highestHole = holeNum;
1773
1774 const int atIndex = (*it).indexOf("@");
1775 const QString name = (*it).mid(dashIndex + 1, atIndex - (dashIndex + 1));
1776
1777 if (holeNum != curHole)
1778 {
1779 // Break before reading all groups, if the highest hole
1780 // number is known and all items in curHole are done.
1781 if (numItems && !recalcHighestHole)
1782 break;
1783 continue;
1784 }
1785 numItems++;
1786
1787
1788 const int commaIndex = (*it).indexOf(",");
1789 const int pipeIndex = (*it).indexOf("|");
1790 const int x = (*it).mid(atIndex + 1, commaIndex - (atIndex + 1)).toInt();
1791 const int y = (*it).mid(commaIndex + 1, pipeIndex - (commaIndex + 1)).toInt();
1792
1793 // will tell where ball is
1794 if (name == "ball")
1795 {
1796 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
1797 (*it).ball()->setPos(x, y);
1798 whiteBall->setPos(x, y);
1799 continue;
1800 }
1801
1802 const int id = (*it).right(len - (pipeIndex + 1)).toInt();
1803
1804 QGraphicsItem* newItem = m_factory.createInstance(name, courseBoard, g_world);
1805 if (newItem)
1806 {
1807 m_topLevelQItems << newItem;
1808 m_moveableQItems << newItem;
1809 CanvasItem *sceneItem = dynamic_cast<CanvasItem *>(newItem);
1810
1811 if (!sceneItem)
1812 continue;
1813
1814 sceneItem->setId(id);
1815 sceneItem->setGame(this);
1816 sceneItem->editModeChanged(editing);
1817 sceneItem->setName(name);
1818 m_moveableQItems.append(sceneItem->moveableItems());
1819
1820 sceneItem->setPosition(QPointF(x, y));
1821 newItem->setVisible(true);
1822
1823 // make things actually show
1824 cfgGroup = KConfigGroup(cfg->group(makeGroup(id, curHole, sceneItem->name(), x, y)));
1825 sceneItem->load(&cfgGroup);
1826 }
1827 else if (name != "hole" && !missingPlugins.contains(name))
1828 missingPlugins.append(name);
1829
1830 }
1831
1832 if (!missingPlugins.empty())
1833 {
1834 KMessageBox::informationList(this, QString("<p>") + i18n("This hole uses the following plugins, which you do not have installed:") + QString("</p>"), missingPlugins, QString(), QString("%1 warning").arg(holeInfo.untranslatedName() + QString::number(curHole)));
1835 }
1836
1837 lastDelId = -1;
1838
1839 // if it's the first hole let's not
1840 if (!numItems && curHole > 1 && !addingNewHole && curHole >= _highestHole)
1841 {
1842 // we're done, let's quit
1843 curHole--;
1844 pause();
1845 emit holesDone();
1846
1847 // tidy things up
1848 setBorderWalls(false);
1849 clearHole();
1850 setModified(false);
1851 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
1852 (*it).ball()->setVisible(false);
1853
1854 return;
1855 }
1856
1857 // do it down here; if !hasFinalLoad, do it up there!
1858 //QGraphicsItem *qsceneItem = 0;
1859 QList<QGraphicsItem *>::const_iterator qsceneItem;
1860 QList<CanvasItem *> todo;
1861 QList<QGraphicsItem *> qtodo;
1862
1863 if (curHole > _highestHole)
1864 _highestHole = curHole;
1865
1866 if (recalcHighestHole)
1867 {
1868 highestHole = _highestHole;
1869 recalcHighestHole = false;
1870 emit largestHole(highestHole);
1871 }
1872
1873 if (curHole == 1 && !filename.isNull() && !infoShown)
1874 {
1875 // let's not now, because they see it when they choose course
1876 //showInfoDlg(true);
1877 infoShown = true;
1878 }
1879
1880 setModified(false);
1881}
1882
1883void KolfGame::addNewObject(const QString& identifier)
1884{
1885 QGraphicsItem *newItem = m_factory.createInstance(identifier, courseBoard, g_world);
1886
1887 m_topLevelQItems << newItem;
1888 m_moveableQItems << newItem;
1889 if(!newItem->isVisible())
1890 newItem->setVisible(true);
1891
1892 CanvasItem *sceneItem = dynamic_cast<CanvasItem *>(newItem);
1893 if (!sceneItem)
1894 return;
1895
1896 // we need to find a number that isn't taken
1897 int i = lastDelId > 0? lastDelId : m_topLevelQItems.count() - 30;
1898 if (i <= 0)
1899 i = 0;
1900
1901 for (;; ++i)
1902 {
1903 bool found = false;
1904 foreach (QGraphicsItem* qitem, m_topLevelQItems)
1905 {
1906 CanvasItem *citem = dynamic_cast<CanvasItem *>(qitem);
1907 if (citem)
1908 {
1909 if (citem->curId() == i)
1910 {
1911 found = true;
1912 break;
1913 }
1914 }
1915 }
1916
1917
1918 if (!found)
1919 break;
1920 }
1921 sceneItem->setId(i);
1922
1923 sceneItem->setGame(this);
1924
1925 foreach (QGraphicsItem* qitem, sceneItem->infoItems())
1926 qitem->setVisible(m_showInfo);
1927
1928 sceneItem->editModeChanged(editing);
1929
1930 sceneItem->setName(identifier);
1931 m_moveableQItems.append(sceneItem->moveableItems());
1932
1933 newItem->setPos(width/2 - 18, height / 2 - 18);
1934 sceneItem->moveBy(0, 0);
1935 sceneItem->setSize(newItem->boundingRect().size());
1936
1937 setModified(true);
1938}
1939
1940bool KolfGame::askSave(bool noMoreChances)
1941{
1942 if (!modified)
1943 // not cancel, don't save
1944 return false;
1945
1946 int result = KMessageBox::warningYesNoCancel(this, i18n("There are unsaved changes to current hole. Save them?"), i18n("Unsaved Changes"), KStandardGuiItem::save(), noMoreChances? KStandardGuiItem::discard() : KGuiItem(i18n("Save &Later")), KStandardGuiItem::cancel(), noMoreChances? "DiscardAsk" : "SaveAsk");
1947 switch (result)
1948 {
1949 case KMessageBox::Yes:
1950 save();
1951 // fallthrough
1952
1953 case KMessageBox::No:
1954 return false;
1955 break;
1956
1957 case KMessageBox::Cancel:
1958 return true;
1959 break;
1960
1961 default:
1962 break;
1963 }
1964
1965 return false;
1966}
1967
1968void KolfGame::addNewHole()
1969{
1970 if (askSave(true))
1971 return;
1972
1973 // either it's already false
1974 // because it was saved by askSave(),
1975 // or the user pressed the 'discard' button
1976 setModified(false);
1977
1978 // find highest hole num, and create new hole
1979 // now openFile makes highest hole for us
1980
1981 addingNewHole = true;
1982 curHole = highestHole;
1983 recalcHighestHole = true;
1984 startNextHole();
1985 addingNewHole = false;
1986 emit currentHole(curHole);
1987
1988 // make sure even the current player isn't showing
1989 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
1990 (*it).ball()->setVisible(false);
1991
1992 whiteBall->setVisible(editing);
1993 putter->setVisible(!editing);
1994 inPlay = false;
1995
1996 // add default objects
1997 foreach (const Kolf::ItemMetadata& metadata, m_factory.knownTypes())
1998 if (metadata.addOnNewHole)
1999 addNewObject(metadata.identifier);
2000
2001 save();
2002}
2003
2004// kantan deshou ;-)
2005void KolfGame::resetHole()
2006{
2007 if (askSave(true))
2008 return;
2009 setModified(false);
2010 curHole--;
2011 startNextHole();
2012 resetHoleScores();
2013}
2014
2015void KolfGame::resetHoleScores()
2016{
2017 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
2018 {
2019 (*it).resetScore(curHole);
2020 emit scoreChanged((*it).id(), curHole, 0);
2021 }
2022}
2023
2024void KolfGame::clearHole()
2025{
2026 QList<QGraphicsItem*> newTopLevelQItems;
2027 foreach (QGraphicsItem* qitem, m_topLevelQItems)
2028 {
2029 if (dynamic_cast<Ball*>(qitem))
2030 {
2031 //do not delete balls
2032 newTopLevelQItems << qitem;
2033 continue;
2034 }
2035 CanvasItem *citem = dynamic_cast<CanvasItem *>(qitem);
2036 if (citem)
2037 {
2038 delete citem;
2039 }
2040 }
2041
2042 m_moveableQItems = m_topLevelQItems = newTopLevelQItems;
2043 setSelectedItem(0);
2044
2045 // add default objects
2046 foreach (const Kolf::ItemMetadata& metadata, m_factory.knownTypes())
2047 if (metadata.addOnNewHole)
2048 addNewObject(metadata.identifier);
2049
2050 setModified(true);
2051}
2052
2053void KolfGame::switchHole(int hole)
2054{
2055 if (inPlay)
2056 return;
2057 if (hole < 1 || hole > highestHole)
2058 return;
2059
2060 bool wasEditing = editing;
2061 if (editing)
2062 toggleEditMode();
2063
2064 if (askSave(true))
2065 return;
2066 setModified(false);
2067
2068 curHole = hole;
2069 resetHole();
2070
2071 if (wasEditing)
2072 toggleEditMode();
2073}
2074
2075void KolfGame::switchHole(const QString &holestring)
2076{
2077 bool ok;
2078 int hole = holestring.toInt(&ok);
2079 if (!ok)
2080 return;
2081 switchHole(hole);
2082}
2083
2084void KolfGame::nextHole()
2085{
2086 switchHole(curHole + 1);
2087}
2088
2089void KolfGame::prevHole()
2090{
2091 switchHole(curHole - 1);
2092}
2093
2094void KolfGame::firstHole()
2095{
2096 switchHole(1);
2097}
2098
2099void KolfGame::lastHole()
2100{
2101 switchHole(highestHole);
2102}
2103
2104void KolfGame::randHole()
2105{
2106 int newHole = 1 + (int)((double)KRandom::random() * ((double)(highestHole - 1) / (double)RAND_MAX));
2107 switchHole(newHole);
2108}
2109
2110void KolfGame::save()
2111{
2112 if (filename.isNull())
2113 {
2114 QString newfilename = KFileDialog::getSaveFileName(KUrl("kfiledialog:///kourses"),
2115 "application/x-kourse", this, i18n("Pick Kolf Course to Save To"));
2116 if (newfilename.isNull())
2117 return;
2118
2119 setFilename(newfilename);
2120 }
2121
2122 emit parChanged(curHole, holeInfo.par());
2123 emit titleChanged(holeInfo.name());
2124
2125 const QStringList groups = cfg->groupList();
2126
2127 // wipe out all groups from this hole
2128 for (QStringList::const_iterator it = groups.begin(); it != groups.end(); ++it)
2129 {
2130 int holeNum = (*it).left((*it).indexOf("-")).toInt();
2131 if (holeNum == curHole)
2132 cfg->deleteGroup(*it);
2133 }
2134 foreach (QGraphicsItem* qitem, m_topLevelQItems)
2135 {
2136 CanvasItem *citem = dynamic_cast<CanvasItem *>(qitem);
2137 if (citem)
2138 {
2139 cfgGroup = KConfigGroup(cfg->group(makeGroup(citem->curId(), curHole, citem->name(), (int)qitem->x(), (int)qitem->y())));
2140 citem->save(&cfgGroup);
2141 }
2142 }
2143
2144 // save where ball starts (whiteBall tells all)
2145 cfgGroup = KConfigGroup(cfg->group(QString("%1-ball@%2,%3").arg(curHole).arg((int)whiteBall->x()).arg((int)whiteBall->y())));
2146 cfgGroup.writeEntry("dummykey", true);
2147
2148 cfgGroup = KConfigGroup(cfg->group(QString("0-course@-50,-50")));
2149 cfgGroup.writeEntry("author", holeInfo.author());
2150 cfgGroup.writeEntry("Name", holeInfo.untranslatedName());
2151
2152 // save hole info
2153 cfgGroup = KConfigGroup(cfg->group(QString("%1-hole@-50,-50|0").arg(curHole)));
2154 cfgGroup.writeEntry("par", holeInfo.par());
2155 cfgGroup.writeEntry("maxstrokes", holeInfo.maxStrokes());
2156 cfgGroup.writeEntry("borderWalls", holeInfo.borderWalls());
2157
2158 cfg->sync();
2159
2160 setModified(false);
2161}
2162
2163void KolfGame::toggleEditMode()
2164{
2165 // won't be editing anymore, and user wants to cancel, we return
2166 // this is pretty useless. when the person leaves the hole,
2167 // he gets asked again
2168 /*
2169 if (editing && modified)
2170 {
2171 if (askSave(false))
2172 {
2173 emit checkEditing();
2174 return;
2175 }
2176 }
2177 */
2178
2179 selectedItem = 0;
2180
2181 editing = !editing;
2182
2183 if (editing)
2184 {
2185 emit editingStarted();
2186 setSelectedItem(0);
2187 }
2188 else
2189 {
2190 emit editingEnded();
2191 setCursor(Qt::ArrowCursor);
2192 }
2193
2194 // alert our items
2195 foreach (QGraphicsItem* qitem, m_topLevelQItems)
2196 {
2197 if (dynamic_cast<Ball*>(qitem)) continue;
2198 CanvasItem *citem = dynamic_cast<CanvasItem *>(qitem);
2199 if (citem)
2200 citem->editModeChanged(editing);
2201 }
2202
2203 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
2204 {
2205 // curplayer shouldn't be hidden no matter what
2206 if ((*it).ball()->beginningOfHole() && it != curPlayer)
2207 (*it).ball()->setVisible(false);
2208 else
2209 (*it).ball()->setVisible(!editing);
2210 }
2211
2212 whiteBall->setVisible(editing);
2213 whiteBall->editModeChanged(editing);
2214
2215 // shouldn't see putter whilst editing
2216 putter->setVisible(!editing);
2217
2218 if (editing)
2219 autoSaveTimer->start(autoSaveMsec);
2220 else
2221 autoSaveTimer->stop();
2222
2223 inPlay = false;
2224}
2225
2226void KolfGame::setSelectedItem(CanvasItem* citem)
2227{
2228 QGraphicsItem* qitem = dynamic_cast<QGraphicsItem*>(citem);
2229 selectedItem = qitem;
2230 emit newSelectedItem(qitem ? citem : &holeInfo);
2231 //deactivate all other overlays
2232 foreach (QGraphicsItem* otherQitem, m_topLevelQItems)
2233 {
2234 CanvasItem* otherCitem = dynamic_cast<CanvasItem*>(otherQitem);
2235 if (otherCitem && otherCitem != citem)
2236 {
2237 //false = do not create overlay if it does not exist yet
2238 Kolf::Overlay* otherOverlay = otherCitem->overlay(false);
2239 if (otherOverlay)
2240 otherOverlay->setState(Kolf::Overlay::Passive);
2241 }
2242 }
2243}
2244
2245#ifdef SOUND
2246void KolfGame::playSound(const QString& file, float vol)
2247{
2248 if (m_sound)
2249 {
2250 QString resFile = soundDir + file + QString::fromLatin1(".wav");
2251
2252 // not needed when all of the files are in the distribution
2253 //if (!QFile::exists(resFile))
2254 //return;
2255 if (vol > 1)
2256 vol = 1;
2257 m_player->setCurrentSource(resFile);
2258 m_player->play();
2259 }
2260}
2261#else //SOUND
2262void KolfGame::playSound( const QString&, float )
2263{
2264}
2265#endif //SOUND
2266
2267void HoleInfo::borderWallsChanged(bool yes)
2268{
2269 m_borderWalls = yes;
2270 game->setBorderWalls(yes);
2271}
2272
2273bool KolfGame::allPlayersDone()
2274{
2275 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
2276 if ((*it).ball()->curState() != Holed)
2277 return false;
2278
2279 return true;
2280}
2281
2282void KolfGame::setBorderWalls(bool showing)
2283{
2284 foreach (Kolf::Wall* wall, borderWalls)
2285 wall->setVisible(showing);
2286}
2287
2288void KolfGame::setUseAdvancedPutting(bool yes)
2289{
2290 m_useAdvancedPutting = yes;
2291
2292 // increase maxStrength in advanced putting mode
2293 if (yes)
2294 maxStrength = 65;
2295 else
2296 maxStrength = 55;
2297}
2298
2299void KolfGame::setShowGuideLine(bool yes)
2300{
2301 putter->setShowGuideLine(yes);
2302}
2303
2304void KolfGame::setSound(bool yes)
2305{
2306 m_sound = yes;
2307}
2308
2309void KolfGame::courseInfo(CourseInfo &info, const QString& filename)
2310{
2311 KConfig config(filename);
2312 KConfigGroup configGroup (config.group(QString("0-course@-50,-50")));
2313 info.author = configGroup.readEntry("author", info.author);
2314 info.name = configGroup.readEntry("Name", configGroup.readEntry("name", info.name));
2315 info.untranslatedName = configGroup.readEntryUntranslated("Name", configGroup.readEntryUntranslated("name", info.name));
2316
2317 unsigned int hole = 1;
2318 unsigned int par= 0;
2319 while (1)
2320 {
2321 QString group = QString("%1-hole@-50,-50|0").arg(hole);
2322 if (!config.hasGroup(group))
2323 {
2324 hole--;
2325 break;
2326 }
2327
2328 configGroup = KConfigGroup(config.group(group));
2329 par += configGroup.readEntry("par", 3);
2330
2331 hole++;
2332 }
2333
2334 info.par = par;
2335 info.holes = hole;
2336}
2337
2338void KolfGame::scoresFromSaved(KConfig *config, PlayerList &players)
2339{
2340 KConfigGroup configGroup(config->group(QString("0 Saved Game")));
2341 int numPlayers = configGroup.readEntry("Players", 0);
2342 if (numPlayers <= 0)
2343 return;
2344
2345 for (int i = 1; i <= numPlayers; ++i)
2346 {
2347 // this is same as in kolf.cpp, but we use saved game values
2348 configGroup = KConfigGroup(config->group(QString::number(i)));
2349 players.append(Player());
2350 players.last().ball()->setColor(configGroup.readEntry("Color", "#ffffff"));
2351 players.last().setName(configGroup.readEntry("Name"));
2352 players.last().setId(i);
2353
2354 const QStringList scores(configGroup.readEntry("Scores",QStringList()));
2355 QList<int> intscores;
2356 for (QStringList::const_iterator it = scores.begin(); it != scores.end(); ++it)
2357 intscores.append((*it).toInt());
2358
2359 players.last().setScores(intscores);
2360 }
2361}
2362
2363void KolfGame::saveScores(KConfig *config)
2364{
2365 // wipe out old player info
2366 const QStringList groups = config->groupList();
2367 for (QStringList::const_iterator it = groups.begin(); it != groups.end(); ++it)
2368 {
2369 // this deletes all int groups, ie, the player info groups
2370 bool ok = false;
2371 (*it).toInt(&ok);
2372 if (ok)
2373 config->deleteGroup(*it);
2374 }
2375
2376 KConfigGroup configGroup(config->group(QString("0 Saved Game")));
2377 configGroup.writeEntry("Players", players->count());
2378 configGroup.writeEntry("Course", filename);
2379 configGroup.writeEntry("Current Hole", curHole);
2380
2381 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
2382 {
2383 KConfigGroup configGroup(config->group(QString::number((*it).id())));
2384 configGroup.writeEntry("Name", (*it).name());
2385 configGroup.writeEntry("Color", (*it).ball()->color().name());
2386
2387 QStringList scores;
2388 QList<int> intscores = (*it).scores();
2389 for (QList<int>::Iterator it = intscores.begin(); it != intscores.end(); ++it)
2390 scores.append(QString::number(*it));
2391
2392 configGroup.writeEntry("Scores", scores);
2393 }
2394}
2395
2396CourseInfo::CourseInfo()
2397 : name(i18n("Course Name")), author(i18n("Course Author")), holes(0), par(0)
2398{
2399}
2400
2401#include "game.moc"
2402