1/*
2 Copyright (C) 2002-2005, Jason Katz-Brown <jasonkb@mit.edu>
3 Copyright 2010 Stefan Majewsky <majewsky@gmx.net>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18*/
19
20#include "objects.h"
21#include "ball.h"
22#include "game.h"
23#include "tagaro/board.h"
24
25#include <QFormLayout>
26#include <QTimer>
27#include <KConfigGroup>
28#include <KNumInput>
29#include <KRandom>
30
31//BEGIN Kolf::BlackHole
32
33Kolf::BlackHole::BlackHole(QGraphicsItem* parent, b2World* world)
34 : EllipticalCanvasItem(true, QLatin1String("black_hole"), parent, world)
35 , m_minSpeed(3.0)
36 , m_maxSpeed(5.0)
37 , m_runs(0)
38 , m_exitDeg(0)
39 , m_exitItem(new QGraphicsLineItem(0, -15, 0, 15, Kolf::findBoard(this)))
40 , m_directionItem(new ArrowItem(Kolf::findBoard(this)))
41 , m_infoLine(new QGraphicsLineItem(this))
42{
43 setSize(QSizeF(16, 18));
44 setZBehavior(CanvasItem::IsRaisedByStrut, 4);
45 setSimulationType(CanvasItem::NoSimulation);
46
47 const QColor myColor((QRgb)(KRandom::random() % 0x01000000));
48 ellipseItem()->setBrush(myColor);
49 m_exitItem->setPen(QPen(myColor, 6));
50 m_directionItem->setPen(myColor);
51 m_infoLine->setPen(QPen(myColor, 2));
52
53 setExitPos(QPointF(300, 100));
54 m_directionItem->setVisible(false);
55 m_infoLine->setVisible(false);
56 moveBy(0, 0); //initializes line item
57}
58
59Kolf::BlackHole::~BlackHole()
60{
61 //these items are not direct childs
62 delete m_directionItem;
63 delete m_exitItem;
64}
65
66double Kolf::BlackHole::minSpeed() const
67{
68 return m_minSpeed;
69}
70
71void Kolf::BlackHole::setMinSpeed(double news)
72{
73 m_minSpeed = news;
74 m_directionItem->setLength(10.0 + 2.5 * (m_minSpeed + m_maxSpeed));
75 propagateUpdate();
76}
77
78double Kolf::BlackHole::maxSpeed() const
79{
80 return m_maxSpeed;
81}
82
83void Kolf::BlackHole::setMaxSpeed(double news)
84{
85 m_maxSpeed = news;
86 m_directionItem->setLength(10.0 + 2.5 * (m_minSpeed + m_maxSpeed));
87 propagateUpdate();
88}
89
90QList<QGraphicsItem*> Kolf::BlackHole::infoItems() const
91{
92 return QList<QGraphicsItem*>() << m_infoLine << m_directionItem;
93}
94
95Kolf::Overlay* Kolf::BlackHole::createOverlay()
96{
97 return new Kolf::BlackHoleOverlay(this);
98}
99
100Config* Kolf::BlackHole::config(QWidget* parent)
101{
102 return new Kolf::BlackHoleConfig(this, parent);
103}
104
105void Kolf::BlackHole::moveBy(double dx, double dy)
106{
107 EllipticalCanvasItem::moveBy(dx, dy);
108 m_infoLine->setLine(QLineF(QPointF(), m_exitItem->pos() - pos()));
109 propagateUpdate();
110}
111
112int Kolf::BlackHole::curExitDeg() const
113{
114 return m_exitDeg;
115}
116
117void Kolf::BlackHole::setExitDeg(int newdeg)
118{
119 m_exitDeg = newdeg;
120 m_exitItem->setRotation(-newdeg);
121 m_directionItem->setAngle(-deg2rad(newdeg));
122 propagateUpdate();
123}
124
125QPointF Kolf::BlackHole::exitPos() const
126{
127 return m_exitItem->pos();
128}
129
130void Kolf::BlackHole::setExitPos(const QPointF& exitPos)
131{
132 m_exitItem->setPos(exitPos);
133 m_directionItem->setPos(exitPos);
134 moveBy(0, 0); //updates line item, and calls propagateUpdate()
135}
136
137Vector Kolf::BlackHole::exitDirection() const
138{
139 return m_directionItem->vector();
140}
141
142void Kolf::BlackHole::shotStarted()
143{
144 m_runs = 0;
145}
146
147bool Kolf::BlackHole::collision(Ball* ball)
148{
149 //miss if speed too high
150 const double speed = Vector(ball->velocity()).magnitude();
151 if (speed > 3.75)
152 return true;
153 // is center of ball in cup?
154 if (!contains(ball->pos() - pos()))
155 return true;
156 // warp through blackhole at most 10 times per shot
157 if (m_runs > 10 && game && game->isInPlay())
158 return true;
159
160 playSound("blackholeputin");
161
162 const double diff = m_maxSpeed - m_minSpeed;
163 const double newSpeed = m_minSpeed + speed / 3.75 * diff;
164
165 ball->setVelocity(Vector());
166 ball->setState(Stopped);
167 ball->setVisible(false);
168 ball->setForceStillGoing(true);
169
170 const double distance = Vector(pos() - m_exitItem->pos()).magnitude();
171 BlackHoleTimer* timer = new BlackHoleTimer(ball, newSpeed, distance * 2.5 - newSpeed * 35 + 500);
172
173 connect(timer, SIGNAL(eject(Ball*,double)), this, SLOT(eject(Ball*,double)));
174 connect(timer, SIGNAL(halfway()), this, SLOT(halfway()));
175
176 playSound("blackhole");
177 return false;
178}
179
180Kolf::BlackHoleTimer::BlackHoleTimer(Ball* ball, double speed, int msec)
181 : m_speed(speed), m_ball(ball)
182{
183 QTimer::singleShot(msec, this, SLOT(emitEject()));
184 QTimer::singleShot(msec / 2, this, SIGNAL(halfway()));
185}
186
187void Kolf::BlackHoleTimer::emitEject()
188{
189 emit eject(m_ball, m_speed);
190 deleteLater();
191}
192
193void Kolf::BlackHole::eject(Ball* ball, double speed)
194{
195 ball->setVisible(true);
196 //place ball 10 units after exit, and set exit velocity
197 const Vector direction = Vector::fromMagnitudeDirection(1, -deg2rad(m_exitDeg));
198 ball->setPos(m_exitItem->pos() + 10 * direction);
199 ball->setVelocity(speed * direction);
200
201 ball->setForceStillGoing(false);
202 ball->setState(Rolling);
203
204 m_runs++;
205
206 playSound("blackholeeject");
207}
208
209void Kolf::BlackHole::halfway()
210{
211 playSound("blackhole");
212}
213
214void Kolf::BlackHole::load(KConfigGroup* cfgGroup)
215{
216 setExitPos(cfgGroup->readEntry("exit", exitPos().toPoint()));
217 setExitDeg(cfgGroup->readEntry("exitDeg", m_exitDeg));
218 setMinSpeed(cfgGroup->readEntry("minspeed", m_minSpeed));
219 setMaxSpeed(cfgGroup->readEntry("maxspeed", m_maxSpeed));
220}
221
222void Kolf::BlackHole::save(KConfigGroup* cfgGroup)
223{
224 cfgGroup->writeEntry("exit", m_exitItem->pos().toPoint());
225 cfgGroup->writeEntry("exitDeg", m_exitDeg);
226 cfgGroup->writeEntry("minspeed", m_minSpeed);
227 cfgGroup->writeEntry("maxspeed", m_maxSpeed);
228}
229
230//END Kolf::BlackHole
231//BEGIN Kolf::BlackHoleConfig
232
233Kolf::BlackHoleConfig::BlackHoleConfig(BlackHole* blackHole, QWidget* parent)
234 : Config(parent)
235 , m_blackHole(blackHole)
236{
237 QFormLayout* layout = new QFormLayout(this);
238
239 KIntSpinBox* deg = new KIntSpinBox(this);
240 deg->setRange(0, 359);
241 deg->setSingleStep(10);
242 deg->setSuffix(ki18np(" degree", " degrees"));
243 deg->setValue(m_blackHole->curExitDeg());
244 deg->setWrapping(true);
245 layout->addRow(i18n("Exiting ball angle:"), deg);
246 connect(deg, SIGNAL(valueChanged(int)), this, SLOT(degChanged(int)));
247
248 KDoubleNumInput* min = new KDoubleNumInput(this);
249 min->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed));
250 min->setRange(0, 8);
251 min->setValue(m_blackHole->minSpeed());
252 layout->addRow(i18n("Minimum exit speed:"), min);
253 connect(min, SIGNAL(valueChanged(double)), this, SLOT(minChanged(double)));
254
255 KDoubleNumInput* max = new KDoubleNumInput(this);
256 max->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed));
257 max->setRange(0, 8);
258 max->setValue(m_blackHole->maxSpeed());
259 layout->addRow(i18n("Maximum exit speed:"), max);
260 connect(max, SIGNAL(valueChanged(double)), this, SLOT(maxChanged(double)));
261}
262
263void Kolf::BlackHoleConfig::degChanged(int newdeg)
264{
265 m_blackHole->setExitDeg(newdeg);
266 changed();
267}
268
269void Kolf::BlackHoleConfig::minChanged(double news)
270{
271 m_blackHole->setMinSpeed(news);
272 changed();
273}
274
275void Kolf::BlackHoleConfig::maxChanged(double news)
276{
277 m_blackHole->setMaxSpeed(news);
278 changed();
279}
280
281//END Kolf::BlackHoleConfig
282//BEGIN Kolf::BlackHoleOverlay
283
284Kolf::BlackHoleOverlay::BlackHoleOverlay(Kolf::BlackHole* blackHole)
285 : Kolf::Overlay(blackHole, blackHole)
286 , m_exitIndicator(new QGraphicsLineItem(this))
287 , m_exitHandle(new Kolf::OverlayHandle(Kolf::OverlayHandle::SquareShape, this))
288 , m_speedHandle(new Kolf::OverlayHandle(Kolf::OverlayHandle::SquareShape, this))
289{
290 addHandle(m_exitIndicator);
291 addHandle(m_exitHandle);
292 addHandle(m_speedHandle);
293 connect(m_exitHandle, SIGNAL(moveRequest(QPointF)), this, SLOT(moveHandle(QPointF)));
294 connect(m_speedHandle, SIGNAL(moveRequest(QPointF)), this, SLOT(moveHandle(QPointF)));
295}
296
297void Kolf::BlackHoleOverlay::update()
298{
299 Kolf::Overlay::update();
300 Kolf::BlackHole* blackHole = dynamic_cast<Kolf::BlackHole*>(qitem());
301 m_exitHandle->setPos(blackHole->exitPos() - blackHole->pos());
302 m_speedHandle->setPos(m_exitHandle->pos() + blackHole->exitDirection());
303 m_exitIndicator->setLine(QLineF(m_exitHandle->pos(), m_speedHandle->pos()));
304 const qreal exitAngle = -deg2rad(blackHole->curExitDeg());
305 m_exitHandle->setRotation(exitAngle);
306 m_speedHandle->setRotation(exitAngle);
307}
308
309void Kolf::BlackHoleOverlay::moveHandle(const QPointF& handleScenePos)
310{
311 Kolf::BlackHole* blackHole = dynamic_cast<Kolf::BlackHole*>(qitem());
312 if (sender() == m_exitHandle)
313 blackHole->setExitPos(handleScenePos);
314 else if (sender() == m_speedHandle)
315 {
316 //this modifies only exit direction, not speed
317 Vector dir = handleScenePos - blackHole->exitPos();
318 blackHole->setExitDeg(-rad2deg(dir.direction()));
319 }
320}
321
322//END Kolf::BlackHoleOverlay
323//BEGIN Kolf::Cup
324
325Kolf::Cup::Cup(QGraphicsItem* parent, b2World* world)
326 : EllipticalCanvasItem(false, QLatin1String("cup"), parent, world)
327{
328 const int diameter = 16;
329 setSize(QSizeF(diameter, diameter));
330 setZBehavior(CanvasItem::IsRaisedByStrut, 4);
331 setSimulationType(CanvasItem::NoSimulation);
332}
333
334Kolf::Overlay* Kolf::Cup::createOverlay()
335{
336 return new Kolf::Overlay(this, this);
337}
338
339bool Kolf::Cup::collision(Ball* ball)
340{
341 //miss if speed too high
342 const double speed = Vector(ball->velocity()).magnitude();
343 if (speed > 3.75)
344 return true;
345 //miss if center of ball not inside cup
346 if (!contains(ball->pos() - pos()))
347 return true;
348 //place ball in hole
349 ball->setState(Holed);
350 playSound("holed");
351 ball->setPos(pos());
352 ball->setVelocity(Vector());
353 return false;
354}
355
356//END Kolf::Cup
357
358#include "objects.moc"
359