File indexing completed on 2025-02-16 06:57:49
0001 /* 0002 Copyright (C) 2002-2005, Jason Katz-Brown <jasonkb@mit.edu> 0003 Copyright 2010 Stefan Majewsky <majewsky@gmx.net> 0004 0005 This program is free software; you can redistribute it and/or modify 0006 it under the terms of the GNU General Public License as published by 0007 the Free Software Foundation; either version 2 of the License, or 0008 (at your option) any later version. 0009 0010 This program is distributed in the hope that it will be useful, 0011 but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 GNU General Public License for more details. 0014 0015 You should have received a copy of the GNU General Public License 0016 along with this program; if not, write to the Free Software 0017 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 0018 */ 0019 0020 #include "game.h" 0021 #include "itemfactory.h" 0022 #include "kcomboboxdialog.h" 0023 #include "obstacles.h" 0024 #include "shape.h" 0025 0026 #include "tagaro/board.h" 0027 0028 #include <QHBoxLayout> 0029 #include <QApplication> 0030 #include <QCheckBox> 0031 #include <QFileDialog> 0032 #include <QLabel> 0033 #include <QMouseEvent> 0034 #include <QSpinBox> 0035 #include <QStandardPaths> 0036 #include <QTimer> 0037 #include <QUrl> 0038 #include <QRandomGenerator> 0039 #include <KGameRenderer> 0040 0041 #include <KLineEdit> 0042 #include <KLocalizedString> 0043 #include <KMessageBox> 0044 0045 #include <KGameTheme> 0046 #include <Box2D/Dynamics/b2Body.h> 0047 #include <Box2D/Dynamics/Contacts/b2Contact.h> 0048 #include <Box2D/Dynamics/b2Fixture.h> 0049 #include <Box2D/Dynamics/b2World.h> 0050 #include <Box2D/Dynamics/b2WorldCallbacks.h> 0051 0052 inline QString makeGroup(int id, int hole, const QString &name, int x, int y) 0053 { 0054 return QStringLiteral("%1-%2@%3,%4|%5").arg(hole).arg(name).arg(x).arg(y).arg(id); 0055 } 0056 0057 inline QString makeStateGroup(int id, const QString &name) 0058 { 0059 return QStringLiteral("%1|%2").arg(name).arg(id); 0060 } 0061 0062 class KolfContactListener : public b2ContactListener 0063 { 0064 public: 0065 void PreSolve(b2Contact* contact, const b2Manifold* oldManifold) override 0066 { 0067 Q_UNUSED(oldManifold) 0068 CanvasItem* citemA = static_cast<CanvasItem*>(contact->GetFixtureA()->GetBody()->GetUserData()); 0069 CanvasItem* citemB = static_cast<CanvasItem*>(contact->GetFixtureB()->GetBody()->GetUserData()); 0070 if (!CanvasItem::mayCollide(citemA, citemB)) 0071 contact->SetEnabled(false); 0072 } 0073 }; 0074 0075 class KolfWorld : public b2World 0076 { 0077 public: 0078 KolfWorld() 0079 : b2World(b2Vec2(0, 0), true) //parameters: no gravity, objects are allowed to sleep 0080 { 0081 SetContactListener(&m_listener); 0082 } 0083 private: 0084 KolfContactListener m_listener; 0085 }; 0086 0087 class KolfTheme : public KGameTheme 0088 { 0089 public: 0090 KolfTheme() : KGameTheme("pics/default_theme.desktop") 0091 { 0092 setGraphicsPath(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("pics/default_theme.svgz"))); 0093 } 0094 }; 0095 0096 class KolfRenderer : public KGameRenderer 0097 { 0098 public: 0099 KolfRenderer() : KGameRenderer(new KolfTheme) 0100 { 0101 setStrategyEnabled(KGameRenderer::UseDiskCache, false); 0102 setStrategyEnabled(KGameRenderer::UseRenderingThreads, false); 0103 } 0104 }; 0105 0106 Q_GLOBAL_STATIC(KolfRenderer, g_renderer) 0107 Q_GLOBAL_STATIC(KolfWorld, g_world) 0108 0109 KGameRenderer* Kolf::renderer() 0110 { 0111 return g_renderer; 0112 } 0113 0114 Tagaro::Board* Kolf::findBoard(QGraphicsItem* item_) 0115 { 0116 //This returns the toplevel board instance in which the given parent resides. 0117 return item_ ? dynamic_cast<Tagaro::Board*>(item_->topLevelItem()) : nullptr; 0118 } 0119 0120 b2World* Kolf::world() 0121 { 0122 return g_world; 0123 } 0124 0125 ///////////////////////// 0126 0127 Putter::Putter(QGraphicsItem* parent, b2World* world) 0128 : QGraphicsLineItem(parent) 0129 , CanvasItem(world) 0130 { 0131 setData(0, Rtti_Putter); 0132 setZBehavior(CanvasItem::FixedZValue, 10001); 0133 m_showGuideLine = true; 0134 oneDegree = M_PI / 180; 0135 guideLineLength = 9; 0136 putterWidth = 11; 0137 angle = 0; 0138 0139 guideLine = new QGraphicsLineItem(this); 0140 guideLine->setPen(QPen(Qt::white)); 0141 guideLine->setZValue(998.8); 0142 0143 setPen(QPen(Qt::black, 4)); 0144 maxAngle = 2 * M_PI; 0145 0146 hideInfo(); 0147 0148 // this also sets Z 0149 resetAngles(); 0150 } 0151 0152 void Putter::showInfo() 0153 { 0154 guideLine->setVisible(isVisible()); 0155 } 0156 0157 void Putter::hideInfo() 0158 { 0159 guideLine->setVisible(m_showGuideLine? isVisible() : false); 0160 } 0161 0162 void Putter::moveBy(double dx, double dy) 0163 { 0164 QGraphicsLineItem::moveBy(dx, dy); 0165 guideLine->setPos(x(), y()); 0166 CanvasItem::moveBy(dx, dy); 0167 } 0168 0169 void Putter::setShowGuideLine(bool yes) 0170 { 0171 m_showGuideLine = yes; 0172 setVisible(isVisible()); 0173 } 0174 0175 void Putter::setVisible(bool yes) 0176 { 0177 QGraphicsLineItem::setVisible(yes); 0178 guideLine->setVisible(m_showGuideLine? yes : false); 0179 } 0180 0181 void Putter::setOrigin(double _x, double _y) 0182 { 0183 setVisible(true); 0184 setPos(_x, _y); 0185 guideLineLength = 9; //reset to default 0186 finishMe(); 0187 } 0188 0189 void Putter::setAngle(Ball *ball) 0190 { 0191 angle = angleMap.contains(ball)? angleMap[ball] : 0; 0192 finishMe(); 0193 } 0194 0195 void Putter::go(Direction d, Amount amount) 0196 { 0197 double addition = (amount == Amount_More? 6 * oneDegree : amount == Amount_Less? .5 * oneDegree : 2 * oneDegree); 0198 0199 switch (d) 0200 { 0201 case Forwards: 0202 guideLineLength -= 1; 0203 guideLine->setVisible(false); 0204 break; 0205 case Backwards: 0206 guideLineLength += 1; 0207 guideLine->setVisible(false); 0208 break; 0209 case D_Left: 0210 angle += addition; 0211 if (angle > maxAngle) 0212 angle -= maxAngle; 0213 break; 0214 case D_Right: 0215 angle -= addition; 0216 if (angle < 0) 0217 angle = maxAngle - fabs(angle); 0218 break; 0219 } 0220 0221 finishMe(); 0222 } 0223 0224 void Putter::finishMe() 0225 { 0226 midPoint.setX(cos(angle) * guideLineLength); 0227 midPoint.setY(-sin(angle) * guideLineLength); 0228 0229 QPointF start; 0230 QPointF end; 0231 0232 if (midPoint.y() || !midPoint.x()) 0233 { 0234 start.setX(midPoint.x() - putterWidth * sin(angle)); 0235 start.setY(midPoint.y() - putterWidth * cos(angle)); 0236 end.setX(midPoint.x() + putterWidth * sin(angle)); 0237 end.setY(midPoint.y() + putterWidth * cos(angle)); 0238 } 0239 else 0240 { 0241 start.setX(midPoint.x()); 0242 start.setY(midPoint.y() + putterWidth); 0243 end.setY(midPoint.y() - putterWidth); 0244 end.setX(midPoint.x()); 0245 } 0246 0247 guideLine->setLine(midPoint.x(), midPoint.y(), -cos(angle) * guideLineLength * 4, sin(angle) * guideLineLength * 4); 0248 0249 setLine(start.x(), start.y(), end.x(), end.y()); 0250 } 0251 0252 ///////////////////////// 0253 0254 HoleConfig::HoleConfig(HoleInfo *holeInfo, QWidget *parent) 0255 : Config(parent) 0256 { 0257 this->holeInfo = holeInfo; 0258 0259 QVBoxLayout *layout = new QVBoxLayout(this); 0260 0261 QHBoxLayout *hlayout = new QHBoxLayout; 0262 layout->addLayout( hlayout ); 0263 hlayout->addWidget(new QLabel(i18n("Course name: "), this)); 0264 KLineEdit *nameEdit = new KLineEdit(holeInfo->untranslatedName(), this); 0265 hlayout->addWidget(nameEdit); 0266 connect(nameEdit, &KLineEdit::textChanged, this, &HoleConfig::nameChanged); 0267 0268 hlayout = new QHBoxLayout; 0269 layout->addLayout( hlayout ); 0270 hlayout->addWidget(new QLabel(i18n("Course author: "), this)); 0271 KLineEdit *authorEdit = new KLineEdit(holeInfo->author(), this); 0272 hlayout->addWidget(authorEdit); 0273 connect(authorEdit, &KLineEdit::textChanged, this, &HoleConfig::authorChanged); 0274 0275 layout->addStretch(); 0276 0277 hlayout = new QHBoxLayout; 0278 layout->addLayout( hlayout ); 0279 hlayout->addWidget(new QLabel(i18n("Par:"), this)); 0280 QSpinBox *par = new QSpinBox(this); 0281 par->setRange( 1, 15 ); 0282 par->setSingleStep( 1 ); 0283 par->setValue(holeInfo->par()); 0284 hlayout->addWidget(par); 0285 connect(par, &QSpinBox::valueChanged, this, 0286 &HoleConfig::parChanged); 0287 hlayout->addStretch(); 0288 0289 hlayout->addWidget(new QLabel(i18n("Maximum:"), this)); 0290 QSpinBox *maxstrokes = new QSpinBox(this); 0291 maxstrokes->setRange( holeInfo->lowestMaxStrokes(), 30 ); 0292 maxstrokes->setSingleStep( 1 ); 0293 maxstrokes->setWhatsThis( i18n("Maximum number of strokes player can take on this hole.")); 0294 maxstrokes->setToolTip( i18n("Maximum number of strokes")); 0295 maxstrokes->setSpecialValueText(i18n("Unlimited")); 0296 maxstrokes->setValue(holeInfo->maxStrokes()); 0297 hlayout->addWidget(maxstrokes); 0298 connect(maxstrokes, &QSpinBox::valueChanged, this, 0299 &HoleConfig::maxStrokesChanged); 0300 0301 QCheckBox *check = new QCheckBox(i18n("Show border walls"), this); 0302 check->setChecked(holeInfo->borderWalls()); 0303 layout->addWidget(check); 0304 connect(check, &QCheckBox::toggled, this, &HoleConfig::borderWallsChanged); 0305 } 0306 0307 void HoleConfig::authorChanged(const QString &newauthor) 0308 { 0309 holeInfo->setAuthor(newauthor); 0310 changed(); 0311 } 0312 0313 void HoleConfig::nameChanged(const QString &newname) 0314 { 0315 holeInfo->setName(newname); 0316 holeInfo->setUntranslatedName(newname); 0317 changed(); 0318 } 0319 0320 void HoleConfig::parChanged(int newpar) 0321 { 0322 holeInfo->setPar(newpar); 0323 changed(); 0324 } 0325 0326 void HoleConfig::maxStrokesChanged(int newms) 0327 { 0328 holeInfo->setMaxStrokes(newms); 0329 changed(); 0330 } 0331 0332 void HoleConfig::borderWallsChanged(bool yes) 0333 { 0334 holeInfo->borderWallsChanged(yes); 0335 changed(); 0336 } 0337 0338 ///////////////////////// 0339 0340 StrokeCircle::StrokeCircle(QGraphicsItem *parent) 0341 : QGraphicsItem(parent) 0342 { 0343 dvalue = 0; 0344 dmax = 360; 0345 iwidth = 100; 0346 iheight = 100; 0347 ithickness = 8; 0348 setZValue(10000); 0349 0350 setSize(QSizeF(80, 80)); 0351 setThickness(8); 0352 } 0353 0354 void StrokeCircle::setValue(double v) 0355 { 0356 dvalue = v; 0357 if (dvalue > dmax) 0358 dvalue = dmax; 0359 0360 update(); 0361 } 0362 0363 double StrokeCircle::value() 0364 { 0365 return dvalue; 0366 } 0367 0368 bool StrokeCircle::collidesWithItem(const QGraphicsItem*, Qt::ItemSelectionMode) const { return false; } 0369 0370 QRectF StrokeCircle::boundingRect() const { return QRectF(x(), y(), iwidth, iheight); } 0371 0372 void StrokeCircle::setMaxValue(double m) 0373 { 0374 dmax = m; 0375 if (dvalue > dmax) 0376 dvalue = dmax; 0377 } 0378 void StrokeCircle::setSize(const QSizeF& size) 0379 { 0380 if (size.width() > 0) 0381 iwidth = size.width(); 0382 if (size.height() > 0) 0383 iheight = size.height(); 0384 } 0385 void StrokeCircle::setThickness(double t) 0386 { 0387 if (t > 0) 0388 ithickness = t; 0389 } 0390 0391 double StrokeCircle::thickness() const 0392 { 0393 return ithickness; 0394 } 0395 0396 double StrokeCircle::width() const 0397 { 0398 return iwidth; 0399 } 0400 0401 double StrokeCircle::height() const 0402 { 0403 return iheight; 0404 } 0405 0406 void StrokeCircle::paint (QPainter *p, const QStyleOptionGraphicsItem *, QWidget * ) 0407 { 0408 int al = (int)((dvalue * 360 * 16) / dmax); 0409 int length, deg; 0410 if (al < 0) 0411 { 0412 deg = 270 * 16; 0413 length = -al; 0414 } 0415 else if (al <= (270 * 16)) 0416 { 0417 deg = 270 * 16 - al; 0418 length = al; 0419 } 0420 else 0421 { 0422 deg = (360 * 16) - (al - (270 * 16)); 0423 length = al; 0424 } 0425 0426 p->setBrush(QBrush(Qt::black, Qt::NoBrush)); 0427 p->setPen(QPen(Qt::white, ithickness / 2)); 0428 p->drawEllipse(QRectF(x() + ithickness / 2, y() + ithickness / 2, iwidth - ithickness, iheight - ithickness)); 0429 0430 if(dvalue>=0) 0431 p->setPen(QPen(QColor((int)((0xff * dvalue) / dmax), 0, (int)(0xff - (0xff * dvalue) / dmax)), ithickness)); 0432 else 0433 p->setPen(QPen(QColor("black"), ithickness)); 0434 0435 p->drawArc(QRectF(x() + ithickness / 2, y() + ithickness / 2, iwidth - ithickness, iheight - ithickness), deg, length); 0436 0437 p->setPen(QPen(Qt::white, 1)); 0438 p->drawEllipse(QRectF(x(), y(), iwidth, iheight)); 0439 p->drawEllipse(QRectF(x() + ithickness, y() + ithickness, iwidth - ithickness * 2, iheight - ithickness * 2)); 0440 p->setPen(QPen(Qt::white, 3)); 0441 p->drawLine(QPointF(x() + iwidth / 2, y() + iheight - ithickness * 1.5), QPointF(x() + iwidth / 2, y() + iheight)); 0442 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)); 0443 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)); 0444 } 0445 ///////////////////////////////////////// 0446 0447 KolfGame::KolfGame(const Kolf::ItemFactory& factory, PlayerList *players, const QString &filename, QWidget *parent) 0448 : QGraphicsView(parent), 0449 m_factory(factory), 0450 m_soundBlackHole(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("sounds/blackhole.wav"))), 0451 m_soundBlackHoleEject(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("sounds/blackholeeject.wav"))), 0452 m_soundBlackHolePutIn(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("sounds/blackholeputin.wav"))), 0453 m_soundBumper(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("sounds/bumper.wav"))), 0454 m_soundHit(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("sounds/hit.wav"))), 0455 m_soundHoled(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("sounds/holed.wav"))), 0456 m_soundHoleINone(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("sounds/holeinone.wav"))), 0457 m_soundPuddle(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("sounds/puddle.wav"))), 0458 m_soundWall(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("sounds/wall.wav"))), 0459 m_soundWooHoo(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("sounds/woohoo.wav"))), 0460 holeInfo(g_world) 0461 { 0462 setRenderHint(QPainter::Antialiasing); 0463 // for mouse control 0464 setMouseTracking(true); 0465 viewport()->setMouseTracking(true); 0466 setFrameShape(NoFrame); 0467 0468 regAdv = false; 0469 curHole = 0; // will get ++'d 0470 cfg = nullptr; 0471 setFilename(filename); 0472 this->players = players; 0473 curPlayer = players->end(); 0474 curPlayer--; // will get ++'d to end and sent back 0475 // to beginning 0476 paused = false; 0477 modified = false; 0478 inPlay = false; 0479 putting = false; 0480 stroking = false; 0481 editing = false; 0482 strict = false; 0483 lastDelId = -1; 0484 m_showInfo = false; 0485 ballStateList.canUndo = false; 0486 dontAddStroke = false; 0487 addingNewHole = false; 0488 scoreboardHoles = 0; 0489 infoShown = false; 0490 m_useMouse = true; 0491 m_useAdvancedPutting = true; 0492 m_sound = true; 0493 m_ignoreEvents = false; 0494 highestHole = 0; 0495 recalcHighestHole = false; 0496 banner = nullptr; 0497 0498 holeInfo.setGame(this); 0499 holeInfo.setAuthor(i18n("Course Author")); 0500 holeInfo.setName(i18n("Course Name")); 0501 holeInfo.setUntranslatedName(i18n("Course Name")); 0502 holeInfo.setMaxStrokes(10); 0503 holeInfo.borderWallsChanged(true); 0504 0505 // width and height are the width and height of the scene 0506 // in easy storage 0507 width = 400; 0508 height = 400; 0509 0510 margin = 10; 0511 0512 setFocusPolicy(Qt::StrongFocus); 0513 setMinimumSize(width, height); 0514 QSizePolicy sizePolicy = QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); 0515 setSizePolicy(sizePolicy); 0516 0517 setContentsMargins(margin, margin, margin, margin); 0518 0519 course = new Tagaro::Scene(Kolf::renderer(), QStringLiteral("grass")); 0520 course->setMainView(this); //this does this->setScene(course) 0521 courseBoard = new Tagaro::Board; 0522 courseBoard->setLogicalSize(QSizeF(400, 400)); 0523 course->addItem(courseBoard); 0524 0525 if( filename.contains( QLatin1String("intro") ) ) 0526 { 0527 banner = new Tagaro::SpriteObjectItem(Kolf::renderer(), QStringLiteral("intro_foreground"), courseBoard); 0528 banner->setSize(400, 132); 0529 banner->setPos(0, 32); 0530 banner->setZValue(3); //on the height of a puddle (above slopes and sands, below any objects) 0531 } 0532 0533 adjustSize(); 0534 0535 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 0536 { 0537 Ball* ball = (*it).ball(); 0538 ball->setParentItem(courseBoard); 0539 m_topLevelQItems << ball; 0540 m_moveableQItems << ball; 0541 } 0542 0543 QFont font = QApplication::font(); 0544 font.setPixelSize(12); 0545 0546 // create the advanced putting indicator 0547 strokeCircle = new StrokeCircle(courseBoard); 0548 strokeCircle->setPos(width - 90, height - 90); 0549 strokeCircle->setVisible(false); 0550 strokeCircle->setValue(0); 0551 strokeCircle->setMaxValue(360); 0552 0553 // whiteBall marks the spot of the whole whilst editing 0554 whiteBall = new Ball(courseBoard, g_world); 0555 whiteBall->setGame(this); 0556 whiteBall->setColor(Qt::white); 0557 whiteBall->setVisible(false); 0558 whiteBall->setDoDetect(false); 0559 m_topLevelQItems << whiteBall; 0560 m_moveableQItems << whiteBall; 0561 0562 int highestLog = 0; 0563 0564 // if players have scores from loaded game, move to last hole 0565 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 0566 { 0567 if ((int)(*it).scores().count() > highestLog) 0568 highestLog = (*it).scores().count(); 0569 0570 (*it).ball()->setGame(this); 0571 } 0572 0573 // here only for saved games 0574 if (highestLog) 0575 curHole = highestLog; 0576 0577 putter = new Putter(courseBoard, g_world); 0578 0579 // border walls: 0580 0581 // horiz 0582 addBorderWall(QPoint(margin, margin), QPoint(width - margin, margin)); 0583 addBorderWall(QPoint(margin, height - margin - 1), QPoint(width - margin, height - margin - 1)); 0584 0585 // vert 0586 addBorderWall(QPoint(margin, margin), QPoint(margin, height - margin)); 0587 addBorderWall(QPoint(width - margin - 1, margin), QPoint(width - margin - 1, height - margin)); 0588 0589 timer = new QTimer(this); 0590 connect(timer, &QTimer::timeout, this, &KolfGame::timeout); 0591 timerMsec = 300; 0592 0593 fastTimer = new QTimer(this); 0594 connect(fastTimer, &QTimer::timeout, this, &KolfGame::fastTimeout); 0595 fastTimerMsec = 11; 0596 0597 autoSaveTimer = new QTimer(this); 0598 connect(autoSaveTimer, &QTimer::timeout, this, &KolfGame::autoSaveTimeout); 0599 autoSaveMsec = 5 * 1000 * 60; // 5 min autosave 0600 0601 // setUseAdvancedPutting() sets maxStrength! 0602 setUseAdvancedPutting(false); 0603 0604 putting = false; 0605 putterTimer = new QTimer(this); 0606 connect(putterTimer, &QTimer::timeout, this, &KolfGame::putterTimeout); 0607 putterTimerMsec = 20; 0608 } 0609 0610 void KolfGame::playSound(Sound soundType) 0611 { 0612 if (m_sound) { 0613 switch (soundType) { 0614 case Sound::BlackHole: 0615 m_soundBlackHole.start(); 0616 break; 0617 case Sound::BlackHoleEject: 0618 m_soundBlackHoleEject.start(); 0619 break; 0620 case Sound::BlackHolePutIn: 0621 m_soundBlackHolePutIn.start(); 0622 break; 0623 case Sound::Bumper: 0624 m_soundBumper.start(); 0625 break; 0626 case Sound::Hit: 0627 m_soundHit.start(); 0628 break; 0629 case Sound::Holed: 0630 m_soundHoled.start(); 0631 break; 0632 case Sound::HoleINone: 0633 m_soundHoleINone.start(); 0634 break; 0635 case Sound::Puddle: 0636 m_soundPuddle.start(); 0637 break; 0638 case Sound::Wall: 0639 m_soundWall.start(); 0640 break; 0641 case Sound::WooHoo: 0642 m_soundWooHoo.start(); 0643 break; 0644 default: 0645 qWarning() << "There was a request to play an unknown sound."; 0646 break; 0647 } 0648 } 0649 } 0650 0651 void KolfGame::startFirstHole(int hole) 0652 { 0653 if (curHole > 0) // if there was saved game, sync scoreboard 0654 // with number of holes 0655 { 0656 for (; scoreboardHoles < curHole; ++scoreboardHoles) 0657 { 0658 cfgGroup = KConfigGroup(cfg->group(QStringLiteral("%1-hole@-50,-50|0").arg(scoreboardHoles + 1))); 0659 Q_EMIT newHole(cfgGroup.readEntry("par", 3)); 0660 } 0661 0662 // lets load all of the scores from saved game if there are any 0663 for (int hole = 1; hole <= curHole; ++hole) 0664 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 0665 Q_EMIT scoreChanged((*it).id(), hole, (*it).score(hole)); 0666 } 0667 0668 curHole = hole - 1; 0669 0670 // this increments curHole, etc 0671 recalcHighestHole = true; 0672 startNextHole(); 0673 paused = true; 0674 unPause(); 0675 } 0676 0677 void KolfGame::setFilename(const QString &filename) 0678 { 0679 this->filename = filename; 0680 delete cfg; 0681 cfg = new KConfig(filename, KConfig::NoGlobals); 0682 } 0683 0684 KolfGame::~KolfGame() 0685 { 0686 const QList<QGraphicsItem*> itemsCopy(m_topLevelQItems); //this list will be modified soon, so take a copy 0687 for (QGraphicsItem* item : itemsCopy) { 0688 CanvasItem* citem = dynamic_cast<CanvasItem*>(item); 0689 delete citem; 0690 } 0691 0692 delete cfg; 0693 } 0694 0695 void KolfGame::setModified(bool mod) 0696 { 0697 modified = mod; 0698 Q_EMIT modifiedChanged(mod); 0699 } 0700 0701 void KolfGame::pause() 0702 { 0703 if (paused) 0704 { 0705 // play along with people who call pause() again, instead of unPause() 0706 unPause(); 0707 return; 0708 } 0709 0710 paused = true; 0711 timer->stop(); 0712 fastTimer->stop(); 0713 putterTimer->stop(); 0714 } 0715 0716 void KolfGame::unPause() 0717 { 0718 if (!paused) 0719 return; 0720 0721 paused = false; 0722 0723 timer->start(timerMsec); 0724 fastTimer->start(fastTimerMsec); 0725 0726 if (putting || stroking) 0727 putterTimer->start(putterTimerMsec); 0728 } 0729 0730 void KolfGame::addBorderWall(const QPoint &start, const QPoint &end) 0731 { 0732 Kolf::Wall *wall = new Kolf::Wall(courseBoard, g_world); 0733 wall->setLine(QLineF(start, end)); 0734 wall->setVisible(true); 0735 wall->setGame(this); 0736 //change Z value to something very high so that border walls 0737 //really keep the balls inside the course 0738 wall->setZBehavior(CanvasItem::FixedZValue, 10000); 0739 borderWalls.append(wall); 0740 } 0741 0742 void KolfGame::handleMouseDoubleClickEvent(QMouseEvent *e) 0743 { 0744 // allow two fast single clicks 0745 handleMousePressEvent(e); 0746 } 0747 0748 void KolfGame::handleMousePressEvent(QMouseEvent *e) 0749 { 0750 if (m_ignoreEvents) 0751 return; 0752 0753 if (editing) 0754 { 0755 //at this point, QGV::mousePressEvent and thus the interaction 0756 //with overlays has already been done; we therefore know that 0757 //the user has clicked into free space 0758 setSelectedItem(nullptr); 0759 return; 0760 } 0761 else 0762 { 0763 if (m_useMouse) 0764 { 0765 if (!inPlay && e->button() == Qt::LeftButton) 0766 puttPress(); 0767 else if (e->button() == Qt::RightButton) 0768 toggleShowInfo(); 0769 } 0770 } 0771 0772 setFocus(); 0773 } 0774 0775 QPoint KolfGame::viewportToViewport(const QPoint &p) 0776 { 0777 //convert viewport coordinates to board coordinates 0778 return courseBoard->deviceTransform(viewportTransform()).inverted().map(p); 0779 } 0780 0781 // the following four functions are needed to handle both 0782 // border presses and regular in-course presses 0783 0784 void KolfGame::mouseReleaseEvent(QMouseEvent * e) 0785 { 0786 e->setAccepted(false); 0787 QGraphicsView::mouseReleaseEvent(e); 0788 if (e->isAccepted()) 0789 return; 0790 0791 QMouseEvent fixedEvent (QEvent::MouseButtonRelease, viewportToViewport(e->position().toPoint()), e->globalPosition(), e->button(), e->buttons(), e->modifiers(), e->pointingDevice()); 0792 handleMouseReleaseEvent(&fixedEvent); 0793 e->accept(); 0794 } 0795 0796 void KolfGame::mousePressEvent(QMouseEvent * e) 0797 { 0798 e->setAccepted(false); 0799 QGraphicsView::mousePressEvent(e); 0800 if (e->isAccepted()) 0801 return; 0802 0803 QMouseEvent fixedEvent (QEvent::MouseButtonPress, viewportToViewport(e->position().toPoint()), e->globalPosition(), e->button(), e->buttons(), e->modifiers(), e->pointingDevice()); 0804 handleMousePressEvent(&fixedEvent); 0805 e->accept(); 0806 } 0807 0808 void KolfGame::mouseDoubleClickEvent(QMouseEvent * e) 0809 { 0810 e->setAccepted(false); 0811 QGraphicsView::mouseDoubleClickEvent(e); 0812 if (e->isAccepted()) 0813 return; 0814 0815 QMouseEvent fixedEvent (QEvent::MouseButtonDblClick, viewportToViewport(e->position().toPoint()), e->globalPosition(), e->button(), e->buttons(), e->modifiers(), e->pointingDevice()); 0816 handleMouseDoubleClickEvent(&fixedEvent); 0817 e->accept(); 0818 } 0819 0820 void KolfGame::mouseMoveEvent(QMouseEvent * e) 0821 { 0822 e->setAccepted(false); 0823 QGraphicsView::mouseMoveEvent(e); 0824 if (e->isAccepted()) 0825 return; 0826 0827 QMouseEvent fixedEvent (QEvent::MouseMove, viewportToViewport(e->position().toPoint()), e->globalPosition(), e->button(), e->buttons(), e->modifiers(), e->pointingDevice()); 0828 handleMouseMoveEvent(&fixedEvent); 0829 e->accept(); 0830 } 0831 0832 void KolfGame::handleMouseMoveEvent(QMouseEvent *e) 0833 { 0834 if (!editing && !inPlay && putter && !m_ignoreEvents) 0835 { 0836 // mouse moving of putter 0837 updateMouse(); 0838 e->accept(); 0839 } 0840 } 0841 0842 void KolfGame::updateMouse() 0843 { 0844 // don't move putter if in advanced putting sequence 0845 if (!m_useMouse || ((stroking || putting) && m_useAdvancedPutting)) 0846 return; 0847 0848 const QPointF cursor = viewportToViewport(mapFromGlobal(QCursor::pos())); 0849 const QPointF ball((*curPlayer).ball()->x(), (*curPlayer).ball()->y()); 0850 putter->setAngle(-Vector(cursor - ball).direction()); 0851 } 0852 0853 void KolfGame::handleMouseReleaseEvent(QMouseEvent *e) 0854 { 0855 setCursor(Qt::ArrowCursor); 0856 0857 if (editing) 0858 { 0859 Q_EMIT newStatusText(QString()); 0860 } 0861 0862 if (m_ignoreEvents) 0863 return; 0864 0865 if (!editing && m_useMouse) 0866 { 0867 if (!inPlay && e->button() == Qt::LeftButton) 0868 puttRelease(); 0869 else if (e->button() == Qt::RightButton) 0870 toggleShowInfo(); 0871 } 0872 0873 setFocus(); 0874 } 0875 0876 void KolfGame::keyPressEvent(QKeyEvent *e) 0877 { 0878 if (inPlay || editing || m_ignoreEvents) 0879 return; 0880 0881 switch (e->key()) 0882 { 0883 case Qt::Key_Up: 0884 if (!e->isAutoRepeat()) 0885 toggleShowInfo(); 0886 break; 0887 0888 case Qt::Key_Escape: 0889 putting = false; 0890 stroking = false; 0891 finishStroking = false; 0892 strokeCircle->setVisible(false); 0893 putterTimer->stop(); 0894 putter->setOrigin((*curPlayer).ball()->x(), (*curPlayer).ball()->y()); 0895 break; 0896 0897 case Qt::Key_Left: 0898 case Qt::Key_Right: 0899 // don't move putter if in advanced putting sequence 0900 if ((!stroking && !putting) || !m_useAdvancedPutting) 0901 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); 0902 break; 0903 0904 case Qt::Key_Space: case Qt::Key_Down: 0905 puttPress(); 0906 break; 0907 0908 default: 0909 break; 0910 } 0911 } 0912 0913 void KolfGame::toggleShowInfo() 0914 { 0915 setShowInfo(!m_showInfo); 0916 } 0917 0918 void KolfGame::updateShowInfo() 0919 { 0920 setShowInfo(m_showInfo); 0921 } 0922 0923 void KolfGame::setShowInfo(bool yes) 0924 { 0925 m_showInfo = yes; 0926 QList<QGraphicsItem*> infoItems; 0927 for (QGraphicsItem* qitem : std::as_const(m_topLevelQItems)) { 0928 CanvasItem *citem = dynamic_cast<CanvasItem *>(qitem); 0929 if (citem) 0930 infoItems << citem->infoItems(); 0931 } 0932 for (QGraphicsItem* qitem : std::as_const(infoItems)) 0933 qitem->setVisible(m_showInfo); 0934 } 0935 0936 void KolfGame::puttPress() 0937 { 0938 // Advanced putting: 1st click start putting sequence, 2nd determine strength, 3rd determine precision 0939 0940 if (!putting && !stroking && !inPlay) 0941 { 0942 puttCount = 0; 0943 puttReverse = false; 0944 putting = true; 0945 stroking = false; 0946 strength = 0; 0947 if (m_useAdvancedPutting) 0948 { 0949 strokeCircle->setValue(0); 0950 int pw = (int)(putter->line().x2() - putter->line().x1()); 0951 if (pw < 0) pw = -pw; 0952 int px = (int)putter->x() + pw / 2; 0953 int py = (int)putter->y(); 0954 if (px > width / 2 && py < height / 2) 0955 strokeCircle->setPos(px/2 - pw / 2 - 5 - strokeCircle->width()/2, py/2 + 5); 0956 else if (px > width / 2) 0957 strokeCircle->setPos(px/2 - pw / 2 - 5 - strokeCircle->width()/2, py/2 - 5 - strokeCircle->height()/2); 0958 else if (py < height / 2) 0959 strokeCircle->setPos(px/2 + pw / 2 + 5, py/2 + 5); 0960 else 0961 strokeCircle->setPos(px/2 + pw / 2 + 5, py/2 - 5 - strokeCircle->height()/2); 0962 strokeCircle->setVisible(true); 0963 } 0964 putterTimer->start(putterTimerMsec); 0965 } 0966 else if (m_useAdvancedPutting && putting && !editing) 0967 { 0968 putting = false; 0969 stroking = true; 0970 puttReverse = false; 0971 finishStroking = false; 0972 } 0973 else if (m_useAdvancedPutting && stroking) 0974 { 0975 finishStroking = true; 0976 putterTimeout(); 0977 } 0978 } 0979 0980 void KolfGame::keyReleaseEvent(QKeyEvent *e) 0981 { 0982 if (e->isAutoRepeat() || m_ignoreEvents) 0983 return; 0984 0985 if (e->key() == Qt::Key_Space || e->key() == Qt::Key_Down) 0986 puttRelease(); 0987 else if ((e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete) && !(e->modifiers() & Qt::ControlModifier)) 0988 { 0989 if (editing && selectedItem) 0990 { 0991 CanvasItem *citem = dynamic_cast<CanvasItem *>(selectedItem); 0992 if (!citem) 0993 return; 0994 QGraphicsItem *item = dynamic_cast<QGraphicsItem *>(citem); 0995 if (citem && !dynamic_cast<Ball*>(item)) 0996 { 0997 lastDelId = citem->curId(); 0998 0999 m_topLevelQItems.removeAll(item); 1000 m_moveableQItems.removeAll(item); 1001 delete citem; 1002 setSelectedItem(nullptr); 1003 1004 setModified(true); 1005 } 1006 } 1007 } 1008 else if (e->key() == Qt::Key_I || e->key() == Qt::Key_Up) 1009 toggleShowInfo(); 1010 } 1011 1012 void KolfGame::resizeEvent( QResizeEvent* ev ) 1013 { 1014 int newW = ev->size().width(); 1015 int newH = ev->size().height(); 1016 int oldW = ev->oldSize().width(); 1017 int oldH = ev->oldSize().height(); 1018 1019 if(oldW<=0 || oldH<=0) //this is the first draw so no point wasting resources resizing yet 1020 return; 1021 else if( (oldW==newW) && (oldH==newH) ) 1022 return; 1023 1024 int setSize = qMin(newW, newH); 1025 QGraphicsView::resize(setSize, setSize); //make sure new size is square 1026 } 1027 1028 void KolfGame::puttRelease() 1029 { 1030 if (!m_useAdvancedPutting && putting && !editing) 1031 { 1032 putting = false; 1033 stroking = true; 1034 } 1035 } 1036 1037 void KolfGame::stoppedBall() 1038 { 1039 if (!inPlay) 1040 { 1041 inPlay = true; 1042 dontAddStroke = true; 1043 } 1044 } 1045 1046 void KolfGame::timeout() 1047 { 1048 Ball *curBall = (*curPlayer).ball(); 1049 1050 // test if the ball is gone 1051 // in this case we want to stop the ball and 1052 // later undo the shot 1053 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 1054 { 1055 //QGV handles management of dirtied rects for us 1056 //course->update(); 1057 1058 if (!QRectF(QPointF(), courseBoard->logicalSize()).contains((*it).ball()->pos())) 1059 { 1060 (*it).ball()->setState(Stopped); 1061 1062 // don't do it if he's past maxStrokes 1063 if ((*it).score(curHole) < holeInfo.maxStrokes() - 1 || !holeInfo.hasMaxStrokes()) 1064 { 1065 loadStateList(); 1066 } 1067 shotDone(); 1068 1069 return; 1070 } 1071 } 1072 1073 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 1074 if ((*it).ball()->forceStillGoing() || ((*it).ball()->curState() == Rolling && Vector((*it).ball()->velocity()).magnitude() > 0 && (*it).ball()->isVisible())) 1075 return; 1076 1077 int curState = curBall->curState(); 1078 if (curState == Stopped && inPlay) 1079 { 1080 inPlay = false; 1081 QTimer::singleShot(0, this, &KolfGame::shotDone); 1082 } 1083 1084 if (curState == Holed && inPlay) 1085 { 1086 Q_EMIT inPlayEnd(); 1087 1088 int curScore = (*curPlayer).score(curHole); 1089 if (!dontAddStroke) 1090 curScore++; 1091 1092 if (curScore == 1) 1093 { 1094 playSound(Sound::HoleINone); 1095 } 1096 else if (curScore <= holeInfo.par()) 1097 { 1098 playSound(Sound::WooHoo); 1099 } 1100 1101 (*curPlayer).ball()->setZValue((*curPlayer).ball()->zValue() + .1 - (.1)/(curScore)); 1102 1103 if (allPlayersDone()) 1104 { 1105 inPlay = false; 1106 1107 if (curHole > 0 && !dontAddStroke) 1108 { 1109 (*curPlayer).addStrokeToHole(curHole); 1110 Q_EMIT scoreChanged((*curPlayer).id(), curHole, (*curPlayer).score(curHole)); 1111 } 1112 QTimer::singleShot(600, this, &KolfGame::holeDone); 1113 } 1114 else 1115 { 1116 inPlay = false; 1117 QTimer::singleShot(0, this, &KolfGame::shotDone); 1118 } 1119 } 1120 } 1121 1122 void KolfGame::fastTimeout() 1123 { 1124 // do regular advance every other time 1125 if (regAdv) 1126 course->advance(); 1127 regAdv = !regAdv; 1128 1129 if (editing) 1130 return; 1131 1132 // do Box2D advance 1133 //Because there are so much CanvasItems out there, there is currently no 1134 //easy and/or systematic approach to iterate over all of them, except for 1135 //using the b2Bodies available on the world. 1136 1137 //prepare simulation 1138 for (b2Body* body = g_world->GetBodyList(); body; body = body->GetNext()) 1139 { 1140 CanvasItem* citem = static_cast<CanvasItem*>(body->GetUserData()); 1141 if (citem) 1142 { 1143 citem->startSimulation(); 1144 //HACK: the following should not be necessary at this point 1145 QGraphicsItem* qitem = dynamic_cast<QGraphicsItem*>(citem); 1146 if (qitem) 1147 citem->updateZ(qitem); 1148 } 1149 } 1150 //step world 1151 //NOTE: I previously set timeStep to 1.0 so that CItem's velocity() 1152 //corresponds to the position change per step. In this case, the 1153 //velocity would be scaled by Kolf::Box2DScaleFactor, which would result in 1154 //very small velocities (below Box2D's internal cutoff thresholds!) for 1155 //usual movements. Therefore, we apply the scaling to the timestep instead. 1156 const double timeStep = 1.0 * Kolf::Box2DScaleFactor; 1157 g_world->Step(timeStep, 10, 10); //parameters 2/3 = iteration counts (TODO: optimize) 1158 //conclude simulation 1159 for (b2Body* body = g_world->GetBodyList(); body; body = body->GetNext()) 1160 { 1161 CanvasItem* citem = static_cast<CanvasItem*>(body->GetUserData()); 1162 if (citem) 1163 { 1164 citem->endSimulation(); 1165 } 1166 } 1167 } 1168 1169 void KolfGame::ballMoved() 1170 { 1171 if (putter->isVisible()) 1172 { 1173 putter->setPos((*curPlayer).ball()->x(), (*curPlayer).ball()->y()); 1174 updateMouse(); 1175 } 1176 } 1177 1178 void KolfGame::putterTimeout() 1179 { 1180 if (inPlay || editing) 1181 return; 1182 1183 if (m_useAdvancedPutting) 1184 { 1185 if (putting) 1186 { 1187 const qreal base = 2.0; 1188 1189 if (puttReverse && strength <= 0) 1190 { 1191 // aborted 1192 putting = false; 1193 strokeCircle->setVisible(false); 1194 } 1195 else if (strength > maxStrength || puttReverse) 1196 { 1197 // decreasing strength as we've reached the top 1198 puttReverse = true; 1199 strength -= pow(base, qreal(strength / maxStrength)) - 1.8; 1200 if ((int)strength < puttCount * 2) 1201 { 1202 puttCount--; 1203 if (puttCount >= 0) 1204 putter->go(Forwards); 1205 } 1206 } 1207 else 1208 { 1209 // make the increase at high strength faster 1210 strength += pow(base, strength / maxStrength) - .3; 1211 if ((int)strength > puttCount * 2) 1212 { 1213 putter->go(Backwards); 1214 puttCount++; 1215 } 1216 } 1217 // make the visible steps at high strength smaller 1218 strokeCircle->setValue(pow(strength / maxStrength, 0.8) * 360); 1219 } 1220 else if (stroking) 1221 { 1222 double al = strokeCircle->value(); 1223 if (al >= 45) 1224 al -= 0.2 + strength / 50 + al / 100; 1225 else 1226 al -= 0.2 + strength / 50; 1227 1228 if (puttReverse) 1229 { 1230 // show the stroke 1231 puttCount--; 1232 if (puttCount >= 0) 1233 putter->go(Forwards); 1234 else 1235 { 1236 strokeCircle->setVisible(false); 1237 finishStroking = false; 1238 putterTimer->stop(); 1239 putting = false; 1240 stroking = false; 1241 shotStart(); 1242 } 1243 } 1244 else if (al < -45 || finishStroking) 1245 { 1246 strokeCircle->setValue(al); 1247 int deg; 1248 auto *generator = QRandomGenerator::global(); 1249 // if > 45 or < -45 then bad stroke 1250 if (al > 45) 1251 { 1252 deg = putter->curDeg() - 45 + rand() % 90; 1253 strength -= generator->bounded((int)strength); 1254 } 1255 else if (!finishStroking) 1256 { 1257 deg = putter->curDeg() - 45 + rand() % 90; 1258 strength -= generator->bounded((int)strength); 1259 } 1260 else 1261 deg = putter->curDeg() + (int)(strokeCircle->value() / 3); 1262 1263 if (deg < 0) 1264 deg += 360; 1265 else if (deg > 360) 1266 deg -= 360; 1267 1268 putter->setDeg(deg); 1269 puttReverse = true; 1270 } 1271 else 1272 { 1273 strokeCircle->setValue(al); 1274 putterTimer->start(putterTimerMsec/10); 1275 } 1276 } 1277 } 1278 else 1279 { 1280 if (putting) 1281 { 1282 putter->go(Backwards); 1283 puttCount++; 1284 strength += 1.5; 1285 if (strength > maxStrength) 1286 { 1287 putting = false; 1288 stroking = true; 1289 } 1290 } 1291 else if (stroking) 1292 { 1293 if (putter->curLen() < (*curPlayer).ball()->height() + 2) 1294 { 1295 stroking = false; 1296 putterTimer->stop(); 1297 putting = false; 1298 stroking = false; 1299 shotStart(); 1300 } 1301 1302 putter->go(Forwards); 1303 putterTimer->start(putterTimerMsec/10); 1304 } 1305 } 1306 } 1307 1308 void KolfGame::autoSaveTimeout() 1309 { 1310 // this should be a config option 1311 // until it is i'll disable it 1312 if (editing) 1313 { 1314 //save(); 1315 } 1316 } 1317 1318 void KolfGame::recreateStateList() 1319 { 1320 savedState.clear(); 1321 for (QGraphicsItem* item : std::as_const(m_topLevelQItems)) { 1322 if (dynamic_cast<Ball*>(item)) continue; //see below 1323 CanvasItem* citem = dynamic_cast<CanvasItem*>(item); 1324 if (citem) 1325 { 1326 const QString key = makeStateGroup(citem->curId(), citem->name()); 1327 savedState.insert(key, item->pos()); 1328 } 1329 } 1330 1331 ballStateList.clear(); 1332 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 1333 ballStateList.append((*it).stateInfo(curHole)); 1334 1335 ballStateList.canUndo = true; 1336 } 1337 1338 void KolfGame::undoShot() 1339 { 1340 if (ballStateList.canUndo) 1341 loadStateList(); 1342 } 1343 1344 void KolfGame::loadStateList() 1345 { 1346 for (QGraphicsItem* item : std::as_const(m_topLevelQItems)) { 1347 if (dynamic_cast<Ball*>(item)) continue; //see below 1348 CanvasItem* citem = dynamic_cast<CanvasItem*>(item); 1349 if (citem) 1350 { 1351 const QString key = makeStateGroup(citem->curId(), citem->name()); 1352 const QPointF currentPos = item->pos(); 1353 const QPointF posDiff = savedState.value(key, currentPos) - currentPos; 1354 citem->moveBy(posDiff.x(), posDiff.y()); 1355 } 1356 } 1357 1358 for (BallStateList::Iterator it = ballStateList.begin(); it != ballStateList.end(); ++it) 1359 { 1360 BallStateInfo info = (*it); 1361 Player &player = (*(players->begin() + (info.id - 1) )); 1362 player.ball()->setPos(info.spot.x(), info.spot.y()); 1363 player.ball()->setBeginningOfHole(info.beginningOfHole); 1364 if ((*curPlayer).id() == info.id) 1365 ballMoved(); 1366 else 1367 player.ball()->setVisible(!info.beginningOfHole); 1368 player.setScoreForHole(info.score, curHole); 1369 player.ball()->setState(info.state); 1370 Q_EMIT scoreChanged(info.id, curHole, info.score); 1371 } 1372 } 1373 1374 void KolfGame::shotDone() 1375 { 1376 inPlay = false; 1377 Q_EMIT inPlayEnd(); 1378 setFocus(); 1379 1380 Ball *ball = (*curPlayer).ball(); 1381 1382 if(ball->curState() == Rolling) { 1383 // This is a bit of a hack, since we have different timers for detecting shotDone and for doing animation, it can happen that at some point we think the shot 1384 // was done, do a singleshot 0 call, but then we continue the animation and we realize it's not really done, so here make sure we're realy done 1385 // before adding a stroke to the player 1386 inPlay = true; 1387 return; 1388 } 1389 1390 if (!dontAddStroke && (*curPlayer).numHoles()) 1391 (*curPlayer).addStrokeToHole(curHole); 1392 1393 dontAddStroke = false; 1394 1395 // do hack stuff, shouldn't be done here 1396 1397 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 1398 { 1399 if ((*it).ball()->addStroke()) 1400 { 1401 for (int i = 1; i <= (*it).ball()->addStroke(); ++i) 1402 (*it).addStrokeToHole(curHole); 1403 1404 // emit that we have a new stroke count 1405 Q_EMIT scoreChanged((*it).id(), curHole, (*it).score(curHole)); 1406 } 1407 (*it).ball()->setAddStroke(0); 1408 } 1409 1410 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 1411 { 1412 Ball *ball = (*it).ball(); 1413 1414 if (ball->curState() == Holed) 1415 continue; 1416 1417 Vector oldVelocity; 1418 if (ball->placeOnGround(oldVelocity)) 1419 { 1420 ball->setPlaceOnGround(false); 1421 1422 QStringList options; 1423 const QString placeOutside = i18n("Drop Outside of Hazard"); 1424 const QString rehit = i18n("Rehit From Last Location"); 1425 options << placeOutside << rehit; 1426 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, QStringLiteral("hazardOptions")); 1427 1428 if (choice == placeOutside) 1429 { 1430 (*it).ball()->setDoDetect(false); 1431 1432 QPointF pos = ball->pos(); 1433 //normalize old velocity 1434 const QPointF v = oldVelocity / oldVelocity.magnitude(); 1435 1436 while (1) 1437 { 1438 QList<QGraphicsItem *> list = ball->collidingItems(); 1439 bool keepMoving = false; 1440 while (!list.isEmpty()) 1441 { 1442 QGraphicsItem *item = list.takeFirst(); 1443 if (item->data(0) == Rtti_DontPlaceOn) 1444 keepMoving = true; 1445 } 1446 if (!keepMoving) 1447 break; 1448 1449 const qreal movePixel = 3.0; 1450 pos -= v * movePixel; 1451 ball->setPos(pos); 1452 } 1453 } 1454 else if (choice == rehit) 1455 { 1456 for (BallStateList::Iterator it = ballStateList.begin(); it != ballStateList.end(); ++it) 1457 { 1458 if ((*it).id == (*curPlayer).id()) 1459 { 1460 if ((*it).beginningOfHole) 1461 ball->setPos(whiteBall->x(), whiteBall->y()); 1462 else 1463 ball->setPos((*it).spot.x(), (*it).spot.y()); 1464 1465 break; 1466 } 1467 } 1468 } 1469 1470 ball->setVisible(true); 1471 ball->setState(Stopped); 1472 1473 (*it).ball()->setDoDetect(true); 1474 ball->collisionDetect(); 1475 } 1476 } 1477 1478 // emit again 1479 Q_EMIT scoreChanged((*curPlayer).id(), curHole, (*curPlayer).score(curHole)); 1480 1481 if(ball->curState() == Rolling) { 1482 inPlay = true; 1483 return; 1484 } 1485 1486 ball->setVelocity(Vector()); 1487 1488 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 1489 { 1490 Ball *ball = (*it).ball(); 1491 1492 int curStrokes = (*it).score(curHole); 1493 if (curStrokes >= holeInfo.maxStrokes() && holeInfo.hasMaxStrokes()) 1494 { 1495 ball->setState(Holed); 1496 ball->setVisible(false); 1497 1498 // move to center in case he/she hit out 1499 ball->setPos(width / 2, height / 2); 1500 playerWhoMaxed = (*it).name(); 1501 1502 if (allPlayersDone()) 1503 { 1504 startNextHole(); 1505 QTimer::singleShot(100, this, &KolfGame::emitMax); 1506 return; 1507 } 1508 1509 QTimer::singleShot(100, this, &KolfGame::emitMax); 1510 } 1511 } 1512 1513 // change player to next player 1514 // skip player if he's Holed 1515 do 1516 { 1517 curPlayer++; 1518 if (curPlayer == players->end()) 1519 curPlayer = players->begin(); 1520 } 1521 while ((*curPlayer).ball()->curState() == Holed); 1522 1523 Q_EMIT newPlayersTurn(&(*curPlayer)); 1524 1525 (*curPlayer).ball()->setVisible(true); 1526 1527 inPlay = false; 1528 (*curPlayer).ball()->collisionDetect(); 1529 1530 putter->setAngle((*curPlayer).ball()); 1531 putter->setOrigin((*curPlayer).ball()->x(), (*curPlayer).ball()->y()); 1532 updateMouse(); 1533 } 1534 1535 void KolfGame::emitMax() 1536 { 1537 Q_EMIT maxStrokesReached(playerWhoMaxed); 1538 } 1539 1540 void KolfGame::startBall(const Vector &velocity) 1541 { 1542 playSound(Sound::Hit); 1543 Q_EMIT inPlayStart(); 1544 putter->setVisible(false); 1545 1546 (*curPlayer).ball()->setState(Rolling); 1547 (*curPlayer).ball()->setVelocity(velocity); 1548 (*curPlayer).ball()->shotStarted(); 1549 1550 for (QGraphicsItem* qitem : std::as_const(m_topLevelQItems)) { 1551 CanvasItem *citem = dynamic_cast<CanvasItem *>(qitem); 1552 if (citem) 1553 citem->shotStarted(); 1554 } 1555 1556 inPlay = true; 1557 } 1558 1559 void KolfGame::shotStart() 1560 { 1561 // ensure we never hit the ball back into the hole which 1562 // can cause hole skippage 1563 if ((*curPlayer).ball()->curState() == Holed) 1564 return; 1565 1566 // save state 1567 recreateStateList(); 1568 1569 putter->saveAngle((*curPlayer).ball()); 1570 strength /= 8; 1571 if (!strength) 1572 strength = 1; 1573 1574 //kDebug(12007) << "Start started. BallX:" << (*curPlayer).ball()->x() << ", BallY:" << (*curPlayer).ball()->y() << ", Putter Angle:" << putter->curAngle() << ", Vector Strength: " << strength; 1575 1576 (*curPlayer).ball()->collisionDetect(); 1577 1578 startBall(Vector::fromMagnitudeDirection(strength, -(putter->curAngle() + M_PI))); 1579 1580 addHoleInfo(ballStateList); 1581 } 1582 1583 void KolfGame::addHoleInfo(BallStateList &list) 1584 { 1585 list.player = (*curPlayer).id(); 1586 list.vector = (*curPlayer).ball()->velocity(); 1587 list.hole = curHole; 1588 } 1589 1590 void KolfGame::sayWhosGoing() 1591 { 1592 if (players->count() >= 2) 1593 { 1594 KMessageBox::information(this, i18n("%1 will start off.", (*curPlayer).name()), i18n("New Hole"), QStringLiteral("newHole")); 1595 } 1596 } 1597 1598 void KolfGame::holeDone() 1599 { 1600 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 1601 (*it).ball()->setVisible(false); 1602 startNextHole(); 1603 sayWhosGoing(); 1604 } 1605 1606 // this function is WAY too smart for it's own good 1607 // ie, bad design :-( 1608 void KolfGame::startNextHole() 1609 { 1610 setFocus(); 1611 1612 bool reset = true; 1613 if (askSave(true)) 1614 { 1615 if (allPlayersDone()) 1616 { 1617 // we'll reload this hole, but not reset 1618 curHole--; 1619 reset = false; 1620 } 1621 else 1622 return; 1623 } 1624 else 1625 setModified(false); 1626 1627 pause(); 1628 1629 dontAddStroke = false; 1630 1631 inPlay = false; 1632 timer->stop(); 1633 putter->resetAngles(); 1634 1635 int oldCurHole = curHole; 1636 curHole++; 1637 Q_EMIT currentHole(curHole); 1638 1639 if (reset) 1640 { 1641 whiteBall->setPos(width/2, height/2); 1642 holeInfo.borderWallsChanged(true); 1643 } 1644 1645 int leastScore = INT_MAX; 1646 1647 // to get the first player to go first on every hole, 1648 // don't do the score stuff below 1649 curPlayer = players->begin(); 1650 1651 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 1652 { 1653 if (curHole > 1) 1654 { 1655 bool ahead = false; 1656 if ((*it).lastScore() != 0) 1657 { 1658 if ((*it).lastScore() < leastScore) 1659 ahead = true; 1660 else if ((*it).lastScore() == leastScore) 1661 { 1662 for (int i = curHole - 1; i > 0; --i) 1663 { 1664 while(i > (*it).scores().size()) 1665 i--; 1666 1667 const int thisScore = (*it).score(i); 1668 const int thatScore = (*curPlayer).score(i); 1669 if (thisScore < thatScore) 1670 { 1671 ahead = true; 1672 break; 1673 } 1674 else if (thisScore > thatScore) 1675 break; 1676 } 1677 } 1678 } 1679 1680 if (ahead) 1681 { 1682 curPlayer = it; 1683 leastScore = (*it).lastScore(); 1684 } 1685 } 1686 1687 if (reset) 1688 (*it).ball()->setPos(width / 2, height / 2); 1689 else 1690 (*it).ball()->setPos(whiteBall->x(), whiteBall->y()); 1691 1692 (*it).ball()->setState(Stopped); 1693 1694 // this gets set to false when the ball starts 1695 // to move by the Mr. Ball himself. 1696 (*it).ball()->setBeginningOfHole(true); 1697 if ((int)(*it).scores().count() < curHole) 1698 (*it).addHole(); 1699 (*it).ball()->setVelocity(Vector()); 1700 (*it).ball()->setVisible(false); 1701 } 1702 1703 Q_EMIT newPlayersTurn(&(*curPlayer)); 1704 1705 if (reset) 1706 openFile(); 1707 1708 inPlay = false; 1709 timer->start(timerMsec); 1710 1711 if(size().width()!=400 || size().height()!=400) { //not default size, so resizing needed 1712 int setSize = qMin(size().width(), size().height()); 1713 //resize needs to be called for setSize+1 first because otherwise it doesn't seem to get called (not sure why) 1714 QGraphicsView::resize(setSize+1, setSize+1); 1715 QGraphicsView::resize(setSize, setSize); 1716 } 1717 1718 // if (false) { we're done with the round! } 1719 if (oldCurHole != curHole) 1720 { 1721 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) { 1722 (*it).ball()->setPlaceOnGround(false); 1723 while( (*it).numHoles() < (unsigned)curHole) 1724 (*it).addHole(); 1725 } 1726 1727 // here we have to make sure the scoreboard shows 1728 // all of the holes up until now; 1729 1730 for (; scoreboardHoles < curHole; ++scoreboardHoles) 1731 { 1732 cfgGroup = KConfigGroup(cfg->group(QStringLiteral("%1-hole@-50,-50|0").arg(scoreboardHoles + 1))); 1733 Q_EMIT newHole(cfgGroup.readEntry("par", 3)); 1734 } 1735 1736 resetHoleScores(); 1737 updateShowInfo(); 1738 1739 // this is from shotDone() 1740 (*curPlayer).ball()->setVisible(true); 1741 putter->setOrigin((*curPlayer).ball()->x(), (*curPlayer).ball()->y()); 1742 updateMouse(); 1743 1744 ballStateList.canUndo = false; 1745 1746 (*curPlayer).ball()->collisionDetect(); 1747 } 1748 1749 unPause(); 1750 } 1751 1752 void KolfGame::showInfoDlg(bool addDontShowAgain) 1753 { 1754 KMessageBox::information(parentWidget(), 1755 i18n("Course name: %1", holeInfo.name()) + QStringLiteral("\n") 1756 + i18n("Created by %1", holeInfo.author()) + QStringLiteral("\n") 1757 + i18np("%1 hole", "%1 holes", highestHole), 1758 i18nc("@title:window", "Course Information"), 1759 addDontShowAgain? holeInfo.name() + QStringLiteral(" ") + holeInfo.author() : QString()); 1760 } 1761 1762 void KolfGame::openFile() 1763 { 1764 QList<QGraphicsItem*> newTopLevelQItems; 1765 const auto currentTopLevelQItems = m_topLevelQItems; 1766 for (QGraphicsItem* qitem : currentTopLevelQItems) { 1767 if (dynamic_cast<Ball*>(qitem)) 1768 { 1769 //do not delete balls 1770 newTopLevelQItems << qitem; 1771 continue; 1772 } 1773 CanvasItem *citem = dynamic_cast<CanvasItem *>(qitem); 1774 if (citem) 1775 { 1776 delete citem; 1777 } 1778 } 1779 1780 m_moveableQItems = m_topLevelQItems = newTopLevelQItems; 1781 selectedItem = nullptr; 1782 1783 // will tell basic course info 1784 // we do this here for the hell of it. 1785 // there is no fake id, by the way, 1786 // because it's old and when i added ids i forgot to change it. 1787 cfgGroup = KConfigGroup(cfg->group(QStringLiteral("0-course@-50,-50"))); 1788 holeInfo.setAuthor(cfgGroup.readEntry("author", holeInfo.author())); 1789 holeInfo.setName(cfgGroup.readEntry("Name", holeInfo.name())); 1790 holeInfo.setUntranslatedName(cfgGroup.readEntryUntranslated("Name", holeInfo.untranslatedName())); 1791 Q_EMIT titleChanged(holeInfo.name()); 1792 1793 cfgGroup = KConfigGroup(KSharedConfig::openConfig(filename), QStringLiteral("%1-hole@-50,-50|0").arg(curHole)); 1794 curPar = cfgGroup.readEntry("par", 3); 1795 holeInfo.setPar(curPar); 1796 holeInfo.borderWallsChanged(cfgGroup.readEntry("borderWalls", holeInfo.borderWalls())); 1797 holeInfo.setMaxStrokes(cfgGroup.readEntry("maxstrokes", 10)); 1798 1799 QStringList missingPlugins; 1800 1801 // The "for" loop depends on the list of groups being in sorted order. 1802 QStringList groups = cfg->groupList(); 1803 groups.sort(); 1804 1805 int numItems = 0; 1806 int _highestHole = 0; 1807 1808 for (QStringList::const_iterator it = groups.constBegin(); it != groups.constEnd(); ++it) 1809 { 1810 // Format of group name is [<holeNum>-<name>@<x>,<y>|<id>] 1811 cfgGroup = KConfigGroup(cfg->group(*it)); 1812 1813 const int len = (*it).length(); 1814 const int dashIndex = (*it).indexOf(QLatin1Char('-')); 1815 const int holeNum = QStringView(*it).left(dashIndex).toInt(); 1816 if (holeNum > _highestHole) 1817 _highestHole = holeNum; 1818 1819 const int atIndex = (*it).indexOf(QLatin1Char('@')); 1820 const QString name = (*it).mid(dashIndex + 1, atIndex - (dashIndex + 1)); 1821 1822 if (holeNum != curHole) 1823 { 1824 // Break before reading all groups, if the highest hole 1825 // number is known and all items in curHole are done. 1826 if (numItems && !recalcHighestHole) 1827 break; 1828 continue; 1829 } 1830 numItems++; 1831 1832 1833 const int commaIndex = (*it).indexOf(QLatin1Char(',')); 1834 const int pipeIndex = (*it).indexOf(QLatin1Char('|')); 1835 const int x = QStringView(*it).mid(atIndex + 1, commaIndex - (atIndex + 1)).toInt(); 1836 const int y = QStringView(*it).mid(commaIndex + 1, pipeIndex - (commaIndex + 1)).toInt(); 1837 // will tell where ball is 1838 if (name == QLatin1String("ball")) 1839 { 1840 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 1841 (*it).ball()->setPos(x, y); 1842 whiteBall->setPos(x, y); 1843 continue; 1844 } 1845 const int id = QStringView(*it).right(len - (pipeIndex + 1)).toInt(); 1846 QGraphicsItem* newItem = m_factory.createInstance(name, courseBoard, g_world); 1847 if (newItem) 1848 { 1849 m_topLevelQItems << newItem; 1850 m_moveableQItems << newItem; 1851 CanvasItem *sceneItem = dynamic_cast<CanvasItem *>(newItem); 1852 1853 if (!sceneItem) 1854 continue; 1855 1856 sceneItem->setId(id); 1857 sceneItem->setGame(this); 1858 sceneItem->editModeChanged(editing); 1859 sceneItem->setName(name); 1860 m_moveableQItems.append(sceneItem->moveableItems()); 1861 1862 sceneItem->setPosition(QPointF(x, y)); 1863 newItem->setVisible(true); 1864 1865 // make things actually show 1866 cfgGroup = KConfigGroup(cfg->group(makeGroup(id, curHole, sceneItem->name(), x, y))); 1867 sceneItem->load(&cfgGroup); 1868 } 1869 else if (name != QLatin1String("hole") && !missingPlugins.contains(name)) 1870 missingPlugins.append(name); 1871 1872 } 1873 1874 if (!missingPlugins.empty()) 1875 { 1876 KMessageBox::informationList(this, QStringLiteral("<p>") + i18n("This hole uses the following plugins, which you do not have installed:") + QStringLiteral("</p>"), missingPlugins, QString(), QStringLiteral("%1 warning").arg(holeInfo.untranslatedName() + QString::number(curHole))); 1877 } 1878 1879 lastDelId = -1; 1880 1881 // if it's the first hole let's not 1882 if (!numItems && curHole > 1 && !addingNewHole && curHole >= _highestHole) 1883 { 1884 // we're done, let's quit 1885 curHole--; 1886 pause(); 1887 Q_EMIT holesDone(); 1888 1889 // tidy things up 1890 setBorderWalls(false); 1891 clearHole(); 1892 setModified(false); 1893 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 1894 (*it).ball()->setVisible(false); 1895 1896 return; 1897 } 1898 1899 // do it down here; if !hasFinalLoad, do it up there! 1900 //QGraphicsItem *qsceneItem = 0; 1901 QList<QGraphicsItem *>::const_iterator qsceneItem; 1902 QList<CanvasItem *> todo; 1903 QList<QGraphicsItem *> qtodo; 1904 1905 if (curHole > _highestHole) 1906 _highestHole = curHole; 1907 1908 if (recalcHighestHole) 1909 { 1910 highestHole = _highestHole; 1911 recalcHighestHole = false; 1912 Q_EMIT largestHole(highestHole); 1913 } 1914 1915 if (curHole == 1 && !filename.isNull() && !infoShown) 1916 { 1917 // let's not now, because they see it when they choose course 1918 //showInfoDlg(true); 1919 infoShown = true; 1920 } 1921 1922 setModified(false); 1923 } 1924 1925 void KolfGame::addNewObject(const QString& identifier) 1926 { 1927 QGraphicsItem *newItem = m_factory.createInstance(identifier, courseBoard, g_world); 1928 1929 m_topLevelQItems << newItem; 1930 m_moveableQItems << newItem; 1931 if(!newItem->isVisible()) 1932 newItem->setVisible(true); 1933 1934 CanvasItem *sceneItem = dynamic_cast<CanvasItem *>(newItem); 1935 if (!sceneItem) 1936 return; 1937 1938 // we need to find a number that isn't taken 1939 int i = lastDelId > 0? lastDelId : m_topLevelQItems.count() - 30; 1940 if (i <= 0) 1941 i = 0; 1942 1943 for (;; ++i) 1944 { 1945 bool found = false; 1946 for (QGraphicsItem* qitem : std::as_const(m_topLevelQItems)) { 1947 CanvasItem *citem = dynamic_cast<CanvasItem *>(qitem); 1948 if (citem) 1949 { 1950 if (citem->curId() == i) 1951 { 1952 found = true; 1953 break; 1954 } 1955 } 1956 } 1957 1958 1959 if (!found) 1960 break; 1961 } 1962 sceneItem->setId(i); 1963 1964 sceneItem->setGame(this); 1965 1966 const auto infoItems = sceneItem->infoItems(); 1967 for (QGraphicsItem* qitem : infoItems) 1968 qitem->setVisible(m_showInfo); 1969 1970 sceneItem->editModeChanged(editing); 1971 1972 sceneItem->setName(identifier); 1973 m_moveableQItems.append(sceneItem->moveableItems()); 1974 1975 newItem->setPos(width/2 - 18, height / 2 - 18); 1976 sceneItem->moveBy(0, 0); 1977 sceneItem->setSize(newItem->boundingRect().size()); 1978 1979 setModified(true); 1980 } 1981 1982 bool KolfGame::askSave(bool noMoreChances) 1983 { 1984 if (!modified) 1985 // not cancel, don't save 1986 return false; 1987 1988 int result = KMessageBox::warningTwoActionsCancel(this, 1989 i18n("There are unsaved changes to current hole. Save them?"), 1990 i18n("Unsaved Changes"), 1991 KStandardGuiItem::save(), 1992 noMoreChances? KStandardGuiItem::discard() : KGuiItem(i18n("Save &Later")), 1993 KStandardGuiItem::cancel(), 1994 noMoreChances? QStringLiteral("DiscardAsk") : QStringLiteral("SaveAsk")); 1995 switch (result) 1996 { 1997 case KMessageBox::PrimaryAction: 1998 save(); 1999 [[fallthrough]]; 2000 2001 case KMessageBox::SecondaryAction: 2002 return false; 2003 break; 2004 2005 case KMessageBox::Cancel: 2006 return true; 2007 break; 2008 2009 default: 2010 break; 2011 } 2012 2013 return false; 2014 } 2015 2016 void KolfGame::addNewHole() 2017 { 2018 if (askSave(true)) 2019 return; 2020 2021 // either it's already false 2022 // because it was saved by askSave(), 2023 // or the user pressed the 'discard' button 2024 setModified(false); 2025 2026 // find highest hole num, and create new hole 2027 // now openFile makes highest hole for us 2028 2029 addingNewHole = true; 2030 curHole = highestHole; 2031 recalcHighestHole = true; 2032 startNextHole(); 2033 addingNewHole = false; 2034 Q_EMIT currentHole(curHole); 2035 2036 // make sure even the current player isn't showing 2037 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 2038 (*it).ball()->setVisible(false); 2039 2040 whiteBall->setVisible(editing); 2041 putter->setVisible(!editing); 2042 inPlay = false; 2043 2044 // add default objects 2045 const auto knownTypes = m_factory.knownTypes(); 2046 for (const Kolf::ItemMetadata& metadata : knownTypes) 2047 if (metadata.addOnNewHole) 2048 addNewObject(metadata.identifier); 2049 2050 save(); 2051 } 2052 2053 // kantan deshou ;-) 2054 void KolfGame::resetHole() 2055 { 2056 if (askSave(true)) 2057 return; 2058 setModified(false); 2059 curHole--; 2060 startNextHole(); 2061 resetHoleScores(); 2062 } 2063 2064 void KolfGame::resetHoleScores() 2065 { 2066 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 2067 { 2068 (*it).resetScore(curHole); 2069 Q_EMIT scoreChanged((*it).id(), curHole, 0); 2070 } 2071 } 2072 2073 void KolfGame::clearHole() 2074 { 2075 QList<QGraphicsItem*> newTopLevelQItems; 2076 const auto currentTopLevelQItems = m_topLevelQItems; 2077 for (QGraphicsItem* qitem : currentTopLevelQItems) { 2078 if (dynamic_cast<Ball*>(qitem)) 2079 { 2080 //do not delete balls 2081 newTopLevelQItems << qitem; 2082 continue; 2083 } 2084 CanvasItem *citem = dynamic_cast<CanvasItem *>(qitem); 2085 if (citem) 2086 { 2087 delete citem; 2088 } 2089 } 2090 2091 m_moveableQItems = m_topLevelQItems = newTopLevelQItems; 2092 setSelectedItem(nullptr); 2093 2094 // add default objects 2095 const auto knownTypes = m_factory.knownTypes(); 2096 for (const Kolf::ItemMetadata& metadata : knownTypes) 2097 if (metadata.addOnNewHole) 2098 addNewObject(metadata.identifier); 2099 2100 setModified(true); 2101 } 2102 2103 void KolfGame::switchHole(int hole) 2104 { 2105 if (inPlay) 2106 return; 2107 if (hole < 1 || hole > highestHole) 2108 return; 2109 2110 bool wasEditing = editing; 2111 if (editing) 2112 toggleEditMode(); 2113 2114 if (askSave(true)) 2115 return; 2116 setModified(false); 2117 2118 curHole = hole; 2119 resetHole(); 2120 2121 if (wasEditing) 2122 toggleEditMode(); 2123 } 2124 2125 void KolfGame::switchHole(const QString &holestring) 2126 { 2127 bool ok; 2128 int hole = holestring.toInt(&ok); 2129 if (!ok) 2130 return; 2131 switchHole(hole); 2132 } 2133 2134 void KolfGame::nextHole() 2135 { 2136 switchHole(curHole + 1); 2137 } 2138 2139 void KolfGame::prevHole() 2140 { 2141 switchHole(curHole - 1); 2142 } 2143 2144 void KolfGame::firstHole() 2145 { 2146 switchHole(1); 2147 } 2148 2149 void KolfGame::lastHole() 2150 { 2151 switchHole(highestHole); 2152 } 2153 2154 void KolfGame::randHole() 2155 { 2156 const int newHole = QRandomGenerator::global()->bounded(1, highestHole); 2157 switchHole(newHole); 2158 } 2159 2160 void KolfGame::save() 2161 { 2162 if (filename.isEmpty()) 2163 { 2164 QPointer<QFileDialog> fileSaveDialog = new QFileDialog(this); 2165 fileSaveDialog->setWindowTitle(i18nc("@title:window", "Pick Kolf Course to Save To")); 2166 fileSaveDialog->setMimeTypeFilters(QStringList(QStringLiteral("application/x-kourse"))); 2167 fileSaveDialog->setAcceptMode(QFileDialog::AcceptSave); 2168 if (fileSaveDialog->exec() == QDialog::Accepted) { 2169 QUrl newfile = fileSaveDialog->selectedUrls().first(); 2170 if (newfile.isEmpty()) { 2171 return; 2172 } 2173 else { 2174 setFilename(newfile.toLocalFile()); 2175 } 2176 } 2177 delete fileSaveDialog; 2178 } 2179 2180 Q_EMIT parChanged(curHole, holeInfo.par()); 2181 Q_EMIT titleChanged(holeInfo.name()); 2182 2183 const QStringList groups = cfg->groupList(); 2184 2185 // wipe out all groups from this hole 2186 for (QStringList::const_iterator it = groups.begin(); it != groups.end(); ++it) 2187 { 2188 int holeNum = QStringView(*it).left((*it).indexOf(QLatin1Char('-'))).toInt(); 2189 if (holeNum == curHole) 2190 cfg->deleteGroup(*it); 2191 } 2192 for (QGraphicsItem* qitem : std::as_const(m_topLevelQItems)) { 2193 CanvasItem *citem = dynamic_cast<CanvasItem *>(qitem); 2194 if (citem) 2195 { 2196 cfgGroup = KConfigGroup(cfg->group(makeGroup(citem->curId(), curHole, citem->name(), (int)qitem->x(), (int)qitem->y()))); 2197 citem->save(&cfgGroup); 2198 } 2199 } 2200 2201 // save where ball starts (whiteBall tells all) 2202 cfgGroup = KConfigGroup(cfg->group(QStringLiteral("%1-ball@%2,%3").arg(curHole).arg((int)whiteBall->x()).arg((int)whiteBall->y()))); 2203 cfgGroup.writeEntry("dummykey", true); 2204 2205 cfgGroup = KConfigGroup(cfg->group(QStringLiteral("0-course@-50,-50"))); 2206 cfgGroup.writeEntry("author", holeInfo.author()); 2207 cfgGroup.writeEntry("Name", holeInfo.untranslatedName()); 2208 2209 // save hole info 2210 cfgGroup = KConfigGroup(cfg->group(QStringLiteral("%1-hole@-50,-50|0").arg(curHole))); 2211 cfgGroup.writeEntry("par", holeInfo.par()); 2212 cfgGroup.writeEntry("maxstrokes", holeInfo.maxStrokes()); 2213 cfgGroup.writeEntry("borderWalls", holeInfo.borderWalls()); 2214 2215 cfg->sync(); 2216 2217 setModified(false); 2218 } 2219 2220 void KolfGame::toggleEditMode() 2221 { 2222 // won't be editing anymore, and user wants to cancel, we return 2223 // this is pretty useless. when the person leaves the hole, 2224 // he gets asked again 2225 /* 2226 if (editing && modified) 2227 { 2228 if (askSave(false)) 2229 { 2230 Q_EMIT checkEditing(); 2231 return; 2232 } 2233 } 2234 */ 2235 2236 selectedItem = nullptr; 2237 2238 editing = !editing; 2239 2240 if (editing) 2241 { 2242 Q_EMIT editingStarted(); 2243 setSelectedItem(nullptr); 2244 } 2245 else 2246 { 2247 Q_EMIT editingEnded(); 2248 setCursor(Qt::ArrowCursor); 2249 } 2250 2251 // alert our items 2252 for (QGraphicsItem* qitem : std::as_const(m_topLevelQItems)) { 2253 if (dynamic_cast<Ball*>(qitem)) continue; 2254 CanvasItem *citem = dynamic_cast<CanvasItem *>(qitem); 2255 if (citem) 2256 citem->editModeChanged(editing); 2257 } 2258 2259 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 2260 { 2261 // curplayer shouldn't be hidden no matter what 2262 if ((*it).ball()->beginningOfHole() && it != curPlayer) 2263 (*it).ball()->setVisible(false); 2264 else 2265 (*it).ball()->setVisible(!editing); 2266 } 2267 2268 whiteBall->setVisible(editing); 2269 whiteBall->editModeChanged(editing); 2270 2271 // shouldn't see putter whilst editing 2272 putter->setVisible(!editing); 2273 2274 if (editing) 2275 autoSaveTimer->start(autoSaveMsec); 2276 else 2277 autoSaveTimer->stop(); 2278 2279 inPlay = false; 2280 } 2281 2282 void KolfGame::setSelectedItem(CanvasItem* citem) 2283 { 2284 QGraphicsItem* qitem = dynamic_cast<QGraphicsItem*>(citem); 2285 selectedItem = qitem; 2286 Q_EMIT newSelectedItem(qitem ? citem : &holeInfo); 2287 //deactivate all other overlays 2288 for (QGraphicsItem* otherQitem : std::as_const(m_topLevelQItems)) { 2289 CanvasItem* otherCitem = dynamic_cast<CanvasItem*>(otherQitem); 2290 if (otherCitem && otherCitem != citem) 2291 { 2292 //false = do not create overlay if it does not exist yet 2293 Kolf::Overlay* otherOverlay = otherCitem->overlay(false); 2294 if (otherOverlay) 2295 otherOverlay->setState(Kolf::Overlay::Passive); 2296 } 2297 } 2298 } 2299 2300 void HoleInfo::borderWallsChanged(bool yes) 2301 { 2302 m_borderWalls = yes; 2303 game->setBorderWalls(yes); 2304 } 2305 2306 bool KolfGame::allPlayersDone() 2307 { 2308 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 2309 if ((*it).ball()->curState() != Holed) 2310 return false; 2311 2312 return true; 2313 } 2314 2315 void KolfGame::setBorderWalls(bool showing) 2316 { 2317 for (Kolf::Wall* wall : std::as_const(borderWalls)) 2318 wall->setVisible(showing); 2319 } 2320 2321 void KolfGame::setUseAdvancedPutting(bool yes) 2322 { 2323 m_useAdvancedPutting = yes; 2324 2325 // increase maxStrength in advanced putting mode 2326 if (yes) { 2327 maxStrength = 65; 2328 } else { 2329 maxStrength = 55; 2330 strokeCircle->setVisible(false); 2331 } 2332 } 2333 2334 void KolfGame::setShowGuideLine(bool yes) 2335 { 2336 putter->setShowGuideLine(yes); 2337 } 2338 2339 void KolfGame::setSound(bool yes) 2340 { 2341 m_sound = yes; 2342 } 2343 2344 void KolfGame::courseInfo(CourseInfo &info, const QString& filename) 2345 { 2346 KConfig config(filename); 2347 KConfigGroup configGroup (config.group(QStringLiteral("0-course@-50,-50"))); 2348 info.author = configGroup.readEntry("author", info.author); 2349 info.name = configGroup.readEntry("Name", configGroup.readEntry("name", info.name)); 2350 info.untranslatedName = configGroup.readEntryUntranslated("Name", configGroup.readEntryUntranslated("name", info.name)); 2351 2352 unsigned int hole = 1; 2353 unsigned int par= 0; 2354 while (1) 2355 { 2356 QString group = QStringLiteral("%1-hole@-50,-50|0").arg(hole); 2357 if (!config.hasGroup(group)) 2358 { 2359 hole--; 2360 break; 2361 } 2362 2363 configGroup = KConfigGroup(config.group(group)); 2364 par += configGroup.readEntry("par", 3); 2365 2366 hole++; 2367 } 2368 2369 info.par = par; 2370 info.holes = hole; 2371 } 2372 2373 void KolfGame::scoresFromSaved(KConfig *config, PlayerList &players) 2374 { 2375 KConfigGroup configGroup(config->group(QStringLiteral("0 Saved Game"))); 2376 int numPlayers = configGroup.readEntry("Players", 0); 2377 if (numPlayers <= 0) 2378 return; 2379 2380 for (int i = 1; i <= numPlayers; ++i) 2381 { 2382 // this is same as in kolf.cpp, but we use saved game values 2383 configGroup = KConfigGroup(config->group(QString::number(i))); 2384 players.append(Player()); 2385 players.last().ball()->setColor(configGroup.readEntry("Color", "#ffffff")); 2386 players.last().setName(configGroup.readEntry("Name")); 2387 players.last().setId(i); 2388 2389 const QStringList scores(configGroup.readEntry("Scores",QStringList())); 2390 QList<int> intscores; 2391 for (QStringList::const_iterator it = scores.begin(); it != scores.end(); ++it) 2392 intscores.append((*it).toInt()); 2393 2394 players.last().setScores(intscores); 2395 } 2396 } 2397 2398 void KolfGame::saveScores(KConfig *config) 2399 { 2400 // wipe out old player info 2401 const QStringList groups = config->groupList(); 2402 for (QStringList::const_iterator it = groups.begin(); it != groups.end(); ++it) 2403 { 2404 // this deletes all int groups, ie, the player info groups 2405 bool ok = false; 2406 (*it).toInt(&ok); 2407 if (ok) 2408 config->deleteGroup(*it); 2409 } 2410 2411 KConfigGroup configGroup(config->group(QStringLiteral("0 Saved Game"))); 2412 configGroup.writeEntry("Players", players->count()); 2413 configGroup.writeEntry("Course", filename); 2414 configGroup.writeEntry("Current Hole", curHole); 2415 2416 for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) 2417 { 2418 KConfigGroup configGroup(config->group(QString::number((*it).id()))); 2419 configGroup.writeEntry("Name", (*it).name()); 2420 configGroup.writeEntry("Color", (*it).ball()->color().name()); 2421 2422 QStringList scores; 2423 QList<int> intscores = (*it).scores(); 2424 for (QList<int>::Iterator it = intscores.begin(); it != intscores.end(); ++it) 2425 scores.append(QString::number(*it)); 2426 2427 configGroup.writeEntry("Scores", scores); 2428 } 2429 } 2430 2431 CourseInfo::CourseInfo() 2432 : name(i18n("Course Name")), author(i18n("Course Author")), holes(0), par(0) 2433 { 2434 } 2435 2436 #include "moc_game.cpp"