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 | |
48 | inline 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 | |
53 | inline QString makeStateGroup(int id, const QString &name) |
54 | { |
55 | return QString("%1|%2" ).arg(name).arg(id); |
56 | } |
57 | |
58 | class 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 | |
71 | class 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 | |
83 | class 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 | |
92 | class 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 | |
102 | K_GLOBAL_STATIC(KolfRenderer, g_renderer) |
103 | K_GLOBAL_STATIC(KolfWorld, g_world) |
104 | |
105 | KGameRenderer* Kolf::renderer() |
106 | { |
107 | return g_renderer; |
108 | } |
109 | |
110 | Tagaro::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 | |
116 | b2World* Kolf::world() |
117 | { |
118 | return g_world; |
119 | } |
120 | |
121 | ///////////////////////// |
122 | |
123 | Putter::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 | |
148 | void Putter::showInfo() |
149 | { |
150 | guideLine->setVisible(isVisible()); |
151 | } |
152 | |
153 | void Putter::hideInfo() |
154 | { |
155 | guideLine->setVisible(m_showGuideLine? isVisible() : false); |
156 | } |
157 | |
158 | void Putter::moveBy(double dx, double dy) |
159 | { |
160 | QGraphicsLineItem::moveBy(dx, dy); |
161 | guideLine->setPos(x(), y()); |
162 | CanvasItem::moveBy(dx, dy); |
163 | } |
164 | |
165 | void Putter::setShowGuideLine(bool yes) |
166 | { |
167 | m_showGuideLine = yes; |
168 | setVisible(isVisible()); |
169 | } |
170 | |
171 | void Putter::setVisible(bool yes) |
172 | { |
173 | QGraphicsLineItem::setVisible(yes); |
174 | guideLine->setVisible(m_showGuideLine? yes : false); |
175 | } |
176 | |
177 | void Putter::setOrigin(double _x, double _y) |
178 | { |
179 | setVisible(true); |
180 | setPos(_x, _y); |
181 | guideLineLength = 9; //reset to default |
182 | finishMe(); |
183 | } |
184 | |
185 | void Putter::setAngle(Ball *ball) |
186 | { |
187 | angle = angleMap.contains(ball)? angleMap[ball] : 0; |
188 | finishMe(); |
189 | } |
190 | |
191 | void 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 | |
220 | void 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 | |
250 | HoleConfig::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 | |
306 | void HoleConfig::authorChanged(const QString &newauthor) |
307 | { |
308 | holeInfo->setAuthor(newauthor); |
309 | changed(); |
310 | } |
311 | |
312 | void HoleConfig::nameChanged(const QString &newname) |
313 | { |
314 | holeInfo->setName(newname); |
315 | holeInfo->setUntranslatedName(newname); |
316 | changed(); |
317 | } |
318 | |
319 | void HoleConfig::parChanged(int newpar) |
320 | { |
321 | holeInfo->setPar(newpar); |
322 | changed(); |
323 | } |
324 | |
325 | void HoleConfig::maxStrokesChanged(int newms) |
326 | { |
327 | holeInfo->setMaxStrokes(newms); |
328 | changed(); |
329 | } |
330 | |
331 | void HoleConfig::borderWallsChanged(bool yes) |
332 | { |
333 | holeInfo->borderWallsChanged(yes); |
334 | changed(); |
335 | } |
336 | |
337 | ///////////////////////// |
338 | |
339 | StrokeCircle::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 | |
353 | void StrokeCircle::setValue(double v) |
354 | { |
355 | dvalue = v; |
356 | if (dvalue > dmax) |
357 | dvalue = dmax; |
358 | |
359 | update(); |
360 | } |
361 | |
362 | double StrokeCircle::value() |
363 | { |
364 | return dvalue; |
365 | } |
366 | |
367 | bool StrokeCircle::collidesWithItem(const QGraphicsItem*, Qt::ItemSelectionMode) const { return false; } |
368 | |
369 | QRectF StrokeCircle::boundingRect() const { return QRectF(x(), y(), iwidth, iheight); } |
370 | |
371 | void StrokeCircle::setMaxValue(double m) |
372 | { |
373 | dmax = m; |
374 | if (dvalue > dmax) |
375 | dvalue = dmax; |
376 | } |
377 | void 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 | } |
384 | void StrokeCircle::setThickness(double t) |
385 | { |
386 | if (t > 0) |
387 | ithickness = t; |
388 | } |
389 | |
390 | double StrokeCircle::thickness() const |
391 | { |
392 | return ithickness; |
393 | } |
394 | |
395 | double StrokeCircle::width() const |
396 | { |
397 | return iwidth; |
398 | } |
399 | |
400 | double StrokeCircle::height() const |
401 | { |
402 | return iheight; |
403 | } |
404 | |
405 | void 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 | |
446 | KolfGame::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 | |
604 | void 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 | |
630 | void KolfGame::setFilename(const QString &filename) |
631 | { |
632 | this->filename = filename; |
633 | delete cfg; |
634 | cfg = new KConfig(filename, KConfig::NoGlobals); |
635 | } |
636 | |
637 | KolfGame::~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 | |
652 | void KolfGame::setModified(bool mod) |
653 | { |
654 | modified = mod; |
655 | emit modifiedChanged(mod); |
656 | } |
657 | |
658 | void 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 | |
673 | void 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 | |
687 | void 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 | |
699 | void KolfGame::handleMouseDoubleClickEvent(QMouseEvent *e) |
700 | { |
701 | // allow two fast single clicks |
702 | handleMousePressEvent(e); |
703 | } |
704 | |
705 | void 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 | |
732 | QPoint 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 | |
741 | void 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 | |
753 | void 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 | |
765 | void 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 | |
777 | void 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 | |
789 | void 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 | |
799 | void 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 | |
810 | void 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 | |
833 | void 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 | |
870 | void KolfGame::toggleShowInfo() |
871 | { |
872 | setShowInfo(!m_showInfo); |
873 | } |
874 | |
875 | void KolfGame::updateShowInfo() |
876 | { |
877 | setShowInfo(m_showInfo); |
878 | } |
879 | |
880 | void 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 | |
894 | void 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 | |
938 | void 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 | |
970 | void 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 | |
986 | void KolfGame::puttRelease() |
987 | { |
988 | if (!m_useAdvancedPutting && putting && !editing) |
989 | { |
990 | putting = false; |
991 | stroking = true; |
992 | } |
993 | } |
994 | |
995 | void KolfGame::stoppedBall() |
996 | { |
997 | if (!inPlay) |
998 | { |
999 | inPlay = true; |
1000 | dontAddStroke = true; |
1001 | } |
1002 | } |
1003 | |
1004 | void 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 | |
1082 | void 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 | |
1129 | void KolfGame::ballMoved() |
1130 | { |
1131 | if (putter->isVisible()) |
1132 | { |
1133 | putter->setPos((*curPlayer).ball()->x(), (*curPlayer).ball()->y()); |
1134 | updateMouse(); |
1135 | } |
1136 | } |
1137 | |
1138 | void 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 | |
1267 | void 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 | |
1277 | void 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 | |
1298 | void KolfGame::undoShot() |
1299 | { |
1300 | if (ballStateList.canUndo) |
1301 | loadStateList(); |
1302 | } |
1303 | |
1304 | void 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 | |
1335 | void 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 | |
1488 | void KolfGame::emitMax() |
1489 | { |
1490 | emit maxStrokesReached(playerWhoMaxed); |
1491 | } |
1492 | |
1493 | void 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 | |
1514 | void 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 | |
1538 | void KolfGame::addHoleInfo(BallStateList &list) |
1539 | { |
1540 | list.player = (*curPlayer).id(); |
1541 | list.vector = (*curPlayer).ball()->velocity(); |
1542 | list.hole = curHole; |
1543 | } |
1544 | |
1545 | void 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 | |
1553 | void 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 :-( |
1563 | void 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 | |
1707 | void 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 | |
1717 | void 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 | |
1883 | void 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 | |
1940 | bool 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 | |
1968 | void 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 ;-) |
2005 | void KolfGame::resetHole() |
2006 | { |
2007 | if (askSave(true)) |
2008 | return; |
2009 | setModified(false); |
2010 | curHole--; |
2011 | startNextHole(); |
2012 | resetHoleScores(); |
2013 | } |
2014 | |
2015 | void 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 | |
2024 | void 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 | |
2053 | void 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 | |
2075 | void 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 | |
2084 | void KolfGame::nextHole() |
2085 | { |
2086 | switchHole(curHole + 1); |
2087 | } |
2088 | |
2089 | void KolfGame::prevHole() |
2090 | { |
2091 | switchHole(curHole - 1); |
2092 | } |
2093 | |
2094 | void KolfGame::firstHole() |
2095 | { |
2096 | switchHole(1); |
2097 | } |
2098 | |
2099 | void KolfGame::lastHole() |
2100 | { |
2101 | switchHole(highestHole); |
2102 | } |
2103 | |
2104 | void KolfGame::randHole() |
2105 | { |
2106 | int newHole = 1 + (int)((double)KRandom::random() * ((double)(highestHole - 1) / (double)RAND_MAX)); |
2107 | switchHole(newHole); |
2108 | } |
2109 | |
2110 | void 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 | |
2163 | void 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 | |
2226 | void 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 |
2246 | void 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 |
2262 | void KolfGame::playSound( const QString&, float ) |
2263 | { |
2264 | } |
2265 | #endif //SOUND |
2266 | |
2267 | void HoleInfo::borderWallsChanged(bool yes) |
2268 | { |
2269 | m_borderWalls = yes; |
2270 | game->setBorderWalls(yes); |
2271 | } |
2272 | |
2273 | bool 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 | |
2282 | void KolfGame::setBorderWalls(bool showing) |
2283 | { |
2284 | foreach (Kolf::Wall* wall, borderWalls) |
2285 | wall->setVisible(showing); |
2286 | } |
2287 | |
2288 | void 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 | |
2299 | void KolfGame::setShowGuideLine(bool yes) |
2300 | { |
2301 | putter->setShowGuideLine(yes); |
2302 | } |
2303 | |
2304 | void KolfGame::setSound(bool yes) |
2305 | { |
2306 | m_sound = yes; |
2307 | } |
2308 | |
2309 | void 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 | |
2338 | void 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 | |
2363 | void 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 | |
2396 | CourseInfo::CourseInfo() |
2397 | : name(i18n("Course Name" )), author(i18n("Course Author" )), holes(0), par(0) |
2398 | { |
2399 | } |
2400 | |
2401 | #include "game.moc" |
2402 | |