diff options
Diffstat (limited to 'kolf/game.cpp')
-rw-r--r-- | kolf/game.cpp | 4302 |
1 files changed, 4302 insertions, 0 deletions
diff --git a/kolf/game.cpp b/kolf/game.cpp new file mode 100644 index 00000000..55b324ce --- /dev/null +++ b/kolf/game.cpp @@ -0,0 +1,4302 @@ +#include <arts/kmedia2.h> +#include <arts/kplayobject.h> +#include <arts/kplayobjectfactory.h> + +#include <kapplication.h> +#include <kconfig.h> +#include <kcursor.h> +#include <kdebug.h> +#include <knuminput.h> +#include <kfiledialog.h> +#include <kglobal.h> +#include <klineedit.h> +#include <kmessagebox.h> +#include <kpixmapeffect.h> +#include <kprinter.h> +#include <kstandarddirs.h> + +#include <qbrush.h> +#include <qcanvas.h> +#include <qcheckbox.h> +#include <qcolor.h> +#include <qcursor.h> +#include <qevent.h> +#include <qfont.h> +#include <qfontmetrics.h> +#include <qimage.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qmap.h> +#include <qpainter.h> +#include <qpaintdevicemetrics.h> +#include <qpen.h> +#include <qpixmap.h> +#include <qpixmapcache.h> +#include <qpoint.h> +#include <qpointarray.h> +#include <qrect.h> +#include <qsimplerichtext.h> +#include <qsize.h> +#include <qslider.h> +#include <qspinbox.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qtimer.h> +#include <qtooltip.h> +#include <qvaluelist.h> +#include <qwhatsthis.h> + +#include <math.h> +#include <stdlib.h> +#include <unistd.h> + +#include "kcomboboxdialog.h" +#include "kvolumecontrol.h" +#include "vector.h" +#include "game.h" + + +inline QString makeGroup(int id, int hole, QString name, int x, int y) +{ + return QString("%1-%2@%3,%4|%5").arg(hole).arg(name).arg(x).arg(y).arg(id); +} + +inline QString makeStateGroup(int id, const QString &name) +{ + return QString("%1|%2").arg(name).arg(id); +} + +///////////////////////// + +RectPoint::RectPoint(QColor color, RectItem *rect, QCanvas *canvas) + : QCanvasEllipse(canvas) +{ + setZ(9999); + setSize(10, 10); + this->rect = rect; + setBrush(QBrush(color)); + setSizeFactor(1.0); + dontmove = false; +} + +void RectPoint::moveBy(double dx, double dy) +{ + QCanvasEllipse::moveBy(dx, dy); + + if (dontmove) + { + dontmove = false; + return; + } + + QCanvasItem *qitem = dynamic_cast<QCanvasItem *>(rect); + if (!qitem) + return; + + double nw = m_sizeFactor * fabs(x() - qitem->x()); + double nh = m_sizeFactor * fabs(y() - qitem->y()); + if (nw <= 0 || nh <= 0) + return; + + rect->newSize(nw, nh); +} + +Config *RectPoint::config(QWidget *parent) +{ + CanvasItem *citem = dynamic_cast<CanvasItem *>(rect); + if (citem) + return citem->config(parent); + else + return CanvasItem::config(parent); +} + +///////////////////////// + +Arrow::Arrow(QCanvas *canvas) + : QCanvasLine(canvas) +{ + line1 = new QCanvasLine(canvas); + line2 = new QCanvasLine(canvas); + + m_angle = 0; + m_length = 20; + m_reversed = false; + + setPen(black); + + updateSelf(); + setVisible(false); +} + +void Arrow::setPen(QPen p) +{ + QCanvasLine::setPen(p); + line1->setPen(p); + line2->setPen(p); +} + +void Arrow::setZ(double newz) +{ + QCanvasLine::setZ(newz); + line1->setZ(newz); + line2->setZ(newz); +} + +void Arrow::setVisible(bool yes) +{ + QCanvasLine::setVisible(yes); + line1->setVisible(yes); + line2->setVisible(yes); +} + +void Arrow::moveBy(double dx, double dy) +{ + QCanvasLine::moveBy(dx, dy); + line1->moveBy(dx, dy); + line2->moveBy(dx, dy); +} + +void Arrow::aboutToDie() +{ + delete line1; + delete line2; +} + +void Arrow::updateSelf() +{ + QPoint start = startPoint(); + QPoint end(m_length * cos(m_angle), m_length * sin(m_angle)); + + if (m_reversed) + { + QPoint tmp(start); + start = end; + end = tmp; + } + + setPoints(start.x(), start.y(), end.x(), end.y()); + + const double lineLen = m_length / 2; + + const double angle1 = m_angle - M_PI / 2 - 1; + line1->move(end.x() + x(), end.y() + y()); + start = end; + end = QPoint(lineLen * cos(angle1), lineLen * sin(angle1)); + line1->setPoints(0, 0, end.x(), end.y()); + + const double angle2 = m_angle + M_PI / 2 + 1; + line2->move(start.x() + x(), start.y() + y()); + end = QPoint(lineLen * cos(angle2), lineLen * sin(angle2)); + line2->setPoints(0, 0, end.x(), end.y()); +} + +///////////////////////// + +BridgeConfig::BridgeConfig(Bridge *bridge, QWidget *parent) + : Config(parent) +{ + this->bridge = bridge; + + m_vlayout = new QVBoxLayout(this, marginHint(), spacingHint()); + QGridLayout *layout = new QGridLayout(m_vlayout, 2, 3, spacingHint()); + layout->addWidget(new QLabel(i18n("Walls on:"), this), 0, 0); + top = new QCheckBox(i18n("&Top"), this); + layout->addWidget(top, 0, 1); + connect(top, SIGNAL(toggled(bool)), this, SLOT(topWallChanged(bool))); + top->setChecked(bridge->topWallVisible()); + bot = new QCheckBox(i18n("&Bottom"), this); + layout->addWidget(bot, 1, 1); + connect(bot, SIGNAL(toggled(bool)), this, SLOT(botWallChanged(bool))); + bot->setChecked(bridge->botWallVisible()); + left = new QCheckBox(i18n("&Left"), this); + layout->addWidget(left, 1, 0); + connect(left, SIGNAL(toggled(bool)), this, SLOT(leftWallChanged(bool))); + left->setChecked(bridge->leftWallVisible()); + right = new QCheckBox(i18n("&Right"), this); + layout->addWidget(right, 1, 2); + connect(right, SIGNAL(toggled(bool)), this, SLOT(rightWallChanged(bool))); + right->setChecked(bridge->rightWallVisible()); +} + +void BridgeConfig::topWallChanged(bool yes) +{ + bridge->setTopWallVisible(yes); + changed(); +} + +void BridgeConfig::botWallChanged(bool yes) +{ + bridge->setBotWallVisible(yes); + changed(); +} + +void BridgeConfig::leftWallChanged(bool yes) +{ + bridge->setLeftWallVisible(yes); + changed(); +} + +void BridgeConfig::rightWallChanged(bool yes) +{ + bridge->setRightWallVisible(yes); + changed(); +} + +///////////////////////// + +Bridge::Bridge(QRect rect, QCanvas *canvas) + : QCanvasRectangle(rect, canvas) +{ + QColor color("#92772D"); + setBrush(QBrush(color)); + setPen(NoPen); + setZ(998); + + topWall = new Wall(canvas); + topWall->setAlwaysShow(true); + botWall = new Wall(canvas); + botWall->setAlwaysShow(true); + leftWall = new Wall(canvas); + leftWall->setAlwaysShow(true); + rightWall = new Wall(canvas); + rightWall->setAlwaysShow(true); + + setWallZ(z() + 0.01); + setWallColor(color); + + topWall->setVisible(false); + botWall->setVisible(false); + leftWall->setVisible(false); + rightWall->setVisible(false); + + point = new RectPoint(color, this, canvas); + editModeChanged(false); + + newSize(width(), height()); +} + +bool Bridge::collision(Ball *ball, long int /*id*/) +{ + ball->setFrictionMultiplier(.63); + return false; +} + +void Bridge::setWallZ(double newz) +{ + topWall->setZ(newz); + botWall->setZ(newz); + leftWall->setZ(newz); + rightWall->setZ(newz); +} + +void Bridge::setGame(KolfGame *game) +{ + CanvasItem::setGame(game); + topWall->setGame(game); + botWall->setGame(game); + leftWall->setGame(game); + rightWall->setGame(game); +} + +void Bridge::setWallColor(QColor color) +{ + topWall->setPen(QPen(color.dark(), 3)); + botWall->setPen(topWall->pen()); + leftWall->setPen(topWall->pen()); + rightWall->setPen(topWall->pen()); +} + +void Bridge::aboutToDie() +{ + delete point; + topWall->aboutToDie(); + delete topWall; + botWall->aboutToDie(); + delete botWall; + leftWall->aboutToDie(); + delete leftWall; + rightWall->aboutToDie(); + delete rightWall; +} + +void Bridge::editModeChanged(bool changed) +{ + point->setVisible(changed); + moveBy(0, 0); +} + +void Bridge::moveBy(double dx, double dy) +{ + QCanvasRectangle::moveBy(dx, dy); + + point->dontMove(); + point->move(x() + width(), y() + height()); + + topWall->move(x(), y()); + botWall->move(x(), y() - 1); + leftWall->move(x(), y()); + rightWall->move(x(), y()); + + QCanvasItemList list = collisions(true); + for (QCanvasItemList::Iterator it = list.begin(); it != list.end(); ++it) + { + CanvasItem *citem = dynamic_cast<CanvasItem *>(*it); + if (citem) + citem->updateZ(); + } +} + +void Bridge::load(KConfig *cfg) +{ + doLoad(cfg); +} + +void Bridge::doLoad(KConfig *cfg) +{ + newSize(cfg->readNumEntry("width", width()), cfg->readNumEntry("height", height())); + setTopWallVisible(cfg->readBoolEntry("topWallVisible", topWallVisible())); + setBotWallVisible(cfg->readBoolEntry("botWallVisible", botWallVisible())); + setLeftWallVisible(cfg->readBoolEntry("leftWallVisible", leftWallVisible())); + setRightWallVisible(cfg->readBoolEntry("rightWallVisible", rightWallVisible())); +} + +void Bridge::save(KConfig *cfg) +{ + doSave(cfg); +} + +void Bridge::doSave(KConfig *cfg) +{ + cfg->writeEntry("width", width()); + cfg->writeEntry("height", height()); + cfg->writeEntry("topWallVisible", topWallVisible()); + cfg->writeEntry("botWallVisible", botWallVisible()); + cfg->writeEntry("leftWallVisible", leftWallVisible()); + cfg->writeEntry("rightWallVisible", rightWallVisible()); +} + +QPtrList<QCanvasItem> Bridge::moveableItems() const +{ + QPtrList<QCanvasItem> ret; + ret.append(point); + return ret; +} + +void Bridge::newSize(int width, int height) +{ + setSize(width, height); +} + +void Bridge::setSize(int width, int height) +{ + QCanvasRectangle::setSize(width, height); + + topWall->setPoints(0, 0, width, 0); + botWall->setPoints(0, height, width, height); + leftWall->setPoints(0, 0, 0, height); + rightWall->setPoints(width, 0, width, height); + + moveBy(0, 0); +} + +///////////////////////// + +WindmillConfig::WindmillConfig(Windmill *windmill, QWidget *parent) + : BridgeConfig(windmill, parent) +{ + this->windmill = windmill; + m_vlayout->addStretch(); + + QCheckBox *check = new QCheckBox(i18n("Windmill on bottom"), this); + check->setChecked(windmill->bottom()); + connect(check, SIGNAL(toggled(bool)), this, SLOT(endChanged(bool))); + m_vlayout->addWidget(check); + + QHBoxLayout *hlayout = new QHBoxLayout(m_vlayout, spacingHint()); + hlayout->addWidget(new QLabel(i18n("Slow"), this)); + QSlider *slider = new QSlider(1, 10, 1, windmill->curSpeed(), Qt::Horizontal, this); + hlayout->addWidget(slider); + hlayout->addWidget(new QLabel(i18n("Fast"), this)); + connect(slider, SIGNAL(valueChanged(int)), this, SLOT(speedChanged(int))); + + endChanged(check->isChecked()); +} + +void WindmillConfig::speedChanged(int news) +{ + windmill->setSpeed(news); + changed(); +} + +void WindmillConfig::endChanged(bool bottom) +{ + windmill->setBottom(bottom); + changed(); + + bot->setEnabled(!bottom); + if (startedUp) + { + bot->setChecked(!bottom); + botWallChanged(bot->isChecked()); + } + top->setEnabled(bottom); + if (startedUp) + { + top->setChecked(bottom); + topWallChanged(top->isChecked()); + } +} + +///////////////////////// + +Windmill::Windmill(QRect rect, QCanvas *canvas) + : Bridge(rect, canvas), speedfactor(16), m_bottom(true) +{ + guard = new WindmillGuard(canvas); + guard->setPen(QPen(black, 5)); + guard->setVisible(true); + guard->setAlwaysShow(true); + setSpeed(5); + guard->setZ(wallZ() + .1); + + left = new Wall(canvas); + left->setPen(wallPen()); + left->setAlwaysShow(true); + right = new Wall(canvas); + right->setPen(wallPen()); + right->setAlwaysShow(true); + left->setZ(wallZ()); + right->setZ(wallZ()); + left->setVisible(true); + right->setVisible(true); + + setTopWallVisible(false); + setBotWallVisible(false); + setLeftWallVisible(true); + setRightWallVisible(true); + + newSize(width(), height()); + moveBy(0, 0); +} + +void Windmill::aboutToDie() +{ + Bridge::aboutToDie(); + guard->aboutToDie(); + delete guard; + left->aboutToDie(); + delete left; + right->aboutToDie(); + delete right; +} + +void Windmill::setSpeed(int news) +{ + if (news < 0) + return; + speed = news; + guard->setXVelocity(((double)news / (double)3) * (guard->xVelocity() > 0? 1 : -1)); +} + +void Windmill::setGame(KolfGame *game) +{ + Bridge::setGame(game); + guard->setGame(game); + left->setGame(game); + right->setGame(game); +} + +void Windmill::save(KConfig *cfg) +{ + cfg->writeEntry("speed", speed); + cfg->writeEntry("bottom", m_bottom); + + doSave(cfg); +} + +void Windmill::load(KConfig *cfg) +{ + setSpeed(cfg->readNumEntry("speed", -1)); + + doLoad(cfg); + + left->editModeChanged(false); + right->editModeChanged(false); + guard->editModeChanged(false); + + setBottom(cfg->readBoolEntry("bottom", true)); +} + +void Windmill::moveBy(double dx, double dy) +{ + Bridge::moveBy(dx, dy); + + left->move(x(), y()); + right->move(x(), y()); + + guard->moveBy(dx, dy); + guard->setBetween(x(), x() + width()); + + update(); +} + +void Windmill::setSize(int width, int height) +{ + newSize(width, height); +} + +void Windmill::setBottom(bool yes) +{ + m_bottom = yes; + newSize(width(), height()); +} + +void Windmill::newSize(int width, int height) +{ + Bridge::newSize(width, height); + + const int indent = width / 4; + + double indentY = m_bottom? height : 0; + left->setPoints(0, indentY, indent, indentY); + right->setPoints(width - indent, indentY, width, indentY); + + guard->setBetween(x(), x() + width); + double guardY = m_bottom? height + 4 : -4; + guard->setPoints(0, guardY, (double)indent / (double)1.07 - 2, guardY); +} + +///////////////////////// + +void WindmillGuard::advance(int phase) +{ + Wall::advance(phase); + + if (phase == 1) + { + if (x() + startPoint().x() <= min) + setXVelocity(fabs(xVelocity())); + else if (x() + endPoint().x() >= max) + setXVelocity(-fabs(xVelocity())); + } +} + +///////////////////////// + +Sign::Sign(QCanvas *canvas) + : Bridge(QRect(0, 0, 110, 40), canvas) +{ + setZ(998.8); + m_text = m_untranslatedText = i18n("New Text"); + setBrush(QBrush(white)); + setWallColor(black); + setWallZ(z() + .01); + + setTopWallVisible(true); + setBotWallVisible(true); + setLeftWallVisible(true); + setRightWallVisible(true); +} + +void Sign::load(KConfig *cfg) +{ + m_text = cfg->readEntry("Comment", m_text); + m_untranslatedText = cfg->readEntryUntranslated("Comment", m_untranslatedText); + + doLoad(cfg); +} + +void Sign::save(KConfig *cfg) +{ + cfg->writeEntry("Comment", m_untranslatedText); + + doSave(cfg); +} + +void Sign::setText(const QString &text) +{ + m_text = text; + m_untranslatedText = text; + + update(); +} + +void Sign::draw(QPainter &painter) +{ + Bridge::draw(painter); + + painter.setPen(QPen(black, 1)); + QSimpleRichText txt(m_text, kapp->font()); + const int indent = wallPen().width() + 3; + txt.setWidth(width() - 2*indent); + QColorGroup colorGroup; + colorGroup.setColor(QColorGroup::Foreground, black); + colorGroup.setColor(QColorGroup::Text, black); + colorGroup.setColor(QColorGroup::Background, black); + colorGroup.setColor(QColorGroup::Base, black); + txt.draw(&painter, x() + indent, y(), QRect(x() + indent, y(), width() - indent, height() - indent), colorGroup); +} + +///////////////////////// + +SignConfig::SignConfig(Sign *sign, QWidget *parent) + : BridgeConfig(sign, parent) +{ + this->sign = sign; + m_vlayout->addStretch(); + m_vlayout->addWidget(new QLabel(i18n("Sign HTML:"), this)); + KLineEdit *name = new KLineEdit(sign->text(), this); + m_vlayout->addWidget(name); + connect(name, SIGNAL(textChanged(const QString &)), this, SLOT(textChanged(const QString &))); +} + +void SignConfig::textChanged(const QString &text) +{ + sign->setText(text); + changed(); +} + +///////////////////////// + +EllipseConfig::EllipseConfig(Ellipse *ellipse, QWidget *parent) + : Config(parent), slow1(0), fast1(0), slow2(0), fast2(0), slider1(0), slider2(0) +{ + this->ellipse = ellipse; + + m_vlayout = new QVBoxLayout(this, marginHint(), spacingHint()); + + QCheckBox *check = new QCheckBox(i18n("Enable show/hide"), this); + m_vlayout->addWidget(check); + connect(check, SIGNAL(toggled(bool)), this, SLOT(check1Changed(bool))); + check->setChecked(ellipse->changeEnabled()); + + QHBoxLayout *hlayout = new QHBoxLayout(m_vlayout, spacingHint()); + slow1 = new QLabel(i18n("Slow"), this); + hlayout->addWidget(slow1); + slider1 = new QSlider(1, 100, 5, 100 - ellipse->changeEvery(), Qt::Horizontal, this); + hlayout->addWidget(slider1); + fast1 = new QLabel(i18n("Fast"), this); + hlayout->addWidget(fast1); + + connect(slider1, SIGNAL(valueChanged(int)), this, SLOT(value1Changed(int))); + + check1Changed(ellipse->changeEnabled()); + + // TODO add slider2 and friends and make it possible for ellipses to grow and contract + + m_vlayout->addStretch(); +} + +void EllipseConfig::value1Changed(int news) +{ + ellipse->setChangeEvery(100 - news); + changed(); +} + +void EllipseConfig::value2Changed(int /*news*/) +{ + changed(); +} + +void EllipseConfig::check1Changed(bool on) +{ + ellipse->setChangeEnabled(on); + if (slider1) + slider1->setEnabled(on); + if (slow1) + slow1->setEnabled(on); + if (fast1) + fast1->setEnabled(on); + + changed(); +} + +void EllipseConfig::check2Changed(bool on) +{ + //ellipse->setChangeEnabled(on); + if (slider2) + slider2->setEnabled(on); + if (slow2) + slow2->setEnabled(on); + if (fast2) + fast2->setEnabled(on); + + changed(); +} + +///////////////////////// + +Ellipse::Ellipse(QCanvas *canvas) + : QCanvasEllipse(canvas) +{ + savingDone(); + setChangeEnabled(false); + setChangeEvery(50); + count = 0; + setVisible(true); + + point = new RectPoint(black, this, canvas); + point->setSizeFactor(2.0); +} + +void Ellipse::aboutToDie() +{ + delete point; +} + +void Ellipse::setChangeEnabled(bool changeEnabled) +{ + m_changeEnabled = changeEnabled; + setAnimated(m_changeEnabled); + + if (!m_changeEnabled) + setVisible(true); +} + +QPtrList<QCanvasItem> Ellipse::moveableItems() const +{ + QPtrList<QCanvasItem> ret; + ret.append(point); + return ret; +} + +void Ellipse::newSize(int width, int height) +{ + QCanvasEllipse::setSize(width, height); +} + +void Ellipse::moveBy(double dx, double dy) +{ + QCanvasEllipse::moveBy(dx, dy); + + point->dontMove(); + point->move(x() + width() / 2, y() + height() / 2); +} + +void Ellipse::editModeChanged(bool changed) +{ + point->setVisible(changed); + moveBy(0, 0); +} + +void Ellipse::advance(int phase) +{ + QCanvasEllipse::advance(phase); + + if (phase == 1 && m_changeEnabled && !dontHide) + { + if (count > (m_changeEvery + 10) * 1.8) + count = 0; + if (count == 0) + setVisible(!isVisible()); + + count++; + } +} + +void Ellipse::load(KConfig *cfg) +{ + setChangeEnabled(cfg->readBoolEntry("changeEnabled", changeEnabled())); + setChangeEvery(cfg->readNumEntry("changeEvery", changeEvery())); + double newWidth = width(), newHeight = height(); + newWidth = cfg->readNumEntry("width", newWidth); + newHeight = cfg->readNumEntry("height", newHeight); + newSize(newWidth, newHeight); +} + +void Ellipse::save(KConfig *cfg) +{ + cfg->writeEntry("changeEvery", changeEvery()); + cfg->writeEntry("changeEnabled", changeEnabled()); + cfg->writeEntry("width", width()); + cfg->writeEntry("height", height()); +} + +Config *Ellipse::config(QWidget *parent) +{ + return new EllipseConfig(this, parent); +} + +void Ellipse::aboutToSave() +{ + setVisible(true); + dontHide = true; +} + +void Ellipse::savingDone() +{ + dontHide = false; +} + +///////////////////////// + +Puddle::Puddle(QCanvas *canvas) + : Ellipse(canvas) +{ + setSize(45, 30); + + QBrush brush; + QPixmap pic; + + if (!QPixmapCache::find("puddle", pic)) + { + pic.load(locate("appdata", "pics/puddle.png")); + QPixmapCache::insert("puddle", pic); + } + + brush.setPixmap(pic); + setBrush(brush); + + KPixmap pointPic(pic); + KPixmapEffect::intensity(pointPic, .45); + brush.setPixmap(pointPic); + point->setBrush(brush); + + setZ(-25); +} + +bool Puddle::collision(Ball *ball, long int /*id*/) +{ + if (ball->isVisible()) + { + QCanvasRectangle i(QRect(ball->x(), ball->y(), 1, 1), canvas()); + i.setVisible(true); + + // is center of ball in? + if (i.collidesWith(this)/* && ball->curVector().magnitude() < 4*/) + { + playSound("puddle"); + ball->setAddStroke(ball->addStroke() + 1); + ball->setPlaceOnGround(true); + ball->setVisible(false); + ball->setState(Stopped); + ball->setVelocity(0, 0); + if (game && game->curBall() == ball) + game->stoppedBall(); + } + else + return true; + } + + return false; +} + +///////////////////////// + +Sand::Sand(QCanvas *canvas) + : Ellipse(canvas) +{ + setSize(45, 40); + + QBrush brush; + QPixmap pic; + + if (!QPixmapCache::find("sand", pic)) + { + pic.load(locate("appdata", "pics/sand.png")); + QPixmapCache::insert("sand", pic); + } + + brush.setPixmap(pic); + setBrush(brush); + + KPixmap pointPic(pic); + KPixmapEffect::intensity(pointPic, .45); + brush.setPixmap(pointPic); + point->setBrush(brush); + + setZ(-26); +} + +bool Sand::collision(Ball *ball, long int /*id*/) +{ + QCanvasRectangle i(QRect(ball->x(), ball->y(), 1, 1), canvas()); + i.setVisible(true); + + // is center of ball in? + if (i.collidesWith(this)/* && ball->curVector().magnitude() < 4*/) + { + if (ball->curVector().magnitude() > 0) + ball->setFrictionMultiplier(7); + else + { + ball->setVelocity(0, 0); + ball->setState(Stopped); + } + } + + return true; +} + +///////////////////////// + +Putter::Putter(QCanvas *canvas) + : QCanvasLine(canvas) +{ + m_showGuideLine = true; + oneDegree = M_PI / 180; + len = 9; + angle = 0; + + guideLine = new QCanvasLine(canvas); + guideLine->setPen(QPen(white, 1, QPen::DotLine)); + guideLine->setZ(998.8); + + setPen(QPen(black, 4)); + putterWidth = 11; + maxAngle = 2 * M_PI; + + hideInfo(); + + // this also sets Z + resetAngles(); +} + +void Putter::showInfo() +{ + guideLine->setVisible(isVisible()); +} + +void Putter::hideInfo() +{ + guideLine->setVisible(m_showGuideLine? isVisible() : false); +} + +void Putter::moveBy(double dx, double dy) +{ + QCanvasLine::moveBy(dx, dy); + guideLine->move(x(), y()); +} + +void Putter::setShowGuideLine(bool yes) +{ + m_showGuideLine = yes; + setVisible(isVisible()); +} + +void Putter::setVisible(bool yes) +{ + QCanvasLine::setVisible(yes); + guideLine->setVisible(m_showGuideLine? yes : false); +} + +void Putter::setOrigin(int _x, int _y) +{ + setVisible(true); + move(_x, _y); + len = 9; + finishMe(); +} + +void Putter::setAngle(Ball *ball) +{ + angle = angleMap.contains(ball)? angleMap[ball] : 0; + finishMe(); +} + +void Putter::go(Direction d, Amount amount) +{ + double addition = (amount == Amount_More? 6 * oneDegree : amount == Amount_Less? .5 * oneDegree : 2 * oneDegree); + + switch (d) + { + case Forwards: + len -= 1; + guideLine->setVisible(false); + break; + case Backwards: + len += 1; + guideLine->setVisible(false); + break; + case D_Left: + angle += addition; + if (angle > maxAngle) + angle -= maxAngle; + break; + case D_Right: + angle -= addition; + if (angle < 0) + angle = maxAngle - fabs(angle); + break; + } + + finishMe(); +} + +void Putter::finishMe() +{ + midPoint.setX(cos(angle) * len); + midPoint.setY(-sin(angle) * len); + + QPoint start; + QPoint end; + + if (midPoint.y() || !midPoint.x()) + { + start.setX(midPoint.x() - putterWidth * sin(angle)); + start.setY(midPoint.y() - putterWidth * cos(angle)); + end.setX(midPoint.x() + putterWidth * sin(angle)); + end.setY(midPoint.y() + putterWidth * cos(angle)); + } + else + { + start.setX(midPoint.x()); + start.setY(midPoint.y() + putterWidth); + end.setY(midPoint.y() - putterWidth); + end.setX(midPoint.x()); + } + + guideLine->setPoints(midPoint.x(), midPoint.y(), -cos(angle) * len * 4, sin(angle) * len * 4); + + setPoints(start.x(), start.y(), end.x(), end.y()); +} + +///////////////////////// + +Bumper::Bumper(QCanvas *canvas) + : QCanvasEllipse(20, 20, canvas) +{ + setZ(-25); + + firstColor = QColor("#E74804"); + secondColor = firstColor.light(); + + count = 0; + setBrush(firstColor); + setAnimated(false); + + inside = new Inside(this, canvas); + inside->setBrush(firstColor.light(109)); + inside->setSize(width() / 2.6, height() / 2.6); + inside->show(); +} + +void Bumper::aboutToDie() +{ + delete inside; +} + +void Bumper::moveBy(double dx, double dy) +{ + QCanvasEllipse::moveBy(dx, dy); + //const double insideLen = (double)(width() - inside->width()) / 2.0; + inside->move(x(), y()); +} + +void Bumper::editModeChanged(bool changed) +{ + inside->setVisible(!changed); +} + +void Bumper::advance(int phase) +{ + QCanvasEllipse::advance(phase); + + if (phase == 1) + { + count++; + if (count > 2) + { + count = 0; + setBrush(firstColor); + update(); + setAnimated(false); + } + } +} + +bool Bumper::collision(Ball *ball, long int /*id*/) +{ + setBrush(secondColor); + + double speed = 1.8 + ball->curVector().magnitude() * .9; + if (speed > 8) + speed = 8; + + const QPoint start(x(), y()); + const QPoint end(ball->x(), ball->y()); + + Vector betweenVector(start, end); + betweenVector.setMagnitude(speed); + + // add some randomness so we don't go indefinetely + betweenVector.setDirection(betweenVector.direction() + deg2rad((kapp->random() % 3) - 1)); + + ball->setVector(betweenVector); + // for some reason, x is always switched... + ball->setXVelocity(-ball->xVelocity()); + ball->setState(Rolling); + + setAnimated(true); + + return true; +} + +///////////////////////// + +Hole::Hole(QColor color, QCanvas *canvas) + : QCanvasEllipse(15, 15, canvas) +{ + setZ(998.1); + setPen(black); + setBrush(color); +} + +bool Hole::collision(Ball *ball, long int /*id*/) +{ + bool wasCenter = false; + + switch (result(QPoint(ball->x(), ball->y()), ball->curVector().magnitude(), &wasCenter)) + { + case Result_Holed: + place(ball, wasCenter); + return false; + + default: + break; + } + + return true; +} + +HoleResult Hole::result(QPoint p, double s, bool * /*wasCenter*/) +{ + const double longestRadius = width() > height()? width() : height(); + if (s > longestRadius / 5.0) + return Result_Miss; + + QCanvasRectangle i(QRect(p, QSize(1, 1)), canvas()); + i.setVisible(true); + + // is center of ball in cup? + if (i.collidesWith(this)) + { + return Result_Holed; + } + else + return Result_Miss; +} + +///////////////////////// + +Cup::Cup(QCanvas *canvas) + : Hole(QColor("#808080"), canvas) +{ + if (!QPixmapCache::find("cup", pixmap)) + { + pixmap.load(locate("appdata", "pics/cup.png")); + QPixmapCache::insert("cup", pixmap); + } +} + +void Cup::draw(QPainter &p) +{ + p.drawPixmap(QPoint(x() - width() / 2, y() - height() / 2), pixmap); +} + +bool Cup::place(Ball *ball, bool /*wasCenter*/) +{ + ball->setState(Holed); + playSound("holed"); + + // the picture's center is a little different + ball->move(x() - 1, y()); + ball->setVelocity(0, 0); + if (game && game->curBall() == ball) + game->stoppedBall(); + return true; +} + +void Cup::save(KConfig *cfg) +{ + cfg->writeEntry("dummykey", true); +} + +///////////////////////// + +BlackHole::BlackHole(QCanvas *canvas) + : Hole(black, canvas), exitDeg(0) +{ + infoLine = 0; + m_minSpeed = 3.0; + m_maxSpeed = 5.0; + runs = 0; + + const QColor myColor((QRgb)(kapp->random() % 0x01000000)); + + outside = new QCanvasEllipse(canvas); + outside->setZ(z() - .001); + + outside->setBrush(QBrush(myColor)); + setBrush(black); + + exitItem = new BlackHoleExit(this, canvas); + exitItem->setPen(QPen(myColor, 6)); + exitItem->setX(300); + exitItem->setY(100); + + setSize(width(), width() / .8); + const float factor = 1.3; + outside->setSize(width() * factor, height() * factor); + outside->setVisible(true); + + moveBy(0, 0); + + finishMe(); +} + +void BlackHole::showInfo() +{ + delete infoLine; + infoLine = new QCanvasLine(canvas()); + infoLine->setVisible(true); + infoLine->setPen(QPen(exitItem->pen().color(), 2)); + infoLine->setZ(10000); + infoLine->setPoints(x(), y(), exitItem->x(), exitItem->y()); + + exitItem->showInfo(); +} + +void BlackHole::hideInfo() +{ + delete infoLine; + infoLine = 0; + + exitItem->hideInfo(); +} + +void BlackHole::aboutToDie() +{ + Hole::aboutToDie(); + delete outside; + exitItem->aboutToDie(); + delete exitItem; +} + +void BlackHole::updateInfo() +{ + if (infoLine) + { + infoLine->setVisible(true); + infoLine->setPoints(x(), y(), exitItem->x(), exitItem->y()); + exitItem->showInfo(); + } +} + +void BlackHole::moveBy(double dx, double dy) +{ + QCanvasEllipse::moveBy(dx, dy); + outside->move(x(), y()); + updateInfo(); +} + +void BlackHole::setExitDeg(int newdeg) +{ + exitDeg = newdeg; + if (game && game->isEditing() && game->curSelectedItem() == exitItem) + game->updateHighlighter(); + + exitItem->updateArrowAngle(); + finishMe(); +} + +QPtrList<QCanvasItem> BlackHole::moveableItems() const +{ + QPtrList<QCanvasItem> ret; + ret.append(exitItem); + return ret; +} + +BlackHoleTimer::BlackHoleTimer(Ball *ball, double speed, int msec) + : m_speed(speed), m_ball(ball) +{ + QTimer::singleShot(msec, this, SLOT(mySlot())); + QTimer::singleShot(msec / 2, this, SLOT(myMidSlot())); +} + +void BlackHoleTimer::mySlot() +{ + emit eject(m_ball, m_speed); + delete this; +} + +void BlackHoleTimer::myMidSlot() +{ + emit halfway(); +} + +bool BlackHole::place(Ball *ball, bool /*wasCenter*/) +{ + // most number is 10 + if (runs > 10 && game && game->isInPlay()) + return false; + + playSound("blackholeputin"); + + const double diff = (m_maxSpeed - m_minSpeed); + const double speed = m_minSpeed + ball->curVector().magnitude() * (diff / 3.75); + + ball->setVelocity(0, 0); + ball->setState(Stopped); + ball->setVisible(false); + ball->setForceStillGoing(true); + + double magnitude = Vector(QPoint(x(), y()), QPoint(exitItem->x(), exitItem->y())).magnitude(); + BlackHoleTimer *timer = new BlackHoleTimer(ball, speed, magnitude * 2.5 - speed * 35 + 500); + + connect(timer, SIGNAL(eject(Ball *, double)), this, SLOT(eject(Ball *, double))); + connect(timer, SIGNAL(halfway()), this, SLOT(halfway())); + + playSound("blackhole"); + return false; +} + +void BlackHole::eject(Ball *ball, double speed) +{ + ball->move(exitItem->x(), exitItem->y()); + + Vector v; + v.setMagnitude(10); + v.setDirection(deg2rad(exitDeg)); + ball->setVector(v); + + // advance ball 10 + ball->doAdvance(); + + v.setMagnitude(speed); + ball->setVector(v); + + ball->setForceStillGoing(false); + ball->setVisible(true); + ball->setState(Rolling); + + runs++; + + playSound("blackholeeject"); +} + +void BlackHole::halfway() +{ + playSound("blackhole"); +} + +void BlackHole::load(KConfig *cfg) +{ + QPoint exit = cfg->readPointEntry("exit", &exit); + exitItem->setX(exit.x()); + exitItem->setY(exit.y()); + exitDeg = cfg->readNumEntry("exitDeg", exitDeg); + m_minSpeed = cfg->readDoubleNumEntry("minspeed", m_minSpeed); + m_maxSpeed = cfg->readDoubleNumEntry("maxspeed", m_maxSpeed); + exitItem->updateArrowAngle(); + exitItem->updateArrowLength(); + + finishMe(); +} + +void BlackHole::finishMe() +{ + double radians = deg2rad(exitDeg); + QPoint midPoint(0, 0); + QPoint start; + QPoint end; + const int width = 15; + + if (midPoint.y() || !midPoint.x()) + { + start.setX(midPoint.x() - width*sin(radians)); + start.setY(midPoint.y() - width*cos(radians)); + end.setX(midPoint.x() + width*sin(radians)); + end.setY(midPoint.y() + width*cos(radians)); + } + else + { + start.setX(midPoint.x()); + start.setY(midPoint.y() + width); + end.setY(midPoint.y() - width); + end.setX(midPoint.x()); + } + + exitItem->setPoints(start.x(), start.y(), end.x(), end.y()); + exitItem->setVisible(true); +} + +void BlackHole::save(KConfig *cfg) +{ + cfg->writeEntry("exit", QPoint(exitItem->x(), exitItem->y())); + cfg->writeEntry("exitDeg", exitDeg); + cfg->writeEntry("minspeed", m_minSpeed); + cfg->writeEntry("maxspeed", m_maxSpeed); +} + +///////////////////////// + +BlackHoleExit::BlackHoleExit(BlackHole *blackHole, QCanvas *canvas) + : QCanvasLine(canvas) +{ + this->blackHole = blackHole; + arrow = new Arrow(canvas); + setZ(blackHole->z()); + arrow->setZ(z() - .00001); + updateArrowLength(); + arrow->setVisible(false); +} + +void BlackHoleExit::aboutToDie() +{ + arrow->aboutToDie(); + delete arrow; +} + +void BlackHoleExit::moveBy(double dx, double dy) +{ + QCanvasLine::moveBy(dx, dy); + arrow->move(x(), y()); + blackHole->updateInfo(); +} + +void BlackHoleExit::setPen(QPen p) +{ + QCanvasLine::setPen(p); + arrow->setPen(QPen(p.color(), 1)); +} + +void BlackHoleExit::updateArrowAngle() +{ + // arrows work in a different angle system + arrow->setAngle(-deg2rad(blackHole->curExitDeg())); + arrow->updateSelf(); +} + +void BlackHoleExit::updateArrowLength() +{ + arrow->setLength(10.0 + 5.0 * (double)(blackHole->minSpeed() + blackHole->maxSpeed()) / 2.0); + arrow->updateSelf(); +} + +void BlackHoleExit::editModeChanged(bool editing) +{ + if (editing) + showInfo(); + else + hideInfo(); +} + +void BlackHoleExit::showInfo() +{ + arrow->setVisible(true); +} + +void BlackHoleExit::hideInfo() +{ + arrow->setVisible(false); +} + +Config *BlackHoleExit::config(QWidget *parent) +{ + return blackHole->config(parent); +} + +///////////////////////// + +BlackHoleConfig::BlackHoleConfig(BlackHole *blackHole, QWidget *parent) + : Config(parent) +{ + this->blackHole = blackHole; + QVBoxLayout *layout = new QVBoxLayout(this, marginHint(), spacingHint()); + layout->addWidget(new QLabel(i18n("Exiting ball angle:"), this)); + QSpinBox *deg = new QSpinBox(0, 359, 10, this); + deg->setSuffix(QString(" ") + i18n("degrees")); + deg->setValue(blackHole->curExitDeg()); + deg->setWrapping(true); + layout->addWidget(deg); + connect(deg, SIGNAL(valueChanged(int)), this, SLOT(degChanged(int))); + + layout->addStretch(); + + QHBoxLayout *hlayout = new QHBoxLayout(layout, spacingHint()); + hlayout->addWidget(new QLabel(i18n("Minimum exit speed:"), this)); + KDoubleNumInput *min = new KDoubleNumInput(this); + min->setRange(0, 8, 1, true); + hlayout->addWidget(min); + connect(min, SIGNAL(valueChanged(double)), this, SLOT(minChanged(double))); + min->setValue(blackHole->minSpeed()); + + hlayout = new QHBoxLayout(layout, spacingHint()); + hlayout->addWidget(new QLabel(i18n("Maximum:"), this)); + KDoubleNumInput *max = new KDoubleNumInput(this); + max->setRange(1, 10, 1, true); + hlayout->addWidget(max); + connect(max, SIGNAL(valueChanged(double)), this, SLOT(maxChanged(double))); + max->setValue(blackHole->maxSpeed()); +} + +void BlackHoleConfig::degChanged(int newdeg) +{ + blackHole->setExitDeg(newdeg); + changed(); +} + +void BlackHoleConfig::minChanged(double news) +{ + blackHole->setMinSpeed(news); + changed(); +} + +void BlackHoleConfig::maxChanged(double news) +{ + blackHole->setMaxSpeed(news); + changed(); +} + +///////////////////////// + +WallPoint::WallPoint(bool start, Wall *wall, QCanvas *canvas) + : QCanvasEllipse(canvas) +{ + this->wall = wall; + this->start = start; + alwaysShow = false; + editing = false; + visible = true; + lastId = INT_MAX - 10; + dontmove = false; + + move(0, 0); + QPoint p; + if (start) + p = wall->startPoint(); + else + p = wall->endPoint(); + setX(p.x()); + setY(p.y()); +} + +void WallPoint::clean() +{ + int oldWidth = width(); + setSize(7, 7); + update(); + + QCanvasItem *onPoint = 0; + QCanvasItemList l = collisions(true); + for (QCanvasItemList::Iterator it = l.begin(); it != l.end(); ++it) + if ((*it)->rtti() == rtti()) + onPoint = (*it); + + if (onPoint) + move(onPoint->x(), onPoint->y()); + + setSize(oldWidth, oldWidth); +} + +void WallPoint::moveBy(double dx, double dy) +{ + QCanvasEllipse::moveBy(dx, dy); + if (!editing) + updateVisible(); + + if (dontmove) + { + dontmove = false; + return; + } + + if (!wall) + return; + + if (start) + { + wall->setPoints(x(), y(), wall->endPoint().x() + wall->x(), wall->endPoint().y() + wall->y()); + } + else + { + wall->setPoints(wall->startPoint().x() + wall->x(), wall->startPoint().y() + wall->y(), x(), y()); + } + wall->move(0, 0); +} + +void WallPoint::updateVisible() +{ + if (!wall->isVisible()) + { + visible = false; + return; + } + + if (alwaysShow) + visible = true; + else + { + visible = true; + QCanvasItemList l = collisions(true); + for (QCanvasItemList::Iterator it = l.begin(); it != l.end(); ++it) + if ((*it)->rtti() == rtti()) + visible = false; + } +} + +void WallPoint::editModeChanged(bool changed) +{ + editing = changed; + setVisible(true); + if (!editing) + updateVisible(); +} + +bool WallPoint::collision(Ball *ball, long int id) +{ + if (ball->curVector().magnitude() <= 0) + return false; + + long int tempLastId = lastId; + lastId = id; + QCanvasItemList l = collisions(true); + for (QCanvasItemList::Iterator it = l.begin(); it != l.end(); ++it) + { + if ((*it)->rtti() == rtti()) + { + WallPoint *point = (WallPoint *)(*it); + point->lastId = id; + } + } + + //kdDebug(12007) << "WallPoint::collision id: " << id << ", tempLastId: " << tempLastId << endl; + Vector ballVector(ball->curVector()); + + //kdDebug(12007) << "Wall::collision ball speed: " << ball->curVector().magnitude() << endl; + int allowableDifference = 1; + if (ballVector.magnitude() < .30) + allowableDifference = 8; + else if (ballVector.magnitude() < .50) + allowableDifference = 6; + else if (ballVector.magnitude() < .65) + allowableDifference = 4; + else if (ballVector.magnitude() < .95) + allowableDifference = 2; + + if (abs(id - tempLastId) <= allowableDifference) + { + //kdDebug(12007) << "WallPoint::collision - SKIP\n"; + } + else + { + bool weirdBounce = visible; + + QPoint relStart(start? wall->startPoint() : wall->endPoint()); + QPoint relEnd(start? wall->endPoint() : wall->startPoint()); + Vector wallVector(relStart, relEnd); + wallVector.setDirection(-wallVector.direction()); + + // find the angle between vectors, between 0 and PI + { + double difference = fabs(wallVector.direction() - ballVector.direction()); + while (difference > 2 * M_PI) + difference -= 2 * M_PI; + + if (difference < M_PI / 2 || difference > 3 * M_PI / 2) + weirdBounce = false; + } + + playSound("wall", ball->curVector().magnitude() / 10.0); + + ballVector /= wall->dampening; + const double ballAngle = ballVector.direction(); + + double wallAngle = wallVector.direction(); + + // opposite bounce, because we're the endpoint + if (weirdBounce) + wallAngle += M_PI / 2; + + const double collisionAngle = ballAngle - wallAngle; + const double leavingAngle = wallAngle - collisionAngle; + + ballVector.setDirection(leavingAngle); + ball->setVector(ballVector); + wall->lastId = id; + + //kdDebug(12007) << "WallPoint::collision - NOT skip, weirdBounce is " << weirdBounce << endl; + } // end if that skips + + wall->lastId = id; + return false; +} + +///////////////////////// + +Wall::Wall(QCanvas *canvas) + : QCanvasLine(canvas) +{ + editing = false; + lastId = INT_MAX - 10; + + dampening = 1.2; + + startItem = 0; + endItem = 0; + + moveBy(0, 0); + setZ(50); + + startItem = new WallPoint(true, this, canvas); + endItem = new WallPoint(false, this, canvas); + startItem->setVisible(true); + endItem->setVisible(true); + setPen(QPen(darkRed, 3)); + + setPoints(-15, 10, 15, -5); + + moveBy(0, 0); + + editModeChanged(false); +} + +void Wall::selectedItem(QCanvasItem *item) +{ + if (item->rtti() == Rtti_WallPoint) + { + WallPoint *wallPoint = dynamic_cast<WallPoint *>(item); + if (wallPoint) { + setPoints(startPoint().x(), startPoint().y(), wallPoint->x() - x(), wallPoint->y() - y()); + } + } +} + +void Wall::clean() +{ + startItem->clean(); + endItem->clean(); +} + +void Wall::setAlwaysShow(bool yes) +{ + startItem->setAlwaysShow(yes); + endItem->setAlwaysShow(yes); +} + +void Wall::setVisible(bool yes) +{ + QCanvasLine::setVisible(yes); + + startItem->setVisible(yes); + endItem->setVisible(yes); + startItem->updateVisible(); + endItem->updateVisible(); +} + +void Wall::setZ(double newz) +{ + QCanvasLine::setZ(newz); + if (startItem) + startItem->setZ(newz + .002); + if (endItem) + endItem->setZ(newz + .001); +} + +void Wall::setPen(QPen p) +{ + QCanvasLine::setPen(p); + + if (startItem) + startItem->setBrush(QBrush(p.color())); + if (endItem) + endItem->setBrush(QBrush(p.color())); +} + +void Wall::aboutToDie() +{ + delete startItem; + delete endItem; +} + +void Wall::setGame(KolfGame *game) +{ + CanvasItem::setGame(game); + startItem->setGame(game); + endItem->setGame(game); +} + +QPtrList<QCanvasItem> Wall::moveableItems() const +{ + QPtrList<QCanvasItem> ret; + ret.append(startItem); + ret.append(endItem); + return ret; +} + +void Wall::moveBy(double dx, double dy) +{ + QCanvasLine::moveBy(dx, dy); + + if (!startItem || !endItem) + return; + + startItem->dontMove(); + endItem->dontMove(); + startItem->move(startPoint().x() + x(), startPoint().y() + y()); + endItem->move(endPoint().x() + x(), endPoint().y() + y()); +} + +void Wall::setVelocity(double vx, double vy) +{ + QCanvasLine::setVelocity(vx, vy); + /* + startItem->setVelocity(vx, vy); + endItem->setVelocity(vx, vy); + */ +} + +QPointArray Wall::areaPoints() const +{ + // editing we want full width for easy moving + if (editing) + return QCanvasLine::areaPoints(); + + // lessen width, for QCanvasLine::areaPoints() likes + // to make lines _very_ fat :( + // from qcanvas.cpp, only the stuff for a line width of 1 taken + + // it's all squished because I don't want my + // line counts to count code I didn't write! + QPointArray p(4); const int xi = int(x()); const int yi = int(y()); const QPoint start = startPoint(); const QPoint end = endPoint(); const int x1 = start.x(); const int x2 = end.x(); const int y1 = start.y(); const int y2 = end.y(); const int dx = QABS(x1-x2); const int dy = QABS(y1-y2); if ( dx > dy ) { p[0] = QPoint(x1+xi,y1+yi-1); p[1] = QPoint(x2+xi,y2+yi-1); p[2] = QPoint(x2+xi,y2+yi+1); p[3] = QPoint(x1+xi,y1+yi+1); } else { p[0] = QPoint(x1+xi-1,y1+yi); p[1] = QPoint(x2+xi-1,y2+yi); p[2] = QPoint(x2+xi+1,y2+yi); p[3] = QPoint(x1+xi+1,y1+yi); } return p; +} + +void Wall::editModeChanged(bool changed) +{ + // make big for debugging? + const bool debugPoints = false; + + editing = changed; + + startItem->setZ(z() + .002); + endItem->setZ(z() + .001); + startItem->editModeChanged(editing); + endItem->editModeChanged(editing); + + int neww = 0; + if (changed || debugPoints) + neww = 10; + else + neww = pen().width(); + + startItem->setSize(neww, neww); + endItem->setSize(neww, neww); + + moveBy(0, 0); +} + +bool Wall::collision(Ball *ball, long int id) +{ + if (ball->curVector().magnitude() <= 0) + return false; + + long int tempLastId = lastId; + lastId = id; + startItem->lastId = id; + endItem->lastId = id; + + //kdDebug(12007) << "Wall::collision id: " << id << ", tempLastId: " << tempLastId << endl; + Vector ballVector(ball->curVector()); + + //kdDebug(12007) << "Wall::collision ball speed: " << ball->curVector().magnitude() << endl; + int allowableDifference = 1; + if (ballVector.magnitude() < .30) + allowableDifference = 8; + else if (ballVector.magnitude() < .50) + allowableDifference = 6; + else if (ballVector.magnitude() < .75) + allowableDifference = 4; + else if (ballVector.magnitude() < .95) + allowableDifference = 2; + //kdDebug(12007) << "Wall::collision allowableDifference is " << allowableDifference << endl; + if (abs(id - tempLastId) <= allowableDifference) + { + //kdDebug(12007) << "Wall::collision - SKIP\n"; + return false; + } + + playSound("wall", ball->curVector().magnitude() / 10.0); + + ballVector /= dampening; + const double ballAngle = ballVector.direction(); + + const double wallAngle = -Vector(startPoint(), endPoint()).direction(); + const double collisionAngle = ballAngle - wallAngle; + const double leavingAngle = wallAngle - collisionAngle; + + ballVector.setDirection(leavingAngle); + ball->setVector(ballVector); + + //kdDebug(12007) << "Wall::collision - NOT skip\n"; + return false; +} + +void Wall::load(KConfig *cfg) +{ + QPoint start(startPoint()); + start = cfg->readPointEntry("startPoint", &start); + QPoint end(endPoint()); + end = cfg->readPointEntry("endPoint", &end); + + setPoints(start.x(), start.y(), end.x(), end.y()); + + moveBy(0, 0); + startItem->move(start.x(), start.y()); + endItem->move(end.x(), end.y()); +} + +void Wall::save(KConfig *cfg) +{ + cfg->writeEntry("startPoint", QPoint(startItem->x(), startItem->y())); + cfg->writeEntry("endPoint", QPoint(endItem->x(), endItem->y())); +} + +///////////////////////// + +HoleConfig::HoleConfig(HoleInfo *holeInfo, QWidget *parent) + : Config(parent) +{ + this->holeInfo = holeInfo; + + QVBoxLayout *layout = new QVBoxLayout(this, marginHint(), spacingHint()); + + QHBoxLayout *hlayout = new QHBoxLayout(layout, spacingHint()); + hlayout->addWidget(new QLabel(i18n("Course name: "), this)); + KLineEdit *nameEdit = new KLineEdit(holeInfo->untranslatedName(), this); + hlayout->addWidget(nameEdit); + connect(nameEdit, SIGNAL(textChanged(const QString &)), this, SLOT(nameChanged(const QString &))); + + hlayout = new QHBoxLayout(layout, spacingHint()); + hlayout->addWidget(new QLabel(i18n("Course author: "), this)); + KLineEdit *authorEdit = new KLineEdit(holeInfo->author(), this); + hlayout->addWidget(authorEdit); + connect(authorEdit, SIGNAL(textChanged(const QString &)), this, SLOT(authorChanged(const QString &))); + + layout->addStretch(); + + hlayout = new QHBoxLayout(layout, spacingHint()); + hlayout->addWidget(new QLabel(i18n("Par:"), this)); + QSpinBox *par = new QSpinBox(1, 15, 1, this); + par->setValue(holeInfo->par()); + hlayout->addWidget(par); + connect(par, SIGNAL(valueChanged(int)), this, SLOT(parChanged(int))); + hlayout->addStretch(); + + hlayout->addWidget(new QLabel(i18n("Maximum:"), this)); + QSpinBox *maxstrokes = new QSpinBox(holeInfo->lowestMaxStrokes(), 30, 1, this); + QWhatsThis::add(maxstrokes, i18n("Maximum number of strokes player can take on this hole.")); + QToolTip::add(maxstrokes, i18n("Maximum number of strokes")); + maxstrokes->setSpecialValueText(i18n("Unlimited")); + maxstrokes->setValue(holeInfo->maxStrokes()); + hlayout->addWidget(maxstrokes); + connect(maxstrokes, SIGNAL(valueChanged(int)), this, SLOT(maxStrokesChanged(int))); + + QCheckBox *check = new QCheckBox(i18n("Show border walls"), this); + check->setChecked(holeInfo->borderWalls()); + layout->addWidget(check); + connect(check, SIGNAL(toggled(bool)), this, SLOT(borderWallsChanged(bool))); +} + +void HoleConfig::authorChanged(const QString &newauthor) +{ + holeInfo->setAuthor(newauthor); + changed(); +} + +void HoleConfig::nameChanged(const QString &newname) +{ + holeInfo->setName(newname); + holeInfo->setUntranslatedName(newname); + changed(); +} + +void HoleConfig::parChanged(int newpar) +{ + holeInfo->setPar(newpar); + changed(); +} + +void HoleConfig::maxStrokesChanged(int newms) +{ + holeInfo->setMaxStrokes(newms); + changed(); +} + +void HoleConfig::borderWallsChanged(bool yes) +{ + holeInfo->borderWallsChanged(yes); + changed(); +} + +///////////////////////// + +StrokeCircle::StrokeCircle(QCanvas *canvas) + : QCanvasItem(canvas) +{ + dvalue = 0; + dmax = 360; + iwidth = 100; + iheight = 100; + ithickness = 8; + setZ(10000); +} + +void StrokeCircle::setValue(double v) +{ + dvalue = v; + if (dvalue > dmax) + dvalue = dmax; + + update(); +} + +double StrokeCircle::value() +{ + return dvalue; +} + +bool StrokeCircle::collidesWith(const QCanvasItem*) const { return false; } + +bool StrokeCircle::collidesWith(const QCanvasSprite*, const QCanvasPolygonalItem*, const QCanvasRectangle*, const QCanvasEllipse*, const QCanvasText*) const { return false; } + +QRect StrokeCircle::boundingRect() const { return QRect(x(), y(), iwidth, iheight); } + +void StrokeCircle::setMaxValue(double m) +{ + dmax = m; + if (dvalue > dmax) + dvalue = dmax; + + update(); +} +void StrokeCircle::setSize(int w, int h) +{ + if (w > 0) + iwidth = w; + if (h > 0) + iheight = h; + + update(); +} +void StrokeCircle::setThickness(int t) +{ + if (t > 0) + ithickness = t; + + update(); +} + +int StrokeCircle::thickness() const +{ + return ithickness; +} + +int StrokeCircle::width() const +{ + return iwidth; +} + +int StrokeCircle::height() const +{ + return iheight; +} + +void StrokeCircle::draw(QPainter &p) +{ + int al = (int)((dvalue * 360 * 16) / dmax); + int length, deg; + if (al < 0) + { + deg = 270 * 16; + length = -al; + } + else if (al <= (270 * 16)) + { + deg = 270 * 16 - al; + length = al; + } + else + { + deg = (360 * 16) - (al - (270 * 16)); + length = al; + } + + p.setBrush(QBrush(black, Qt::NoBrush)); + p.setPen(QPen(white, ithickness / 2)); + p.drawEllipse(x() + ithickness / 2, y() + ithickness / 2, iwidth - ithickness, iheight - ithickness); + p.setPen(QPen(QColor((int)(0xff * dvalue) / dmax, 0, 0xff - (int)(0xff * dvalue) / dmax), ithickness)); + p.drawArc(x() + ithickness / 2, y() + ithickness / 2, iwidth - ithickness, iheight - ithickness, deg, length); + + p.setPen(QPen(white, 1)); + p.drawEllipse(x(), y(), iwidth, iheight); + p.drawEllipse(x() + ithickness, y() + ithickness, iwidth - ithickness * 2, iheight - ithickness * 2); + p.setPen(QPen(white, 3)); + p.drawLine(x() + iwidth / 2, y() + iheight - ithickness * 1.5, x() + iwidth / 2, y() + iheight); + p.drawLine(x() + iwidth / 4 - iwidth / 20, y() + iheight - iheight / 4 + iheight / 20, x() + iwidth / 4 + iwidth / 20, y() + iheight - iheight / 4 - iheight / 20); + p.drawLine(x() + iwidth - iwidth / 4 + iwidth / 20, y() + iheight - iheight / 4 + iheight / 20, x() + iwidth - iwidth / 4 - iwidth / 20, y() + iheight - iheight / 4 - iheight / 20); +} + +///////////////////////////////////////// + +KolfGame::KolfGame(ObjectList *obj, PlayerList *players, QString filename, QWidget *parent, const char *name ) + : QCanvasView(parent, name) +{ + // for mouse control + setMouseTracking(true); + viewport()->setMouseTracking(true); + setFrameShape(NoFrame); + + regAdv = false; + curHole = 0; // will get ++'d + cfg = 0; + setFilename(filename); + this->players = players; + this->obj = obj; + curPlayer = players->end(); + curPlayer--; // will get ++'d to end and sent back + // to beginning + paused = false; + modified = false; + inPlay = false; + putting = false; + stroking = false; + editing = false; + strict = false; + lastDelId = -1; + m_showInfo = false; + ballStateList.canUndo = false; + fastAdvancedExist = false; + soundDir = locate("appdata", "sounds/"); + dontAddStroke = false; + addingNewHole = false; + scoreboardHoles = 0; + infoShown = false; + m_useMouse = true; + m_useAdvancedPutting = false; + m_useAdvancedPutting = true; + m_sound = true; + m_ignoreEvents = false; + soundedOnce = false; + oldPlayObjects.setAutoDelete(true); + highestHole = 0; + recalcHighestHole = false; + + holeInfo.setGame(this); + holeInfo.setAuthor(i18n("Course Author")); + holeInfo.setName(i18n("Course Name")); + holeInfo.setUntranslatedName(i18n("Course Name")); + holeInfo.setMaxStrokes(10); + holeInfo.borderWallsChanged(true); + + // width and height are the width and height of the canvas + // in easy storage + width = 400; + height = 400; + grass = QColor("#35760D"); + + margin = 10; + + setFocusPolicy(QWidget::StrongFocus); + setFixedSize(width + 2 * margin, height + 2 * margin); + + setMargins(margin, margin, margin, margin); + + course = new QCanvas(this); + course->setBackgroundColor(white); + course->resize(width, height); + + QPixmap pic; + if (!QPixmapCache::find("grass", pic)) + { + pic.load(locate("appdata", "pics/grass.png")); + QPixmapCache::insert("grass", pic); + } + course->setBackgroundPixmap(pic); + + setCanvas(course); + move(0, 0); + adjustSize(); + + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + (*it).ball()->setCanvas(course); + + // highlighter shows current item + highlighter = new QCanvasRectangle(course); + highlighter->setPen(QPen(yellow, 1)); + highlighter->setBrush(QBrush(NoBrush)); + highlighter->setVisible(false); + highlighter->setZ(10000); + + // shows some info about hole + infoText = new QCanvasText(course); + infoText->setText(""); + infoText->setColor(white); + QFont font = kapp->font(); + font.setPixelSize(12); + infoText->move(15, width/2); + infoText->setZ(10001); + infoText->setFont(font); + infoText->setVisible(false); + + // create the advanced putting indicator + strokeCircle = new StrokeCircle(course); + strokeCircle->move(width - 90, height - 90); + strokeCircle->setSize(80, 80); + strokeCircle->setThickness(8); + strokeCircle->setVisible(false); + strokeCircle->setValue(0); + strokeCircle->setMaxValue(360); + + // whiteBall marks the spot of the whole whilst editing + whiteBall = new Ball(course); + whiteBall->setGame(this); + whiteBall->setColor(white); + whiteBall->setVisible(false); + whiteBall->setDoDetect(false); + + int highestLog = 0; + + // if players have scores from loaded game, move to last hole + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + { + if ((int)(*it).scores().count() > highestLog) + highestLog = (*it).scores().count(); + + (*it).ball()->setGame(this); + (*it).ball()->setAnimated(true); + } + + // here only for saved games + if (highestLog) + curHole = highestLog; + + putter = new Putter(course); + + // border walls: + + // horiz + addBorderWall(QPoint(margin, margin), QPoint(width - margin, margin)); + addBorderWall(QPoint(margin, height - margin - 1), QPoint(width - margin, height - margin - 1)); + + // vert + addBorderWall(QPoint(margin, margin), QPoint(margin, height - margin)); + addBorderWall(QPoint(width - margin - 1, margin), QPoint(width - margin - 1, height - margin)); + + timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), this, SLOT(timeout())); + timerMsec = 300; + + fastTimer = new QTimer(this); + connect(fastTimer, SIGNAL(timeout()), this, SLOT(fastTimeout())); + fastTimerMsec = 11; + + autoSaveTimer = new QTimer(this); + connect(autoSaveTimer, SIGNAL(timeout()), this, SLOT(autoSaveTimeout())); + autoSaveMsec = 5 * 1000 * 60; // 5 min autosave + + // setUseAdvancedPutting() sets maxStrength! + setUseAdvancedPutting(false); + + putting = false; + putterTimer = new QTimer(this); + connect(putterTimer, SIGNAL(timeout()), this, SLOT(putterTimeout())); + putterTimerMsec = 20; +} + +void KolfGame::startFirstHole(int hole) +{ + if (curHole > 0) // if there was saved game, sync scoreboard + // with number of holes + { + for (; scoreboardHoles < curHole; ++scoreboardHoles) + { + cfg->setGroup(QString("%1-hole@-50,-50|0").arg(scoreboardHoles + 1)); + emit newHole(cfg->readNumEntry("par", 3)); + } + + // lets load all of the scores from saved game if there are any + for (int hole = 1; hole <= curHole; ++hole) + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + emit scoreChanged((*it).id(), hole, (*it).score(hole)); + } + + curHole = hole - 1; + + // this increments curHole, etc + recalcHighestHole = true; + startNextHole(); + paused = true; + unPause(); +} + +void KolfGame::setFilename(const QString &filename) +{ + this->filename = filename; + delete cfg; + cfg = new KConfig(filename, false, false); +} + +KolfGame::~KolfGame() +{ + oldPlayObjects.clear(); + delete cfg; +} + +void KolfGame::setModified(bool mod) +{ + modified = mod; + emit modifiedChanged(mod); +} + +void KolfGame::pause() +{ + if (paused) + { + // play along with people who call pause() again, instead of unPause() + unPause(); + return; + } + + paused = true; + timer->stop(); + fastTimer->stop(); + putterTimer->stop(); +} + +void KolfGame::unPause() +{ + if (!paused) + return; + + paused = false; + + timer->start(timerMsec); + fastTimer->start(fastTimerMsec); + + if (putting || stroking) + putterTimer->start(putterTimerMsec); +} + +void KolfGame::addBorderWall(QPoint start, QPoint end) +{ + Wall *wall = new Wall(course); + wall->setPoints(start.x(), start.y(), end.x(), end.y()); + wall->setVisible(true); + wall->setGame(this); + wall->setZ(998.7); + borderWalls.append(wall); +} + +void KolfGame::updateHighlighter() +{ + if (!selectedItem) + return; + QRect rect = selectedItem->boundingRect(); + highlighter->move(rect.x() + 1, rect.y() + 1); + highlighter->setSize(rect.width(), rect.height()); +} + +void KolfGame::handleMouseDoubleClickEvent(QMouseEvent *e) +{ + // allow two fast single clicks + handleMousePressEvent(e); +} + +void KolfGame::handleMousePressEvent(QMouseEvent *e) +{ + if (m_ignoreEvents) + return; + + if (editing) + { + if (inPlay) + return; + + storedMousePos = e->pos(); + + QCanvasItemList list = course->collisions(e->pos()); + if (list.first() == highlighter) + list.pop_front(); + + moving = false; + highlighter->setVisible(false); + selectedItem = 0; + movingItem = 0; + + if (list.count() < 1) + { + emit newSelectedItem(&holeInfo); + return; + } + // only items we keep track of + if ((!(items.containsRef(list.first()) || list.first() == whiteBall || extraMoveable.containsRef(list.first())))) + { + emit newSelectedItem(&holeInfo); + return; + } + + CanvasItem *citem = dynamic_cast<CanvasItem *>(list.first()); + if (!citem || !citem->moveable()) + { + emit newSelectedItem(&holeInfo); + return; + } + + switch (e->button()) + { + // select AND move now :) + case LeftButton: + { + selectedItem = list.first(); + movingItem = selectedItem; + moving = true; + + if (citem->cornerResize()) + setCursor(KCursor::sizeFDiagCursor()); + else + setCursor(KCursor::sizeAllCursor()); + + emit newSelectedItem(citem); + highlighter->setVisible(true); + QRect rect = selectedItem->boundingRect(); + highlighter->move(rect.x() + 1, rect.y() + 1); + highlighter->setSize(rect.width(), rect.height()); + } + break; + + default: + break; + } + } + else + { + if (m_useMouse) + { + if (!inPlay && e->button() == LeftButton) + puttPress(); + else if (e->button() == RightButton) + toggleShowInfo(); + } + } + + setFocus(); +} + +QPoint KolfGame::viewportToViewport(const QPoint &p) +{ + // for some reason viewportToContents doesn't work right + return p - QPoint(margin, margin); +} + +// the following four functions are needed to handle both +// border presses and regular in-course presses + +void KolfGame::mouseReleaseEvent(QMouseEvent * e) +{ + QMouseEvent fixedEvent (QEvent::MouseButtonRelease, viewportToViewport(viewportToContents(e->pos())), e->button(), e->state()); + handleMouseReleaseEvent(&fixedEvent); +} + +void KolfGame::mousePressEvent(QMouseEvent * e) +{ + QMouseEvent fixedEvent (QEvent::MouseButtonPress, viewportToViewport(viewportToContents(e->pos())), e->button(), e->state()); + handleMousePressEvent(&fixedEvent); +} + +void KolfGame::mouseDoubleClickEvent(QMouseEvent * e) +{ + QMouseEvent fixedEvent (QEvent::MouseButtonDblClick, viewportToViewport(viewportToContents(e->pos())), e->button(), e->state()); + handleMouseDoubleClickEvent(&fixedEvent); +} + +void KolfGame::mouseMoveEvent(QMouseEvent * e) +{ + QMouseEvent fixedEvent (QEvent::MouseMove, viewportToViewport(viewportToContents(e->pos())), e->button(), e->state()); + handleMouseMoveEvent(&fixedEvent); +} + +void KolfGame::handleMouseMoveEvent(QMouseEvent *e) +{ + if (inPlay || !putter || m_ignoreEvents) + return; + + QPoint mouse = e->pos(); + + // mouse moving of putter + if (!editing) + { + updateMouse(); + return; + } + + if (!moving) + { + // lets change the cursor to a hand + // if we're hovering over something + + QCanvasItemList list = course->collisions(e->pos()); + if (list.count() > 0) + setCursor(KCursor::handCursor()); + else + setCursor(KCursor::arrowCursor()); + return; + } + + int moveX = storedMousePos.x() - mouse.x(); + int moveY = storedMousePos.y() - mouse.y(); + + // moving counts as modifying + if (moveX || moveY) + setModified(true); + + highlighter->moveBy(-(double)moveX, -(double)moveY); + movingItem->moveBy(-(double)moveX, -(double)moveY); + QRect brect = movingItem->boundingRect(); + emit newStatusText(QString("%1x%2").arg(brect.x()).arg(brect.y())); + storedMousePos = mouse; +} + +void KolfGame::updateMouse() +{ + // don't move putter if in advanced putting sequence + if (!m_useMouse || ((stroking || putting) && m_useAdvancedPutting)) + return; + + const QPoint cursor = viewportToViewport(viewportToContents(mapFromGlobal(QCursor::pos()))); + const QPoint ball((*curPlayer).ball()->x(), (*curPlayer).ball()->y()); + putter->setAngle(-Vector(cursor, ball).direction()); +} + +void KolfGame::handleMouseReleaseEvent(QMouseEvent *e) +{ + setCursor(KCursor::arrowCursor()); + + if (editing) + { + emit newStatusText(QString::null); + moving = false; + } + + if (m_ignoreEvents) + return; + + if (!editing && m_useMouse) + { + if (!inPlay && e->button() == LeftButton) + puttRelease(); + else if (e->button() == RightButton) + toggleShowInfo(); + } + + setFocus(); +} + +void KolfGame::keyPressEvent(QKeyEvent *e) +{ + if (inPlay || editing || m_ignoreEvents) + return; + + switch (e->key()) + { + case Key_Up: + if (!e->isAutoRepeat()) + toggleShowInfo(); + break; + + case Key_Escape: + putting = false; + stroking = false; + finishStroking = false; + strokeCircle->setVisible(false); + putterTimer->stop(); + putter->setOrigin((*curPlayer).ball()->x(), (*curPlayer).ball()->y()); + break; + + case Key_Left: + case Key_Right: + // don't move putter if in advanced putting sequence + if ((!stroking && !putting) || !m_useAdvancedPutting) + putter->go(e->key() == Key_Left? D_Left : D_Right, e->state() & ShiftButton? Amount_More : e->state() & ControlButton? Amount_Less : Amount_Normal); + break; + + case Key_Space: case Key_Down: + puttPress(); + break; + + default: + break; + } +} + +void KolfGame::toggleShowInfo() +{ + setShowInfo(!m_showInfo); +} + +void KolfGame::updateShowInfo() +{ + setShowInfo(m_showInfo); +} + +void KolfGame::setShowInfo(bool yes) +{ + m_showInfo = yes; + + if (m_showInfo) + { + QCanvasItem *item = 0; + for (item = items.first(); item; item = items.next()) + { + CanvasItem *citem = dynamic_cast<CanvasItem *>(item); + if (citem) + citem->showInfo(); + } + + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + (*it).ball()->showInfo(); + + showInfo(); + } + else + { + QCanvasItem *item = 0; + for (item = items.first(); item; item = items.next()) + { + CanvasItem *citem = dynamic_cast<CanvasItem *>(item); + if (citem) + citem->hideInfo(); + } + + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + (*it).ball()->hideInfo(); + + hideInfo(); + } +} + +void KolfGame::puttPress() +{ + // Advanced putting: 1st click start putting sequence, 2nd determine strength, 3rd determine precision + + if (!putting && !stroking && !inPlay) + { + puttCount = 0; + puttReverse = false; + putting = true; + stroking = false; + strength = 0; + if (m_useAdvancedPutting) + { + strokeCircle->setValue(0); + int pw = putter->endPoint().x() - putter->startPoint().x(); + if (pw < 0) pw = -pw; + int px = (int)putter->x() + pw / 2; + int py = (int)putter->y(); + if (px > width / 2 && py < height / 2) + strokeCircle->move(px - pw / 2 - 10 - strokeCircle->width(), py + 10); + else if (px > width / 2) + strokeCircle->move(px - pw / 2 - 10 - strokeCircle->width(), py - 10 - strokeCircle->height()); + else if (py < height / 2) + strokeCircle->move(px + pw / 2 + 10, py + 10); + else + strokeCircle->move(px + pw / 2 + 10, py - 10 - strokeCircle->height()); + strokeCircle->setVisible(true); + } + putterTimer->start(putterTimerMsec); + } + else if (m_useAdvancedPutting && putting && !editing) + { + putting = false; + stroking = true; + puttReverse = false; + finishStroking = false; + } + else if (m_useAdvancedPutting && stroking) + { + finishStroking = true; + putterTimeout(); + } +} + +void KolfGame::keyReleaseEvent(QKeyEvent *e) +{ + if (e->isAutoRepeat() || m_ignoreEvents) + return; + + if (e->key() == Key_Space || e->key() == Key_Down) + puttRelease(); + else if ((e->key() == Key_Backspace || e->key() == Key_Delete) && !(e->state() & ControlButton)) + { + if (editing && !moving && selectedItem) + { + CanvasItem *citem = dynamic_cast<CanvasItem *>(selectedItem); + if (!citem) + return; + citem = citem->itemToDelete(); + if (!citem) + return; + QCanvasItem *item = dynamic_cast<QCanvasItem *>(citem); + if (citem && citem->deleteable()) + { + lastDelId = citem->curId(); + + highlighter->setVisible(false); + items.removeRef(item); + citem->hideInfo(); + citem->aboutToDelete(); + citem->aboutToDie(); + delete citem; + selectedItem = 0; + emit newSelectedItem(&holeInfo); + + setModified(true); + } + } + } + else if (e->key() == Key_I || e->key() == Key_Up) + toggleShowInfo(); +} + +void KolfGame::puttRelease() +{ + if (!m_useAdvancedPutting && putting && !editing) + { + putting = false; + stroking = true; + } +} + +void KolfGame::stoppedBall() +{ + if (!inPlay) + { + inPlay = true; + dontAddStroke = true; + } +} + +void KolfGame::timeout() +{ + Ball *curBall = (*curPlayer).ball(); + + // test if the ball is gone + // in this case we want to stop the ball and + // later undo the shot + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + { + if (!course->rect().contains(QPoint((*it).ball()->x(), (*it).ball()->y()))) + { + (*it).ball()->setState(Stopped); + + // don't do it if he's past maxStrokes + if ((*it).score(curHole) < holeInfo.maxStrokes() - 1 || !holeInfo.hasMaxStrokes()) + { + loadStateList(); + } + shotDone(); + + return; + } + } + + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + if ((*it).ball()->forceStillGoing() || ((*it).ball()->curState() == Rolling && (*it).ball()->curVector().magnitude() > 0 && (*it).ball()->isVisible())) + return; + + int curState = curBall->curState(); + if (curState == Stopped && inPlay) + { + inPlay = false; + QTimer::singleShot(0, this, SLOT(shotDone())); + } + + if (curState == Holed && inPlay) + { + emit inPlayEnd(); + emit playerHoled(&(*curPlayer)); + + int curScore = (*curPlayer).score(curHole); + if (!dontAddStroke) + curScore++; + + if (curScore == 1) + { + playSound("holeinone"); + } + else if (curScore <= holeInfo.par()) + { + // I don't have a sound!! + // *sob* + // playSound("woohoo"); + } + + (*curPlayer).ball()->setZ((*curPlayer).ball()->z() + .1 - (.1)/(curScore)); + + if (allPlayersDone()) + { + inPlay = false; + + if (curHole > 0 && !dontAddStroke) + { + (*curPlayer).addStrokeToHole(curHole); + emit scoreChanged((*curPlayer).id(), curHole, (*curPlayer).score(curHole)); + } + QTimer::singleShot(600, this, SLOT(holeDone())); + } + else + { + inPlay = false; + QTimer::singleShot(0, this, SLOT(shotDone())); + } + } +} + +void KolfGame::fastTimeout() +{ + // do regular advance every other time + if (regAdv) + course->advance(); + regAdv = !regAdv; + + if (!editing) + { + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + (*it).ball()->doAdvance(); + + if (fastAdvancedExist) + { + CanvasItem *citem = 0; + for (citem = fastAdvancers.first(); citem; citem = fastAdvancers.next()) + citem->doAdvance(); + } + + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + (*it).ball()->fastAdvanceDone(); + + if (fastAdvancedExist) + { + CanvasItem *citem = 0; + for (citem = fastAdvancers.first(); citem; citem = fastAdvancers.next()) + citem->fastAdvanceDone(); + } + } +} + +void KolfGame::ballMoved() +{ + if (putter->isVisible()) + { + putter->move((*curPlayer).ball()->x(), (*curPlayer).ball()->y()); + updateMouse(); + } +} + +void KolfGame::putterTimeout() +{ + if (inPlay || editing) + return; + + if (m_useAdvancedPutting) + { + if (putting) + { + const float base = 2.0; + + if (puttReverse && strength <= 0) + { + // aborted + putting = false; + strokeCircle->setVisible(false); + } + else if (strength > maxStrength || puttReverse) + { + // decreasing strength as we've reached the top + puttReverse = true; + strength -= pow(base, strength / maxStrength) - 1.8; + if ((int)strength < puttCount * 2) + { + puttCount--; + if (puttCount >= 0) + putter->go(Forwards); + } + } + else + { + // make the increase at high strength faster + strength += pow(base, strength / maxStrength) - .3; + if ((int)strength > puttCount * 2) + { + putter->go(Backwards); + puttCount++; + } + } + // make the visible steps at high strength smaller + strokeCircle->setValue(pow(strength / maxStrength, 0.8) * 360); + } + else if (stroking) + { + double al = strokeCircle->value(); + if (al >= 45) + al -= 0.2 + strength / 50 + al / 100; + else + al -= 0.2 + strength / 50; + + if (puttReverse) + { + // show the stroke + puttCount--; + if (puttCount >= 0) + putter->go(Forwards); + else + { + strokeCircle->setVisible(false); + finishStroking = false; + putterTimer->stop(); + putting = false; + stroking = false; + shotStart(); + } + } + else if (al < -45 || finishStroking) + { + strokeCircle->setValue(al); + int deg; + // if > 45 or < -45 then bad stroke + if (al > 45) + { + deg = putter->curDeg() - 45 + rand() % 90; + strength -= rand() % (int)strength; + } + else if (!finishStroking) + { + deg = putter->curDeg() - 45 + rand() % 90; + strength -= rand() % (int)strength; + } + else + deg = putter->curDeg() + (int)(strokeCircle->value() / 3); + + if (deg < 0) + deg += 360; + else if (deg > 360) + deg -= 360; + + putter->setDeg(deg); + puttReverse = true; + } + else + { + strokeCircle->setValue(al); + putterTimer->changeInterval(putterTimerMsec/10); + } + } + + } + else + { + if (putting) + { + putter->go(Backwards); + puttCount++; + strength += 1.5; + if (strength > maxStrength) + { + putting = false; + stroking = true; + } + } + else if (stroking) + { + if (putter->curLen() < (*curPlayer).ball()->height() + 2) + { + stroking = false; + putterTimer->stop(); + putting = false; + stroking = false; + shotStart(); + } + + putter->go(Forwards); + putterTimer->changeInterval(putterTimerMsec/10); + } + } +} + +void KolfGame::autoSaveTimeout() +{ + // this should be a config option + // until it is i'll disable it + if (editing) + { + //save(); + } +} + +void KolfGame::recreateStateList() +{ + stateDB.clear(); + + QCanvasItem *item = 0; + + for (item = items.first(); item; item = items.next()) + { + CanvasItem *citem = dynamic_cast<CanvasItem *>(item); + if (citem) + { + stateDB.setName(makeStateGroup(citem->curId(), citem->name())); + citem->saveState(&stateDB); + } + } + + ballStateList.clear(); + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + ballStateList.append((*it).stateInfo(curHole)); + + ballStateList.canUndo = true; +} + +void KolfGame::undoShot() +{ + if (ballStateList.canUndo) + loadStateList(); +} + +void KolfGame::loadStateList() +{ + QCanvasItem *item = 0; + + for (item = items.first(); item; item = items.next()) + { + CanvasItem *citem = dynamic_cast<CanvasItem *>(item); + if (citem) + { + stateDB.setName(makeStateGroup(citem->curId(), citem->name())); + citem->loadState(&stateDB); + } + } + + for (BallStateList::Iterator it = ballStateList.begin(); it != ballStateList.end(); ++it) + { + BallStateInfo info = (*it); + Player &player = (*players->at(info.id - 1)); + player.ball()->move(info.spot.x(), info.spot.y()); + player.ball()->setBeginningOfHole(info.beginningOfHole); + if ((*curPlayer).id() == info.id) + ballMoved(); + else + player.ball()->setVisible(!info.beginningOfHole); + player.setScoreForHole(info.score, curHole); + player.ball()->setState(info.state); + emit scoreChanged(info.id, curHole, info.score); + } +} + +void KolfGame::shotDone() +{ + inPlay = false; + emit inPlayEnd(); + setFocus(); + + Ball *ball = (*curPlayer).ball(); + double oldx = ball->x(), oldy = ball->y(); + + if (!dontAddStroke && (*curPlayer).numHoles()) + (*curPlayer).addStrokeToHole(curHole); + + dontAddStroke = false; + + // do hack stuff, shouldn't be done here + + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + { + if ((*it).ball()->addStroke()) + { + for (int i = 1; i <= (*it).ball()->addStroke(); ++i) + (*it).addStrokeToHole(curHole); + + // emit that we have a new stroke count + emit scoreChanged((*it).id(), curHole, (*it).score(curHole)); + } + (*it).ball()->setAddStroke(0); + } + + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + { + Ball *ball = (*it).ball(); + + if (ball->curState() == Holed) + continue; + + Vector v; + if (ball->placeOnGround(v)) + { + ball->setPlaceOnGround(false); + + QStringList options; + const QString placeOutside = i18n("Drop Outside of Hazard"); + const QString rehit = i18n("Rehit From Last Location"); + options << placeOutside << rehit; + const QString choice = KComboBoxDialog::getItem(i18n("What would you like to do for your next shot?"), i18n("%1 is in a Hazard").arg((*it).name()), options, placeOutside, "hazardOptions"); + + if (choice == placeOutside) + { + (*it).ball()->setDoDetect(false); + + double x = ball->x(), y = ball->y(); + + while (1) + { + QCanvasItemList list = ball->collisions(true); + bool keepMoving = false; + while (!list.isEmpty()) + { + QCanvasItem *item = list.first(); + if (item->rtti() == Rtti_DontPlaceOn) + keepMoving = true; + + list.pop_front(); + } + if (!keepMoving) + break; + + const float movePixel = 3.0; + x -= cos(v.direction()) * movePixel; + y += sin(v.direction()) * movePixel; + + ball->move(x, y); + } + + // move another two pixels away + x -= cos(v.direction()) * 2; + y += sin(v.direction()) * 2; + } + else if (choice == rehit) + { + for (BallStateList::Iterator it = ballStateList.begin(); it != ballStateList.end(); ++it) + { + if ((*it).id == (*curPlayer).id()) + { + if ((*it).beginningOfHole) + ball->move(whiteBall->x(), whiteBall->y()); + else + ball->move((*it).spot.x(), (*it).spot.y()); + + break; + } + } + } + + ball->setVisible(true); + ball->setState(Stopped); + + (*it).ball()->setDoDetect(true); + ball->collisionDetect(oldx, oldy); + } + } + + // emit again + emit scoreChanged((*curPlayer).id(), curHole, (*curPlayer).score(curHole)); + + ball->setVelocity(0, 0); + + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + { + Ball *ball = (*it).ball(); + + int curStrokes = (*it).score(curHole); + if (curStrokes >= holeInfo.maxStrokes() && holeInfo.hasMaxStrokes()) + { + ball->setState(Holed); + ball->setVisible(false); + + // move to center in case he/she hit out + ball->move(width / 2, height / 2); + playerWhoMaxed = (*it).name(); + + if (allPlayersDone()) + { + startNextHole(); + QTimer::singleShot(100, this, SLOT(emitMax())); + return; + } + + QTimer::singleShot(100, this, SLOT(emitMax())); + } + } + + // change player to next player + // skip player if he's Holed + do + { + curPlayer++; + if (curPlayer == players->end()) + curPlayer = players->begin(); + } + while ((*curPlayer).ball()->curState() == Holed); + + emit newPlayersTurn(&(*curPlayer)); + + (*curPlayer).ball()->setVisible(true); + + putter->setAngle((*curPlayer).ball()); + putter->setOrigin((*curPlayer).ball()->x(), (*curPlayer).ball()->y()); + updateMouse(); + + inPlay = false; + (*curPlayer).ball()->collisionDetect(oldx, oldy); +} + +void KolfGame::emitMax() +{ + emit maxStrokesReached(playerWhoMaxed); +} + +void KolfGame::startBall(const Vector &vector) +{ + playSound("hit"); + + emit inPlayStart(); + putter->setVisible(false); + + (*curPlayer).ball()->setState(Rolling); + (*curPlayer).ball()->setVector(vector); + + QCanvasItem *item = 0; + for (item = items.first(); item; item = items.next()) + { + CanvasItem *citem = dynamic_cast<CanvasItem *>(item); + if (citem) + citem->shotStarted(); + } + + inPlay = true; +} + +void KolfGame::shotStart() +{ + // ensure we never hit the ball back into the hole which + // can cause hole skippage + if ((*curPlayer).ball()->curState() == Holed) + return; + + // save state + recreateStateList(); + + putter->saveAngle((*curPlayer).ball()); + strength /= 8; + if (!strength) + strength = 1; + + startBall(Vector(strength, putter->curAngle() + M_PI)); + + addHoleInfo(ballStateList); +} + +void KolfGame::addHoleInfo(BallStateList &list) +{ + list.player = (*curPlayer).id(); + list.vector = (*curPlayer).ball()->curVector(); + list.hole = curHole; +} + +void KolfGame::sayWhosGoing() +{ + if (players->count() >= 2) + { + KMessageBox::information(this, i18n("%1 will start off.").arg((*curPlayer).name()), i18n("New Hole"), "newHole"); + } +} + +void KolfGame::holeDone() +{ + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + (*it).ball()->setVisible(false); + startNextHole(); + sayWhosGoing(); +} + +// this function is WAY too smart for it's own good +// ie, bad design :-( +void KolfGame::startNextHole() +{ + setFocus(); + + bool reset = true; + if (askSave(true)) + { + if (allPlayersDone()) + { + // we'll reload this hole, but not reset + curHole--; + reset = false; + } + else + return; + } + else + setModified(false); + + pause(); + + dontAddStroke = false; + + inPlay = false; + timer->stop(); + putter->resetAngles(); + + int oldCurHole = curHole; + curHole++; + emit currentHole(curHole); + + if (reset) + { + whiteBall->move(width/2, height/2); + holeInfo.borderWallsChanged(true); + } + + int leastScore = INT_MAX; + + // to get the first player to go first on every hole, + // don't do the score stuff below + curPlayer = players->begin(); + double oldx=(*curPlayer).ball()->x(), oldy=(*curPlayer).ball()->y(); + + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + { + if (curHole > 1) + { + bool ahead = false; + if ((*it).lastScore() != 0) + { + if ((*it).lastScore() < leastScore) + ahead = true; + else if ((*it).lastScore() == leastScore) + { + for (int i = curHole - 1; i > 0; --i) + { + const int thisScore = (*it).score(i); + const int thatScore = (*curPlayer).score(i); + if (thisScore < thatScore) + { + ahead = true; + break; + } + else if (thisScore > thatScore) + break; + } + } + } + + if (ahead) + { + curPlayer = it; + leastScore = (*it).lastScore(); + } + } + + if (reset) + (*it).ball()->move(width / 2, height / 2); + else + (*it).ball()->move(whiteBall->x(), whiteBall->y()); + + (*it).ball()->setState(Stopped); + + // this gets set to false when the ball starts + // to move by the Mr. Ball himself. + (*it).ball()->setBeginningOfHole(true); + if ((int)(*it).scores().count() < curHole) + (*it).addHole(); + (*it).ball()->setVelocity(0, 0); + (*it).ball()->setVisible(false); + } + + emit newPlayersTurn(&(*curPlayer)); + + if (reset) + openFile(); + + inPlay = false; + timer->start(timerMsec); + + // if (false) { we're done with the round! } + if (oldCurHole != curHole) + { + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + (*it).ball()->setPlaceOnGround(false); + + // here we have to make sure the scoreboard shows + // all of the holes up until now; + + for (; scoreboardHoles < curHole; ++scoreboardHoles) + { + cfg->setGroup(QString("%1-hole@-50,-50|0").arg(scoreboardHoles + 1)); + emit newHole(cfg->readNumEntry("par", 3)); + } + + resetHoleScores(); + updateShowInfo(); + + // this is from shotDone() + (*curPlayer).ball()->setVisible(true); + putter->setOrigin((*curPlayer).ball()->x(), (*curPlayer).ball()->y()); + updateMouse(); + + ballStateList.canUndo = false; + + (*curPlayer).ball()->collisionDetect(oldx, oldy); + } + + unPause(); +} + +void KolfGame::showInfo() +{ + QString text = i18n("Hole %1: par %2, maximum %3 strokes").arg(curHole).arg(holeInfo.par()).arg(holeInfo.maxStrokes()); + infoText->move((width - QFontMetrics(infoText->font()).width(text)) / 2, infoText->y()); + infoText->setText(text); + // I hate this text! Let's not show it + //infoText->setVisible(true); + + emit newStatusText(text); +} + +void KolfGame::showInfoDlg(bool addDontShowAgain) +{ + KMessageBox::information(parentWidget(), + i18n("Course name: %1").arg(holeInfo.name()) + QString("\n") + + i18n("Created by %1").arg(holeInfo.author()) + QString("\n") + + i18n("%1 holes").arg(highestHole), + i18n("Course Information"), + addDontShowAgain? holeInfo.name() + QString(" ") + holeInfo.author() : QString::null); +} + +void KolfGame::hideInfo() +{ + infoText->setText(""); + infoText->setVisible(false); + + emit newStatusText(QString::null); +} + +void KolfGame::openFile() +{ + Object *curObj = 0; + + QCanvasItem *item = 0; + for (item = items.first(); item; item = items.next()) + { + CanvasItem *citem = dynamic_cast<CanvasItem *>(item); + if (citem) + { + // sometimes info is still showing + citem->hideInfo(); + citem->aboutToDie(); + } + } + + items.setAutoDelete(true); + items.clear(); + items.setAutoDelete(false); + + extraMoveable.setAutoDelete(false); + extraMoveable.clear(); + fastAdvancers.setAutoDelete(false); + fastAdvancers.clear(); + selectedItem = 0; + + // will tell basic course info + // we do this here for the hell of it. + // there is no fake id, by the way, + // because it's old and when i added ids i forgot to change it. + cfg->setGroup("0-course@-50,-50"); + holeInfo.setAuthor(cfg->readEntry("author", holeInfo.author())); + holeInfo.setName(cfg->readEntry("Name", holeInfo.name())); + holeInfo.setUntranslatedName(cfg->readEntryUntranslated("Name", holeInfo.untranslatedName())); + emit titleChanged(holeInfo.name()); + + cfg->setGroup(QString("%1-hole@-50,-50|0").arg(curHole)); + curPar = cfg->readNumEntry("par", 3); + holeInfo.setPar(curPar); + holeInfo.borderWallsChanged(cfg->readBoolEntry("borderWalls", holeInfo.borderWalls())); + holeInfo.setMaxStrokes(cfg->readNumEntry("maxstrokes", 10)); + bool hasFinalLoad = cfg->readBoolEntry("hasFinalLoad", true); + + QStringList missingPlugins; + QStringList groups = cfg->groupList(); + + int numItems = 0; + int _highestHole = 0; + + for (QStringList::Iterator it = groups.begin(); it != groups.end(); ++it) + { + // [<holeNum>-<name>@<x>,<y>|<id>] + cfg->setGroup(*it); + + const int len = (*it).length(); + const int dashIndex = (*it).find("-"); + const int holeNum = (*it).left(dashIndex).toInt(); + if (holeNum > _highestHole) + _highestHole = holeNum; + + const int atIndex = (*it).find("@"); + const QString name = (*it).mid(dashIndex + 1, atIndex - (dashIndex + 1)); + + if (holeNum != curHole) + { + // if we've had one, break, cause list is sorted + // erps, no, cause we need to know highest hole! + if (numItems && !recalcHighestHole) + break; + continue; + } + numItems++; + + + const int commaIndex = (*it).find(","); + const int pipeIndex = (*it).find("|"); + const int x = (*it).mid(atIndex + 1, commaIndex - (atIndex + 1)).toInt(); + const int y = (*it).mid(commaIndex + 1, pipeIndex - (commaIndex + 1)).toInt(); + + // will tell where ball is + if (name == "ball") + { + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + (*it).ball()->move(x, y); + whiteBall->move(x, y); + continue; + } + + const int id = (*it).right(len - (pipeIndex + 1)).toInt(); + + bool loaded = false; + + for (curObj = obj->first(); curObj; curObj = obj->next()) + { + if (name != curObj->_name()) + continue; + + QCanvasItem *newItem = curObj->newObject(course); + items.append(newItem); + + CanvasItem *canvasItem = dynamic_cast<CanvasItem *>(newItem); + if (!canvasItem) + continue; + + canvasItem->setId(id); + canvasItem->setGame(this); + canvasItem->editModeChanged(editing); + canvasItem->setName(curObj->_name()); + addItemsToMoveableList(canvasItem->moveableItems()); + if (canvasItem->fastAdvance()) + addItemToFastAdvancersList(canvasItem); + + newItem->move(x, y); + canvasItem->firstMove(x, y); + + newItem->setVisible(true); + + // make things actually show + if (!hasFinalLoad) + { + cfg->setGroup(makeGroup(id, curHole, canvasItem->name(), x, y)); + canvasItem->load(cfg); + course->update(); + } + + // we don't allow multiple items for the same thing in + // the file! + + loaded = true; + break; + } + + if (!loaded && name != "hole" && missingPlugins.contains(name) <= 0) + missingPlugins.append(name); + } + + if (!missingPlugins.empty()) + { + KMessageBox::informationList(this, QString("<p><http://katzbrown.com/kolf/Plugins/></p><p>") + i18n("This hole uses the following plugins, which you do not have installed:") + QString("</p>"), missingPlugins, QString::null, QString("%1 warning").arg(holeInfo.untranslatedName() + QString::number(curHole))); + } + + lastDelId = -1; + + // if it's the first hole let's not + if (!numItems && curHole > 1 && !addingNewHole && curHole >= _highestHole) + { + // we're done, let's quit + curHole--; + pause(); + emit holesDone(); + + // tidy things up + setBorderWalls(false); + clearHole(); + setModified(false); + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + (*it).ball()->setVisible(false); + + return; + } + + // do it down here; if !hasFinalLoad, do it up there! + QCanvasItem *qcanvasItem = 0; + QPtrList<CanvasItem> todo; + QPtrList<QCanvasItem> qtodo; + if (hasFinalLoad) + { + for (qcanvasItem = items.first(); qcanvasItem; qcanvasItem = items.next()) + { + CanvasItem *item = dynamic_cast<CanvasItem *>(qcanvasItem); + if (item) + { + if (item->loadLast()) + { + qtodo.append(qcanvasItem); + todo.append(item); + } + else + { + QString group = makeGroup(item->curId(), curHole, item->name(), (int)qcanvasItem->x(), (int)qcanvasItem->y()); + cfg->setGroup(group); + item->load(cfg); + } + } + } + + CanvasItem *citem = 0; + qcanvasItem = qtodo.first(); + for (citem = todo.first(); citem; citem = todo.next()) + { + cfg->setGroup(makeGroup(citem->curId(), curHole, citem->name(), (int)qcanvasItem->x(), (int)qcanvasItem->y())); + citem->load(cfg); + + qcanvasItem = qtodo.next(); + } + } + + for (qcanvasItem = items.first(); qcanvasItem; qcanvasItem = items.next()) + { + CanvasItem *citem = dynamic_cast<CanvasItem *>(qcanvasItem); + if (citem) + citem->updateZ(); + } + + if (curHole > _highestHole) + _highestHole = curHole; + + if (recalcHighestHole) + { + highestHole = _highestHole; + recalcHighestHole = false; + emit largestHole(highestHole); + } + + if (curHole == 1 && !filename.isNull() && !infoShown) + { + // let's not now, because they see it when they choose course + //showInfoDlg(true); + infoShown = true; + } + + setModified(false); +} + +void KolfGame::addItemsToMoveableList(QPtrList<QCanvasItem> list) +{ + QCanvasItem *item = 0; + for (item = list.first(); item; item = list.next()) + extraMoveable.append(item); +} + +void KolfGame::addItemToFastAdvancersList(CanvasItem *item) +{ + fastAdvancers.append(item); + fastAdvancedExist = fastAdvancers.count() > 0; +} + +void KolfGame::addNewObject(Object *newObj) +{ + QCanvasItem *newItem = newObj->newObject(course); + items.append(newItem); + newItem->setVisible(true); + + CanvasItem *canvasItem = dynamic_cast<CanvasItem *>(newItem); + if (!canvasItem) + return; + + // we need to find a number that isn't taken + int i = lastDelId > 0? lastDelId : items.count() - 30; + if (i <= 0) + i = 0; + + for (;; ++i) + { + bool found = false; + QCanvasItem *item = 0; + for (item = items.first(); item; item = items.next()) + { + CanvasItem *citem = dynamic_cast<CanvasItem *>(item); + if (citem) + { + if (citem->curId() == i) + { + found = true; + break; + } + } + } + + + if (!found) + break; + } + canvasItem->setId(i); + + canvasItem->setGame(this); + + if (m_showInfo) + canvasItem->showInfo(); + else + canvasItem->hideInfo(); + + canvasItem->editModeChanged(editing); + + canvasItem->setName(newObj->_name()); + addItemsToMoveableList(canvasItem->moveableItems()); + + if (canvasItem->fastAdvance()) + addItemToFastAdvancersList(canvasItem); + + newItem->move(width/2 - 18, height / 2 - 18); + + if (selectedItem) + canvasItem->selectedItem(selectedItem); + + setModified(true); +} + +bool KolfGame::askSave(bool noMoreChances) +{ + if (!modified) + // not cancel, don't save + return false; + + int result = KMessageBox::warningYesNoCancel(this, i18n("There are unsaved changes to current hole. Save them?"), i18n("Unsaved Changes"), KStdGuiItem::save(), noMoreChances? KStdGuiItem::discard() : i18n("Save &Later"), noMoreChances? "DiscardAsk" : "SaveAsk", true); + switch (result) + { + case KMessageBox::Yes: + save(); + // fallthrough + + case KMessageBox::No: + return false; + break; + + case KMessageBox::Cancel: + return true; + break; + + default: + break; + } + + return false; +} + +void KolfGame::addNewHole() +{ + if (askSave(true)) + return; + + // either it's already false + // because it was saved by askSave(), + // or the user pressed the 'discard' button + setModified(false); + + // find highest hole num, and create new hole + // now openFile makes highest hole for us + + addingNewHole = true; + curHole = highestHole; + recalcHighestHole = true; + startNextHole(); + addingNewHole = false; + emit currentHole(curHole); + + // make sure even the current player isn't showing + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + (*it).ball()->setVisible(false); + + whiteBall->setVisible(editing); + highlighter->setVisible(false); + putter->setVisible(!editing); + inPlay = false; + + // add default objects + Object *curObj = 0; + for (curObj = obj->first(); curObj; curObj = obj->next()) + if (curObj->addOnNewHole()) + addNewObject(curObj); + + save(); +} + +// kantan deshou ;-) +void KolfGame::resetHole() +{ + if (askSave(true)) + return; + setModified(false); + curHole--; + startNextHole(); + resetHoleScores(); +} + +void KolfGame::resetHoleScores() +{ + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + { + (*it).resetScore(curHole); + emit scoreChanged((*it).id(), curHole, 0); + } +} + +void KolfGame::clearHole() +{ + QCanvasItem *qcanvasItem = 0; + for (qcanvasItem = items.first(); qcanvasItem; qcanvasItem = items.next()) + { + CanvasItem *citem = dynamic_cast<CanvasItem *>(qcanvasItem); + if (citem) + citem->aboutToDie(); + } + items.setAutoDelete(true); + items.clear(); + items.setAutoDelete(false); + emit newSelectedItem(&holeInfo); + + // add default objects + Object *curObj = 0; + for (curObj = obj->first(); curObj; curObj = obj->next()) + if (curObj->addOnNewHole()) + addNewObject(curObj); + + setModified(true); +} + +void KolfGame::switchHole(int hole) +{ + if (inPlay) + return; + if (hole < 1 || hole > highestHole) + return; + + bool wasEditing = editing; + if (editing) + toggleEditMode(); + + if (askSave(true)) + return; + setModified(false); + + curHole = hole; + resetHole(); + + if (wasEditing) + toggleEditMode(); +} + +void KolfGame::switchHole(const QString &holestring) +{ + bool ok; + int hole = holestring.toInt(&ok); + if (!ok) + return; + switchHole(hole); +} + +void KolfGame::nextHole() +{ + switchHole(curHole + 1); +} + +void KolfGame::prevHole() +{ + switchHole(curHole - 1); +} + +void KolfGame::firstHole() +{ + switchHole(1); +} + +void KolfGame::lastHole() +{ + switchHole(highestHole); +} + +void KolfGame::randHole() +{ + int newHole = 1 + (int)((double)kapp->random() * ((double)(highestHole - 1) / (double)RAND_MAX)); + switchHole(newHole); +} + +void KolfGame::save() +{ + if (filename.isNull()) + { + QString newfilename = KFileDialog::getSaveFileName(":kourses", "application/x-kourse", this, i18n("Pick Kolf Course to Save To")); + if (newfilename.isNull()) + return; + + setFilename(newfilename); + } + + emit parChanged(curHole, holeInfo.par()); + emit titleChanged(holeInfo.name()); + + // we use this bool for optimization + // in openFile(). + bool hasFinalLoad = false; + fastAdvancedExist = false; + + QCanvasItem *item = 0; + for (item = items.first(); item; item = items.next()) + { + CanvasItem *citem = dynamic_cast<CanvasItem *>(item); + if (citem) + { + citem->aboutToSave(); + if (citem->loadLast()) + hasFinalLoad = true; + } + } + + QStringList groups = cfg->groupList(); + + // wipe out all groups from this hole + for (QStringList::Iterator it = groups.begin(); it != groups.end(); ++it) + { + int holeNum = (*it).left((*it).find("-")).toInt(); + if (holeNum == curHole) + cfg->deleteGroup(*it); + } + for (item = items.first(); item; item = items.next()) + { + CanvasItem *citem = dynamic_cast<CanvasItem *>(item); + if (citem) + { + citem->clean(); + + cfg->setGroup(makeGroup(citem->curId(), curHole, citem->name(), (int)item->x(), (int)item->y())); + citem->save(cfg); + } + } + + // save where ball starts (whiteBall tells all) + cfg->setGroup(QString("%1-ball@%2,%3").arg(curHole).arg((int)whiteBall->x()).arg((int)whiteBall->y())); + cfg->writeEntry("dummykey", true); + + cfg->setGroup("0-course@-50,-50"); + cfg->writeEntry("author", holeInfo.author()); + cfg->writeEntry("Name", holeInfo.untranslatedName()); + + // save hole info + cfg->setGroup(QString("%1-hole@-50,-50|0").arg(curHole)); + cfg->writeEntry("par", holeInfo.par()); + cfg->writeEntry("maxstrokes", holeInfo.maxStrokes()); + cfg->writeEntry("borderWalls", holeInfo.borderWalls()); + cfg->writeEntry("hasFinalLoad", hasFinalLoad); + + cfg->sync(); + + for (item = items.first(); item; item = items.next()) + { + CanvasItem *citem = dynamic_cast<CanvasItem *>(item); + if (citem) + citem->savingDone(); + } + + setModified(false); +} + +void KolfGame::toggleEditMode() +{ + // won't be editing anymore, and user wants to cancel, we return + // this is pretty useless. when the person leaves the hole, + // he gets asked again + /* + if (editing && modified) + { + if (askSave(false)) + { + emit checkEditing(); + return; + } + } + */ + + moving = false; + selectedItem = 0; + + editing = !editing; + + if (editing) + { + emit editingStarted(); + emit newSelectedItem(&holeInfo); + } + else + { + emit editingEnded(); + setCursor(KCursor::arrowCursor()); + } + + // alert our items + QCanvasItem *item = 0; + for (item = items.first(); item; item = items.next()) + { + CanvasItem *citem = dynamic_cast<CanvasItem *>(item); + if (citem) + citem->editModeChanged(editing); + } + + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + { + // curplayer shouldn't be hidden no matter what + if ((*it).ball()->beginningOfHole() && it != curPlayer) + (*it).ball()->setVisible(false); + else + (*it).ball()->setVisible(!editing); + } + + whiteBall->setVisible(editing); + highlighter->setVisible(false); + + // shouldn't see putter whilst editing + putter->setVisible(!editing); + + if (editing) + autoSaveTimer->start(autoSaveMsec); + else + autoSaveTimer->stop(); + + inPlay = false; +} + +void KolfGame::playSound(QString file, double vol) +{ + if (m_sound) + { + KPlayObject *oldPlayObject = 0; + for (oldPlayObject = oldPlayObjects.first(); oldPlayObject; oldPlayObject = oldPlayObjects.next()) + { + if (oldPlayObject && oldPlayObject->state() != Arts::posPlaying) + { + oldPlayObjects.remove(); + + // because we will go to next() next time + // and after remove current item is one after + // removed item + (void) oldPlayObjects.prev(); + } + } + + file = soundDir + file + QString::fromLatin1(".wav"); + + // not needed when all of the files are in the distribution + //if (!QFile::exists(file)) + //return; + + KPlayObjectFactory factory(artsServer.server()); + KPlayObject *playObject = factory.createPlayObject(KURL(file), true); + + if (playObject && !playObject->isNull()) + { + if (vol > 1) + vol = 1; + else if (vol <= .01) + { + delete playObject; + return; + } + + if (vol < .99) + { + //new KVolumeControl(vol, artsServer.server(), playObject); + } + + playObject->play(); + oldPlayObjects.append(playObject); + } + } +} + +void HoleInfo::borderWallsChanged(bool yes) +{ + m_borderWalls = yes; + game->setBorderWalls(yes); +} + +void KolfGame::print(KPrinter &pr) +{ + QPainter p(&pr); + + QPaintDeviceMetrics metrics(&pr); + + // translate to center + p.translate(metrics.width() / 2 - course->rect().width() / 2, metrics.height() / 2 - course->rect().height() / 2); + + QPixmap pix(width, height); + QPainter pixp(&pix); + course->drawArea(course->rect(), &pixp); + p.drawPixmap(0, 0, pix); + + p.setPen(QPen(black, 2)); + p.drawRect(course->rect()); + + p.resetXForm(); + + if (pr.option("kde-kolf-title") == "true") + { + QString text = i18n("%1 - Hole %2; by %3").arg(holeInfo.name()).arg(curHole).arg(holeInfo.author()); + QFont font(kapp->font()); + font.setPointSize(18); + QRect rect = QFontMetrics(font).boundingRect(text); + p.setFont(font); + + p.drawText(metrics.width() / 2 - rect.width() / 2, metrics.height() / 2 - course->rect().height() / 2 -20 - rect.height(), text); + } +} + +bool KolfGame::allPlayersDone() +{ + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + if ((*it).ball()->curState() != Holed) + return false; + + return true; +} + +void KolfGame::setBorderWalls(bool showing) +{ + Wall *wall = 0; + for (wall = borderWalls.first(); wall; wall = borderWalls.next()) + wall->setVisible(showing); +} + +void KolfGame::setUseAdvancedPutting(bool yes) +{ + m_useAdvancedPutting = yes; + + // increase maxStrength in advanced putting mode + if (yes) + maxStrength = 65; + else + maxStrength = 55; +} + +void KolfGame::setShowGuideLine(bool yes) +{ + putter->setShowGuideLine(yes); +} + +void KolfGame::setSound(bool yes) +{ + m_sound = yes; +} + +void KolfGame::courseInfo(CourseInfo &info, const QString& filename) +{ + KConfig cfg(filename); + cfg.setGroup("0-course@-50,-50"); + info.author = cfg.readEntry("author", info.author); + info.name = cfg.readEntry("Name", cfg.readEntry("name", info.name)); + info.untranslatedName = cfg.readEntryUntranslated("Name", cfg.readEntryUntranslated("name", info.name)); + + unsigned int hole = 1; + unsigned int par= 0; + while (1) + { + QString group = QString("%1-hole@-50,-50|0").arg(hole); + if (!cfg.hasGroup(group)) + { + hole--; + break; + } + + cfg.setGroup(group); + par += cfg.readNumEntry("par", 3); + + hole++; + } + + info.par = par; + info.holes = hole; +} + +void KolfGame::scoresFromSaved(KConfig *config, PlayerList &players) +{ + config->setGroup("0 Saved Game"); + int numPlayers = config->readNumEntry("Players", 0); + if (numPlayers <= 0) + return; + + for (int i = 1; i <= numPlayers; ++i) + { + // this is same as in kolf.cpp, but we use saved game values + config->setGroup(QString::number(i)); + players.append(Player()); + players.last().ball()->setColor(config->readEntry("Color", "#ffffff")); + players.last().setName(config->readEntry("Name")); + players.last().setId(i); + + QStringList scores(config->readListEntry("Scores")); + QValueList<int> intscores; + for (QStringList::Iterator it = scores.begin(); it != scores.end(); ++it) + intscores.append((*it).toInt()); + + players.last().setScores(intscores); + } +} + +void KolfGame::saveScores(KConfig *config) +{ + // wipe out old player info + QStringList groups = config->groupList(); + for (QStringList::Iterator it = groups.begin(); it != groups.end(); ++it) + { + // this deletes all int groups, ie, the player info groups + bool ok = false; + (*it).toInt(&ok); + if (ok) + config->deleteGroup(*it); + } + + config->setGroup("0 Saved Game"); + config->writeEntry("Players", players->count()); + config->writeEntry("Course", filename); + config->writeEntry("Current Hole", curHole); + + for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) + { + config->setGroup(QString::number((*it).id())); + config->writeEntry("Name", (*it).name()); + config->writeEntry("Color", (*it).ball()->color().name()); + + QStringList scores; + QValueList<int> intscores = (*it).scores(); + for (QValueList<int>::Iterator it = intscores.begin(); it != intscores.end(); ++it) + scores.append(QString::number(*it)); + + config->writeEntry("Scores", scores); + } +} + +CourseInfo::CourseInfo() +: name(i18n("Course Name")), author(i18n("Course Author")), holes(0), par(0) +{ +} + +#include "game.moc" |