diff --git a/landscape.cpp b/landscape.cpp index 761cb2b..ded410d 100644 --- a/landscape.cpp +++ b/landscape.cpp @@ -1,645 +1,641 @@ /* Copyright (C) 2002-2005, Jason Katz-Brown Copyright 2010 Stefan Majewsky This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "landscape.h" #include "ball.h" #include "game.h" #include #include #include #include #include #include #include #include //BEGIN Kolf::LandscapeItem //END Kolf::LandscapeItem Kolf::LandscapeItem::LandscapeItem(const QString& type, QGraphicsItem* parent, b2World* world) : EllipticalCanvasItem(false, type, parent, world) , m_blinkEnabled(false) , m_blinkInterval(50) , m_blinkFrame(0) { setSimulationType(CanvasItem::NoSimulation); } bool Kolf::LandscapeItem::isBlinkEnabled() const { return m_blinkEnabled; } void Kolf::LandscapeItem::setBlinkEnabled(bool blinkEnabled) { m_blinkEnabled = blinkEnabled; //reset animation m_blinkFrame = 0; setVisible(true); } int Kolf::LandscapeItem::blinkInterval() const { return m_blinkInterval; } void Kolf::LandscapeItem::setBlinkInterval(int blinkInterval) { m_blinkInterval = blinkInterval; //reset animation m_blinkFrame = 0; setVisible(true); } void Kolf::LandscapeItem::advance(int phase) { EllipticalCanvasItem::advance(phase); if (phase == 1 && m_blinkEnabled) { const int actualInterval = 1.8 * (10 + m_blinkInterval); m_blinkFrame = (m_blinkFrame + 1) % (2 * actualInterval); setVisible(m_blinkFrame < actualInterval); } } void Kolf::LandscapeItem::load(KConfigGroup* group) { EllipticalCanvasItem::loadSize(group); setBlinkEnabled(group->readEntry("changeEnabled", m_blinkEnabled)); setBlinkInterval(group->readEntry("changeEvery", m_blinkInterval)); } void Kolf::LandscapeItem::save(KConfigGroup* group) { EllipticalCanvasItem::saveSize(group); group->writeEntry("changeEnabled", m_blinkEnabled); group->writeEntry("changeEvery", m_blinkInterval); } Config* Kolf::LandscapeItem::config(QWidget* parent) { return new Kolf::LandscapeConfig(this, parent); } Kolf::Overlay* Kolf::LandscapeItem::createOverlay() { return new Kolf::LandscapeOverlay(this); } //BEGIN Kolf::LandscapeOverlay Kolf::LandscapeOverlay::LandscapeOverlay(Kolf::LandscapeItem* item) : Kolf::Overlay(item, item) { //TODO: code duplication to Kolf::RectangleOverlay and Kolf::SlopeOverlay for (int i = 0; i < 4; ++i) { Kolf::OverlayHandle* handle = new Kolf::OverlayHandle(Kolf::OverlayHandle::CircleShape, this); m_handles << handle; addHandle(handle); connect(handle, &Kolf::OverlayHandle::moveRequest, this, &Kolf::LandscapeOverlay::moveHandle); } } void Kolf::LandscapeOverlay::update() { Kolf::Overlay::update(); const QRectF rect = qitem()->boundingRect(); m_handles[0]->setPos(rect.topLeft()); m_handles[1]->setPos(rect.topRight()); m_handles[2]->setPos(rect.bottomLeft()); m_handles[3]->setPos(rect.bottomRight()); } void Kolf::LandscapeOverlay::moveHandle(const QPointF& handleScenePos) { const QPointF handlePos = mapFromScene(handleScenePos); //factor 2: item bounding rect is always centered around (0,0) QSizeF newSize(2 * qAbs(handlePos.x()), 2 * qAbs(handlePos.y())); dynamic_cast(qitem())->setSize(newSize); } //END Kolf::LandscapeOverlay //BEGIN Kolf::LandscapeConfig Kolf::LandscapeConfig::LandscapeConfig(Kolf::LandscapeItem* item, QWidget* parent) : Config(parent) { QVBoxLayout* vlayout = new QVBoxLayout(this); QCheckBox* checkBox = new QCheckBox(i18n("Enable show/hide"), this); vlayout->addWidget(checkBox); QHBoxLayout* hlayout = new QHBoxLayout; vlayout->addLayout(hlayout); QLabel* label1 = new QLabel(i18n("Slow"), this); hlayout->addWidget(label1); QSlider* slider = new QSlider(Qt::Horizontal, this); hlayout->addWidget(slider); QLabel* label2 = new QLabel(i18n("Fast"), this); hlayout->addWidget(label2); vlayout->addStretch(); checkBox->setChecked(true); connect(checkBox, &QCheckBox::toggled, label1, &QLabel::setEnabled); connect(checkBox, &QCheckBox::toggled, label2, &QLabel::setEnabled); connect(checkBox, &QCheckBox::toggled, slider, &QSlider::setEnabled); connect(checkBox, &QCheckBox::toggled, item, &Kolf::LandscapeItem::setBlinkEnabled); checkBox->setChecked(item->isBlinkEnabled()); slider->setRange(1, 100); slider->setPageStep(5); slider->setValue(100 - item->blinkInterval()); connect(slider, &QSlider::valueChanged, this, &Kolf::LandscapeConfig::setBlinkInterval); connect(this, &Kolf::LandscapeConfig::blinkIntervalChanged, item, &Kolf::LandscapeItem::setBlinkInterval); } void Kolf::LandscapeConfig::setBlinkInterval(int sliderValue) { emit blinkIntervalChanged(100 - sliderValue); } //END Kolf::LandscapeConfig //BEGIN Kolf::Puddle Kolf::Puddle::Puddle(QGraphicsItem* parent, b2World* world) : Kolf::LandscapeItem(QStringLiteral("puddle"), parent, world) { setData(0, Rtti_DontPlaceOn); setSize(QSizeF(45, 30)); setZBehavior(CanvasItem::FixedZValue, 3); } bool Kolf::Puddle::collision(Ball* ball) { if (!ball->isVisible()) return false; if (!contains(ball->pos() - pos())) return true; //ball is visible and has reached the puddle game->playSound(Sound::Puddle); ball->setAddStroke(ball->addStroke() + 1); ball->setPlaceOnGround(true); ball->setVisible(false); ball->setState(Stopped); ball->setVelocity(Vector()); if (game && game->curBall() == ball) game->stoppedBall(); return false; } //END Kolf::Puddle //BEGIN Kolf::Sand Kolf::Sand::Sand(QGraphicsItem* parent, b2World* world) : Kolf::LandscapeItem(QStringLiteral("sand"), parent, world) { setSize(QSizeF(45, 40)); setZBehavior(CanvasItem::FixedZValue, 2); } bool Kolf::Sand::collision(Ball* ball) { if (contains(ball->pos() - pos())) ball->setFrictionMultiplier(7); return true; } //END Kolf::Sand //BEGIN Kolf::Slope struct SlopeData { QStringList gradientKeys, translatedGradientKeys; QStringList spriteKeys, reversedSpriteKeys; SlopeData() { gradientKeys << QStringLiteral("Vertical") << QStringLiteral("Horizontal") << QStringLiteral("Diagonal") << QStringLiteral("Opposite Diagonal") << QStringLiteral("Elliptic"); translatedGradientKeys << i18n("Vertical") << i18n("Horizontal") << i18n("Diagonal") << i18n("Opposite Diagonal") << i18n("Elliptic"); spriteKeys << QStringLiteral("slope_n") << QStringLiteral("slope_w") << QStringLiteral("slope_nw") << QStringLiteral("slope_ne") << QStringLiteral("slope_bump"); reversedSpriteKeys << QStringLiteral("slope_s") << QStringLiteral("slope_e") << QStringLiteral("slope_se") << QStringLiteral("slope_sw") << QStringLiteral("slope_dip"); } }; Q_GLOBAL_STATIC(SlopeData, g_slopeData) Kolf::Slope::Slope(QGraphicsItem* parent, b2World* world) : Tagaro::SpriteObjectItem(Kolf::renderer(), QString(), parent) , CanvasItem(world) , m_grade(4) , m_reversed(false) , m_stuckOnGround(false) , m_type(Kolf::VerticalSlope) , m_gradeItem(new QGraphicsSimpleTextItem(this)) { m_gradeItem->setBrush(Qt::white); m_gradeItem->setVisible(false); m_gradeItem->setZValue(1); for (int i = 0; i < 4; ++i) { ArrowItem* arrow = new ArrowItem(this); arrow->setLength(0); arrow->setVisible(false); m_arrows << arrow; } setSize(QSizeF(40, 40)); m_stuckOnGround = true; //so that the following call does not return early setStuckOnGround(false); //initializes Z behavior updateAppearance(); } double Kolf::Slope::grade() const { return m_grade; } void Kolf::Slope::setGrade(double grade) { if (m_grade != grade && grade > 0) { m_grade = grade; updateAppearance(); propagateUpdate(); } } bool Kolf::Slope::isReversed() const { return m_reversed; } void Kolf::Slope::setReversed(bool reversed) { if (m_reversed != reversed) { m_reversed = reversed; updateAppearance(); propagateUpdate(); } } Kolf::SlopeType Kolf::Slope::slopeType() const { return m_type; } void Kolf::Slope::setSlopeType(int type) { if (m_type != type && type >= 0) { m_type = (Kolf::SlopeType) type; updateAppearance(); propagateUpdate(); } } bool Kolf::Slope::isStuckOnGround() const { return m_stuckOnGround; } void Kolf::Slope::setStuckOnGround(bool stuckOnGround) { if (m_stuckOnGround != stuckOnGround) { m_stuckOnGround = stuckOnGround; setZBehavior(m_stuckOnGround ? CanvasItem::FixedZValue : CanvasItem::IsRaisedByStrut, 1); propagateUpdate(); } } QPainterPath Kolf::Slope::shape() const { const QRectF rect = boundingRect(); QPainterPath path; if (m_type == Kolf::CrossDiagonalSlope) { QPolygonF polygon(3); polygon[0] = rect.topLeft(); polygon[1] = rect.bottomRight(); polygon[2] = m_reversed ? rect.topRight() : rect.bottomLeft(); path.addPolygon(polygon); } else if (m_type == Kolf::DiagonalSlope) { QPolygonF polygon(3); polygon[0] = rect.topRight(); polygon[1] = rect.bottomLeft(); polygon[2] = m_reversed ? rect.topLeft() : rect.bottomRight(); path.addPolygon(polygon); } else if (m_type == Kolf::EllipticSlope) { path.addEllipse(rect); } else { path.addRect(rect); } return path; } void Kolf::Slope::setSize(const QSizeF& size) { if (m_type == Kolf::EllipticSlope) { const double extent = qMin(size.width(), size.height()); Tagaro::SpriteObjectItem::setSize(extent, extent); } else Tagaro::SpriteObjectItem::setSize(size); updateInfo(); propagateUpdate(); updateZ(this); } QPointF Kolf::Slope::getPosition() const { return Tagaro::SpriteObjectItem::pos(); } void Kolf::Slope::moveBy(double dx, double dy) { Tagaro::SpriteObjectItem::moveBy(dx, dy); CanvasItem::moveBy(dx, dy); } void Kolf::Slope::load(KConfigGroup* group) { setGrade(group->readEntry("grade", m_grade)); setReversed(group->readEntry("reversed", m_reversed)); setStuckOnGround(group->readEntry("stuckOnGround", m_stuckOnGround)); //gradient is a bit more complicated const QString type = group->readEntry("gradient", g_slopeData->gradientKeys.value(m_type)); setSlopeType(g_slopeData->gradientKeys.indexOf(type)); //read size QSizeF size = Tagaro::SpriteObjectItem::size(); size.setWidth(group->readEntry("width", size.width())); size.setHeight(group->readEntry("height", size.height())); setSize(size); } void Kolf::Slope::save(KConfigGroup* group) { group->writeEntry("grade", m_grade); group->writeEntry("reversed", m_reversed); group->writeEntry("stuckOnGround", m_stuckOnGround); group->writeEntry("gradient", g_slopeData->gradientKeys.value(m_type)); const QSizeF size = Tagaro::SpriteObjectItem::size(); group->writeEntry("width", size.width()); group->writeEntry("height", size.height()); } void Kolf::Slope::updateAppearance() { updateInfo(); //set pixmap setSpriteKey((m_reversed ? g_slopeData->reversedSpriteKeys : g_slopeData->spriteKeys).value(m_type)); } void Kolf::Slope::updateInfo() { m_gradeItem->setText(QString::number(m_grade)); const QPointF textOffset = m_gradeItem->boundingRect().center(); //update arrows const QSizeF size = Tagaro::SpriteObjectItem::size(); const double width = size.width(), height = size.height(); const double length = sqrt(width * width + height * height) / 4; if (m_type == Kolf::EllipticSlope) { double angle = 0; for (int i = 0; i < 4; ++i, angle += M_PI / 2) { ArrowItem* arrow = m_arrows[i]; arrow->setLength(length); arrow->setAngle(angle); arrow->setReversed(m_reversed); arrow->setPos(QPointF(width / 2, height / 2)); } m_gradeItem->setPos(QPointF(width / 2, height / 2) - textOffset); } else { double angle = 0; double x = .5 * width, y = .5 * height; switch ((int) m_type) { case Kolf::HorizontalSlope: angle = 0; break; case Kolf::VerticalSlope: angle = M_PI / 2; break; case Kolf::DiagonalSlope: angle = atan(width / height); x = m_reversed ? .25 * width : .75 * width; y = m_reversed ? .25 * height : .75 * height; break; case Kolf::CrossDiagonalSlope: angle = M_PI - atan(width / height); x = m_reversed ? .75 * width : .25 * width; y = m_reversed ? .25 * height : .75 * height; break; } //only one arrow needed - hide all others for (int i = 1; i < 4; ++i) m_arrows[i]->setLength(0); ArrowItem* arrow = m_arrows[0]; arrow->setLength(length); arrow->setAngle(m_reversed ? angle : angle + M_PI); arrow->setPos(QPointF(x, y)); m_gradeItem->setPos(QPointF(x, y) - textOffset); } } bool Kolf::Slope::collision(Ball* ball) { Vector v = ball->velocity(); double addto = 0.013 * m_grade; const bool diag = m_type == Kolf::DiagonalSlope || m_type == Kolf::CrossDiagonalSlope; const bool circle = m_type == Kolf::EllipticSlope; double slopeAngle = 0; const double width = size().width(), height = size().height(); if (diag) slopeAngle = atan(width / height); else if (circle) { const QPointF start = pos() + QPointF(width, height) / 2.0; const QPointF end = ball->pos(); Vector betweenVector = start - end; const double factor = betweenVector.magnitude() / (width / 2.0); slopeAngle = betweenVector.direction(); // this little bit by Daniel addto *= factor * M_PI / 2; addto = sin(addto); } if (!m_reversed) addto = -addto; switch ((int) m_type) { case Kolf::HorizontalSlope: v.rx() += addto; break; case Kolf::VerticalSlope: v.ry() += addto; break; case Kolf::DiagonalSlope: case Kolf::EllipticSlope: v.rx() += cos(slopeAngle) * addto; v.ry() += sin(slopeAngle) * addto; break; case Kolf::CrossDiagonalSlope: v.rx() -= cos(slopeAngle) * addto; v.ry() += sin(slopeAngle) * addto; break; } ball->setVelocity(v); ball->setState(v.isNull() ? Stopped : Rolling); // do NOT do terrain collidingItems return false; } bool Kolf::Slope::terrainCollisions() const { return true; } QList Kolf::Slope::infoItems() const { QList result; foreach (ArrowItem* arrow, m_arrows) result << arrow; result << m_gradeItem; return result; } Config* Kolf::Slope::config(QWidget* parent) { return new Kolf::SlopeConfig(this, parent); } Kolf::Overlay* Kolf::Slope::createOverlay() { return new Kolf::SlopeOverlay(this); } //END Kolf::Slope //BEGIN Kolf::SlopeConfig Kolf::SlopeConfig::SlopeConfig(Kolf::Slope* slope, QWidget* parent) : Config(parent) { QGridLayout* layout = new QGridLayout(this); KComboBox* typeBox = new KComboBox(this); typeBox->addItems(g_slopeData->translatedGradientKeys); typeBox->setCurrentIndex(slope->slopeType()); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) connect(typeBox, QOverload::of(&KComboBox::currentIndexChanged), slope, &Kolf::Slope::setSlopeType); -#else - connect(typeBox, QOverload::of(&KComboBox::currentIndexChanged), slope, &Kolf::Slope::setSlopeType); -#endif layout->addWidget(typeBox, 0, 0, 1, 2); QCheckBox* reversed = new QCheckBox(i18n("Reverse direction"), this); reversed->setChecked(slope->isReversed()); connect(reversed, &QCheckBox::toggled, slope, &Kolf::Slope::setReversed); layout->addWidget(reversed, 1, 0); QCheckBox* stuck = new QCheckBox(i18n("Unmovable"), this); stuck->setChecked(slope->isStuckOnGround()); stuck->setWhatsThis(i18n("Whether or not this slope can be moved by other objects, like floaters.")); connect(stuck, &QCheckBox::toggled, slope, &Kolf::Slope::setStuckOnGround); layout->addWidget(stuck, 1, 1); layout->addWidget(new QLabel(i18n("Grade:"), this), 2, 0); QDoubleSpinBox* grade = new QDoubleSpinBox(this); grade->setRange(0, 8); grade->setSingleStep(1); grade->setValue(slope->grade()); connect(grade, QOverload::of(&QDoubleSpinBox::valueChanged), slope, &Kolf::Slope::setGrade); layout->addWidget(grade, 2, 1); layout->setRowStretch(4, 10); } //END Kolf::SlopeConfig //BEGIN Kolf::SlopeOverlay Kolf::SlopeOverlay::SlopeOverlay(Kolf::Slope* slope) : Kolf::Overlay(slope, slope, true) //true = add shape to outlines { //TODO: code duplication to Kolf::LandscapeOverlay and Kolf::RectangleOverlay for (int i = 0; i < 4; ++i) { Kolf::OverlayHandle* handle = new Kolf::OverlayHandle(Kolf::OverlayHandle::CircleShape, this); m_handles << handle; addHandle(handle); connect(handle, &Kolf::OverlayHandle::moveRequest, this, &Kolf::SlopeOverlay::moveHandle); } } void Kolf::SlopeOverlay::update() { Kolf::Overlay::update(); const QRectF rect = qitem()->boundingRect(); m_handles[0]->setPos(rect.topLeft()); m_handles[1]->setPos(rect.topRight()); m_handles[2]->setPos(rect.bottomLeft()); m_handles[3]->setPos(rect.bottomRight()); } void Kolf::SlopeOverlay::moveHandle(const QPointF& handleScenePos) { Kolf::OverlayHandle* handle = qobject_cast(sender()); const int handleIndex = m_handles.indexOf(handle); Kolf::Slope* item = dynamic_cast(qitem()); const QPointF handlePos = mapFromScene(handleScenePos); //modify bounding rect using new handlePos QRectF rect(QPointF(), item->size()); if (handleIndex % 2 == 0) rect.setLeft(qMin(handlePos.x(), rect.right())); else rect.setRight(qMax(handlePos.x(), rect.left())); if (handleIndex < 2) rect.setTop(qMin(handlePos.y(), rect.bottom())); else rect.setBottom(qMax(handlePos.y(), rect.top())); item->moveBy(rect.x(), rect.y()); item->setSize(rect.size()); } //END Kolf::SlopeOverlay